summaryrefslogtreecommitdiffstats
path: root/build/clang-plugin/TemporaryLifetimeBoundChecker.cpp
blob: dc66f62b0d3a141d4fa5eb139eeb4b2ea91ff912 (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
/* 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 "TemporaryLifetimeBoundChecker.h"
#include "CustomMatchers.h"
#include "clang/Lex/Lexer.h"

void TemporaryLifetimeBoundChecker::registerMatchers(MatchFinder *AstMatcher) {
  // Look for a call to a MOZ_LIFETIME_BOUND member function
  auto isTemporaryLifetimeBoundCall =
      cxxMemberCallExpr(
          onImplicitObjectArgument(anyOf(has(cxxTemporaryObjectExpr()),
                                         has(materializeTemporaryExpr()))),
          callee(functionDecl(isMozTemporaryLifetimeBound())))
          .bind("call");

  // XXX This definitely does not catch everything relevant. In particular, the
  // matching on conditionalOperator would need to be recursive. But it's a
  // start.
  auto hasTemporaryLifetimeBoundCall =
      anyOf(isTemporaryLifetimeBoundCall,
            conditionalOperator(
                anyOf(hasFalseExpression(isTemporaryLifetimeBoundCall),
                      hasTrueExpression(isTemporaryLifetimeBoundCall))));

  AstMatcher->addMatcher(
      returnStmt(hasReturnValue(
                     allOf(exprWithCleanups().bind("expr-with-cleanups"),
                           ignoringParenCasts(hasTemporaryLifetimeBoundCall))))
          .bind("return-stmt"),
      this);

  AstMatcher->addMatcher(
      varDecl(hasType(references(cxxRecordDecl())),
              hasInitializer(
                  allOf(exprWithCleanups(),
                        ignoringParenCasts(hasTemporaryLifetimeBoundCall))))
          .bind("var-decl"),
      this);
}

void TemporaryLifetimeBoundChecker::check(
    const MatchFinder::MatchResult &Result) {
  const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call");
  const auto *ReturnStatement =
      Result.Nodes.getNodeAs<ReturnStmt>("return-stmt");
  const auto *ReferenceVarDecl = Result.Nodes.getNodeAs<VarDecl>("var-decl");

  const char ErrorReturn[] =
      "cannot return result of lifetime-bound function %0 on "
      "temporary of type %1";

  const char ErrorBindToReference[] =
      "cannot bind result of lifetime-bound function %0 on "
      "temporary of type %1 to reference, does not extend lifetime";

  const char NoteCalledFunction[] = "member function declared here";

  // We are either a return statement...
  if (ReturnStatement) {
    const auto *ExprWithCleanups =
        Result.Nodes.getNodeAs<Expr>("expr-with-cleanups");
    if (!ExprWithCleanups->isLValue()) {
      return;
    }

    const auto Range = ReturnStatement->getSourceRange();

    diag(Range.getBegin(), ErrorReturn, DiagnosticIDs::Error)
        << Range << Call->getMethodDecl()
        << Call->getImplicitObjectArgument()
               ->getType()
               .withoutLocalFastQualifiers();
  }

  // ... or a variable declaration that declare a reference
  if (ReferenceVarDecl) {
    const auto Range = ReferenceVarDecl->getSourceRange();

    diag(Range.getBegin(), ErrorBindToReference, DiagnosticIDs::Error)
        << Range << Call->getMethodDecl()
        << Call->getImplicitObjectArgument()
               ->getType()
               .withoutLocalFastQualifiers();
  }

  const auto *MethodDecl = Call->getMethodDecl();
  diag(MethodDecl->getCanonicalDecl()->getLocation(), NoteCalledFunction,
       DiagnosticIDs::Note);
}