Skip to content

Latest commit

 

History

History
260 lines (185 loc) · 11.5 KB

style_lint.md

File metadata and controls

260 lines (185 loc) · 11.5 KB

SV Style Linter Developer Guide

This document describes how to implement style lint rules.

Before you begin, familiarize yourself with:

Communication

Whose Style Guide?

Whose style guide serves as a reference for lint rules? Everyone and anyone's. Every team may set its own guidelines on what constitutes correct style. The style linter hosts an ever-growing library of rules, but you decide which rules or configurations best suit your project.

Traits of good lint rules

  • Identify error-prone constructs, including those that may lead to production bugs
  • Have few exceptions, low risk of false positives, and low frequency of lint waivers
  • Reduces the number of choices for ways-to-express-a-concept
  • Rules that enforce SV-LRM conformance should be implemented in actual compilers, but sometimes can be enforced in style linters as well.

Types of Analyses

The major classes of text analyses available today are:

For complete links to examples of each of the above lint rule classes, click on the class definition and navigate to "Extended By" inside the "Cross References" panel in the code search viewer.

Syntax Tree Analysis Tools

This section describes various tools and libraries for analyzing and querying syntax trees.

Syntax Tree Examiner

Use verible-verilog-syntax --printtree to examine the syntax structure of examples of code of interest.

Syntax Tree Visitors

TreeContextVisitor is a syntax tree visitor that maintains a stack of ancestor nodes in a stack as it traverses nodes and leaves. This is useful for being able to query the stack to determine the context at any node.

Syntax Tree Direct Substructure Access

The SV concrete syntax tree (CST) is described here. The CST library contains a number of useful GetXFromY-type accessor functions. These functions offer the most direct way of extracting information from syntax tree nodes. Accessor functions are useful when you've already narrowed down your search to one or a few specific types (enums) of syntax tree nodes.

Pros:

  • Fast, because no search is involved.

Cons:

  • You may have to write some new CST accessor functions if what you need doesn't already exist.

Syntax Tree Searching

SearchSyntaxTree is a generic search function for identifying all syntax tree nodes that satisfy a given predicate. Searching with this function yields TreeSearchMatch objects that point to syntax tree nodes/leaves and include the context in which the node matched.

Pros:

  • Good for finding nodes when you don't know (or don't care) where in a subtree they could appear.
  • More resilient (but not immune) to CST restructuring.

Cons:

  • Slower than direct access because a search always visits every subnode and leaf.

Syntax Tree Pattern Matching

The CST Matcher library provides a convenient way to create matcher objects that describe certain syntactic patterns.

The Syntax Tree Matcher library uses some principles from Clang's ASTMatcher Library.

Pros:

  • Expressive and composeable.
  • More resilient to CST positional substructure changes such as changing child ranks.

Cons:

  • Can be expensive due to searching.

Single Node Type Matchers

For every SystemVerilog CST node enum, we produce a corresponding node-matcher in verilog_matchers.h that finds that node type. For example, NodekFunctionDeclaration matches nodes tagged kFunctionDeclaration. These are defined using TagMatchBuilder.

Path-based Matchers

Path matchers are a shorthand for expressing a match on an ancestral chain of node types. For example, MakePathMatcher({X, Y, Z}), where X, Y, and Z are CST tags for specific nodes types, creates a matcher that will find nodes of type X that directly contain a Y child that directly contains a Z child. verilog_matchers.h contains several examples.

Composition

Every matcher object can accept inner matchers that can refine matching conditions and narrow search results. Composing matchers looks like OuterMatcher(InnerMatcher(...), ...), which would return a positive match on a node that matches OuterMatcher, whose subtree also satisfies InnerMatcher.

Matcher operators are functions described in core_matchers.h.

Summary:

  • AllOf(...) matches positively if all of its inner matchers positively.
  • AnyOf(...) matches positively if any of its inner matchers positively.
  • Unless(...) matches positively if its inner matcher does not to match.

TagMatchBuilders by default combines its inner matchers with AllOf, so can write NodekFoo(InnerMatcher1(), InnerMatcher2()) instead of the equivalent NodekFoo(AllOf(InnerMatcher1(), InnerMatcher2())).

The order of the inner matchers to the above functions is inconsequential to the match result; they are fully commutative.

Named Binding of Symbols

Many matchers support binding to user-provided names called [BindableMatchers]. This lets you save interesting subtree positions found during the match and retrieve them from a BoundSymbolManager. Example using .Bind().

Reporting Positive Findings

When you've determined that the code being analyzed matches a pattern of interest, record a LintViolation object.

Narrow down the location of the offending substring of text as much as possible so that users can see precisely what is wrong. You can select a whole block of text, a syntax node subtree, a single token, or even a substring within a token.

Include a diagnostic message that describes the problem, citing a passage from a style guide. Recommend a corrective action where you can.

Each lint rule that analyzes code produces a LintRuleStatus that contains a set of [LintViolations].

Lint Rule Configuration

Testing

A typical set of lint rule tests follow this template:

TEST(LintRuleNameTest, Various) {
  const std::initializer_list<LintTestCase> kTestCases = {
    ...
  };
  RunLintTestCases<VerilogAnalyzer, LintRuleName>(kTestCases);
}

Each LintTestCase is built from mix of plain string literals and tagged string literals that markup where findings are to be expected.

  {"uninteresting text ",
  {kSymbolType, "interesting text"},  // expect a finding over this substring
  "; uninteresting text"},

The test driver converts this into input code to analyze and a set of expected findings. A test will fail if the actual findings do not match the expected ones exactly (down to their locations).

Make sure to include negative tests that expect no lint violations.

Exercises