summaryrefslogtreecommitdiffstats
path: root/docs/code-quality/static-analysis/writing-new/advanced-check-features.rst
blob: e8adcf664ffb8557a60ce20a1aa2a5adbd423af2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
.. _advanced_check_features:

Advanced Check Features
=======================

This page covers additional ways to improve and extend the check you've added to build/clang-plugin.

Adding Tests
------------

No doubt you've seen the tests for existing checks in `build/clang-plugin/tests <https://searchfox.org/mozilla-central/source/build/clang-plugin/tests>`_. Adding tests is straightforward; and your reviewer should insist you do so. Simply copying the existing format of any test and how diagnostics are marked as expected.

One wrinkle - all clang plugin checks are applied to all tests. We try to write tests so that only one check applies to it.  If you write a check that triggers on an existing test, try to fix the existing test slightly so the new check does not trigger on it.

Using Bind To Output More Useful Information
--------------------------------------------

You've probably been wondering what the heck ``.bind()`` is for. You've been seeing it all over the place but never has it actually been explained what it's for and when to use it.

``.bind()`` is used to give a name to part of the AST discovered through your matcher, so you can use it later.  Let's go back to our sample matcher:

::

  AstMatcher->addMatcher(
    traverse(TK_IgnoreUnlessSpelledInSource,
      ifStmt(allOf(
              has(
                   binaryOperator(
                       has(
                           declRefExpr(hasType(enumDecl()))
                       )
                   )
               ),
               hasElse(
                   ifStmt(allOf(
                      unless(hasElse(anything())),
                      has(
                           binaryOperator(
                               has(
                                   declRefExpr(hasType(enumDecl()))
                               )
                           )
                       )
                   ))
              )
           ))
          .bind("node")),
      this);

Now the ``.bind("node")`` makes more sense. We're naming the If statement we matched, so we can refer to it later when we call ``Result.Nodes.getNodeAs<IfStmt>("node")``.

Let's say we want to provide the *type* of the enum in our warning message.  There are two enums we end up seeing in our matcher - the enum in the first if statement, and the enum in the second.  We're going to arbitrarily pick the first and give it the name ``enumType``:

::

  AstMatcher->addMatcher(
    traverse(TK_IgnoreUnlessSpelledInSource,
      ifStmt(allOf(
              has(
                   binaryOperator(
                       has(
                           declRefExpr(hasType(enumDecl().bind("enumType")))
                       )
                   )
               ),
               hasElse(
                   ifStmt(allOf(
                      unless(hasElse(anything())),
                      has(
                           binaryOperator(
                               has(
                                   declRefExpr(hasType(enumDecl()))
                               )
                           )
                       )
                   ))
              )
           ))
          .bind("node")),
      this);

And in our check() function, we can use it like so:

::

  void MissingElseInEnumComparisons::check(
      const MatchFinder::MatchResult &Result) {
    const auto *MatchedDecl = Result.Nodes.getNodeAs<IfStmt>("node");
    const auto *EnumType = Result.Nodes.getNodeAs<EnumDecl>("enumType");

    diag(MatchedDecl->getIfLoc(),
         "Enum comparisons to %0 in an if/else if block without a trailing else.",
         DiagnosticIDs::Warning) << EnumType->getName();
  }

Repeated matcher calls
--------------------------

If you find yourself repeating the same several matchers in several spots, you can turn it into a variable to use.

::

  auto isTemporaryLifetimeBoundCall =
      cxxMemberCallExpr(
          onImplicitObjectArgument(anyOf(has(cxxTemporaryObjectExpr()),
                                         has(materializeTemporaryExpr()))),
          callee(functionDecl(isMozTemporaryLifetimeBound())));

  auto hasTemporaryLifetimeBoundCall =
      anyOf(isTemporaryLifetimeBoundCall,
            conditionalOperator(
                anyOf(hasFalseExpression(isTemporaryLifetimeBoundCall),
                      hasTrueExpression(isTemporaryLifetimeBoundCall))));

The above example is parameter-less, but if you need to supply a parameter that changes, you can turn it into a lambda:

::

  auto hasConstCharPtrParam = [](const unsigned int Position) {
    return hasParameter(
        Position, hasType(hasCanonicalType(pointsTo(asString("const char")))));
  };

  auto hasParamOfType = [](const unsigned int Position, const char *Name) {
    return hasParameter(Position, hasType(asString(Name)));
  };

  auto hasIntegerParam = [](const unsigned int Position) {
    return hasParameter(Position, hasType(isInteger()));
  };

  AstMatcher->addMatcher(
      callExpr(
        hasName("fopen"),
        hasConstCharPtrParam(0))
          .bind("funcCall"),
      this);


Allow-listing existing callsites
--------------------------------

While it's not a great situation, you can set up an allow-list of existing callsites if you need to.  A simple allow-list is demonstrated in `NoGetPrincipalURI <https://hg.mozilla.org/mozilla-central/rev/fb60b22ee6616521b386d90aec07b03b77905f4e>`_. The `NoNewThreadsChecker <https://hg.mozilla.org/mozilla-central/rev/f400f164b3947b4dd54089a36ea31cca2d72805b>`_ is an example of a more sophisticated way of setting up a larger allow-list.


Custom Annotations
------------------
It's possible to create custom annotations that will be a no-op when compiled, but can be used by a static analysis check. These can be used to annotate special types of sources and sinks (for example).  We have some examples of this in-tree presently (such as ``MOZ_CAN_RUN_SCRIPT``) but currently don't have a detailed walkthrough in this documentation of how to set these up and use them. (Patches welcome.)