summaryrefslogtreecommitdiffstats
path: root/docs/code-quality/static-analysis/writing-new/adding-a-check.rst
blob: ec3bc030af5e86b66978894f873f8f961ddfd8f0 (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
.. _add_a_check:

Adding a check
==============

After you've completed a matcher using clang-query, it's time to take it to the next step and turn it into C++ and run it on the whole m-c codebase and see what happens.

Clang plugins live in `build/clang-plugin <https://searchfox.org/mozilla-central/source/build/clang-plugin>`_ and here we'll cover what is needed to add one. To see how the most recent check was added, you can look at the log for `Checks.inc <https://hg.mozilla.org/mozilla-central/log/tip/build/clang-plugin/Checks.inc>`_ which is one of the necessary files to edit.  That's also what we'll be covering next.

Boilerplate Steps to Add a New Check
------------------------------------

First pick a name. Pick something that makes sense without punctuation, in no more than 8 words or so.  For this example we'll call it "Missing Else In Enum Comparisons".

#. Add it alphabetically in build/clang-plugin/Checks.inc, ChecksIncludes.inc, and moz.build
#. ``cd build/clang-plugin && touch MissingElseInEnumComparisons.h MissingElseInEnumComparisons.cpp``
#. Copy the contents of an existing, simple .h file (e.g. `build/clang-plugin/ScopeChecker.h <https://searchfox.org/mozilla-central/source/build/clang-plugin/ScopeChecker.h>`_) and edit the class name and header guards.
#. Create the following boilerplate for your implementation:

::

  /* This Source Code Form is subject to the terms of the Mozilla Public
   * License, v. 2.0. If a copy of the MPL was not distributed with this
   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

  #include "MissingElseInEnumComparisons.h"
  #include "CustomMatchers.h"

  void MissingElseInEnumComparisons::registerMatchers(MatchFinder *AstMatcher) {

  }

  void MissingElseInEnumComparisons::check(const MatchFinder::MatchResult &Result) {

  }


Converting your matcher to C++
------------------------------
With the boilerplate out of the way, now we can focus on converting the matcher over to C++.  Once it's in C++ you'll also be able to take advantage of techniques that will make your matcher easier to read and understand.

The gist of converting your matcher is to take the following pseudo-code and paste your entire matcher in where 'foo' is; keeping the `.bind("node")` there:

::

  AstMatcher->addMatcher(
      traverse(TK_IgnoreUnlessSpelledInSource,
          foo
          .bind("node")),
      this);


It honest to god is usually that easy.  Here's a working example where I pasted in straight from Compiler Explorer:

::

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



If for some reason you're not using the ``IgnoreUnlessSpelledInSource`` Traversal Mode, remove the call to traverse and the corresponding closing paren.  (Also, if you're comparing this code to existing source code, know that because this traversal mode is a new clang feature, most historical clang checks do not use it.)

Wiring up Warnings and Errors
-----------------------------
To get started with a some simple output, just take the boilerplate warning here and stick it in:

::

  const auto *MatchedDecl = Result.Nodes.getNodeAs<IfStmt>("node");
    diag(MatchedDecl->getIfLoc(),
        "Enum comparisons in an if/else if block without a trailing else.",
        DiagnosticIDs::Warning);


You'll need to edit two things:

#. Make sure "node" matches whatever you put in `.bind()` up above.
#. ``getNodeAs<IfStmt>`` needs to be changed to whatever type of element "node" is. Above, we bind "node" to an ifStmt so that's what we need to cast it to. Doing this step incorrectly will cause clang to crash during compilation as if there was some internal compiler error.


Running it on Central
----------------------
After this, the next thing to do is to add ``ac_add_options --enable-clang-plugin`` to your .mozconfig and do a build. Your plugin will be automatically compiled and used across the entire codebase.  I suggest using ``./mach build | tee output.txt`` and then ``grep "Enum comparisons" output.txt | cut -d " " -f 3- | sort | uniq``.  (The ``cut`` is there to get rid of the timestamp in the line.)