summaryrefslogtreecommitdiffstats
path: root/build/clang-plugin/NonParamInsideFunctionDeclChecker.cpp
blob: e74fd2bb080adec66a96ad825782c2eb4800a150 (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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/* 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 "NonParamInsideFunctionDeclChecker.h"
#include "CustomMatchers.h"
#include "clang/Basic/TargetInfo.h"

class NonParamAnnotation : public CustomTypeAnnotation {
public:
  NonParamAnnotation() : CustomTypeAnnotation(moz_non_param, "non-param"){};

protected:
  // Helper for checking if a Decl has an explicitly specified alignment.
  // Returns the alignment, in char units, of the largest alignment attribute,
  // if it exceeds pointer alignment, and 0 otherwise.
  static unsigned checkExplicitAlignment(const Decl *D) {
    ASTContext &Context = D->getASTContext();
#if CLANG_VERSION_FULL >= 1600
    unsigned PointerAlign = Context.getTargetInfo().getPointerAlign(LangAS::Default);
#else
    unsigned PointerAlign = Context.getTargetInfo().getPointerAlign(0);
#endif

    // getMaxAlignment gets the largest alignment, in bits, specified by an
    // alignment attribute directly on the declaration. If no alignment
    // attribute is specified, it will return `0`.
    unsigned MaxAlign = D->getMaxAlignment();
    if (MaxAlign > PointerAlign) {
      return Context.toCharUnitsFromBits(MaxAlign).getQuantity();
    }
    return 0;
  }

  // This is directly derived from the logic in Clang's `canPassInRegisters`
  // function, from `SemaDeclCXX`. It is used instead of `canPassInRegisters` to
  // behave consistently on 64-bit windows platforms which are overly
  // permissive, allowing too many types to be passed in registers.
  //
  // Types which can be passed in registers will be re-aligned in the called
  // function by clang, so aren't impacted by the win32 object passing ABI
  // alignment issue.
  static bool canPassAsTemporary(const CXXRecordDecl *D) {
    // Per C++ [class.temporary]p3:
    //
    // When an object of class type X is passed to or returned from a function,
    // if X has at least one eligible copy or move constructor ([special]), each
    // such constructor is trivial, and the destructor of X is either trivial or
    // deleted, implementations are permitted to create a temporary object to
    // hold the function parameter or result object.
    //
    // The temporary object is constructed from the function argument or return
    // value, respectively, and the function's parameter or return object is
    // initialized as if by using the eligible trivial constructor to copy the
    // temporary (even if that constructor is inaccessible or would not be
    // selected by overload resolution to perform a copy or move of the object).
    bool HasNonDeletedCopyOrMove = false;

    if (D->needsImplicitCopyConstructor() &&
        !D->defaultedCopyConstructorIsDeleted()) {
      if (!D->hasTrivialCopyConstructorForCall())
        return false;
      HasNonDeletedCopyOrMove = true;
    }

    if (D->needsImplicitMoveConstructor() &&
        !D->defaultedMoveConstructorIsDeleted()) {
      if (!D->hasTrivialMoveConstructorForCall())
        return false;
      HasNonDeletedCopyOrMove = true;
    }

    if (D->needsImplicitDestructor() && !D->defaultedDestructorIsDeleted() &&
        !D->hasTrivialDestructorForCall())
      return false;

    for (const CXXMethodDecl *MD : D->methods()) {
      if (MD->isDeleted())
        continue;

      auto *CD = dyn_cast<CXXConstructorDecl>(MD);
      if (CD && CD->isCopyOrMoveConstructor())
        HasNonDeletedCopyOrMove = true;
      else if (!isa<CXXDestructorDecl>(MD))
        continue;

      if (!MD->isTrivialForCall())
        return false;
    }

    return HasNonDeletedCopyOrMove;
  }

  // Adding alignas(_) on a struct implicitly marks it as MOZ_NON_PARAM, due to
  // MSVC limitations which prevent passing explcitly aligned types by value as
  // parameters. This overload of hasFakeAnnotation injects fake MOZ_NON_PARAM
  // annotations onto these types.
  std::string getImplicitReason(const TagDecl *D,
                                VisitFlags &ToVisit) const override {
    // Some stdlib types are known to have alignments over the pointer size on
    // non-win32 platforms, but should not be linted against. Clear any
    // annotations on those types.
    if (!D->getASTContext().getTargetInfo().getCXXABI().isMicrosoft() &&
        getDeclarationNamespace(D) == "std") {
      StringRef Name = getNameChecked(D);
      if (Name == "function") {
        ToVisit = VISIT_NONE;
        return "";
      }
    }

    // If the type doesn't have a destructor and can be passed with a temporary,
    // clang will handle re-aligning it for us automatically, and we don't need
    // to worry about the passed alignment.
    auto RD = dyn_cast<CXXRecordDecl>(D);
    if (RD && RD->isCompleteDefinition() && canPassAsTemporary(RD)) {
      return "";
    }

    // Check if the decl itself has an explicit alignment on it.
    if (unsigned ExplicitAlign = checkExplicitAlignment(D)) {
      return "it has an explicit alignment of '" +
             std::to_string(ExplicitAlign) + "'";
    }

    // Check if any of the decl's fields have an explicit alignment on them.
    if (auto RD = dyn_cast<RecordDecl>(D)) {
      for (auto F : RD->fields()) {
        if (unsigned ExplicitAlign = checkExplicitAlignment(F)) {
          return ("member '" + F->getName() +
                  "' has an explicit alignment of '" +
                  std::to_string(ExplicitAlign) + "'")
              .str();
        }
      }
    }

    // We don't need to check the types of fields, as the CustomTypeAnnotation
    // infrastructure will handle that for us.
    return "";
  }
};
NonParamAnnotation NonParam;

void NonParamInsideFunctionDeclChecker::registerMatchers(
    MatchFinder *AstMatcher) {
  AstMatcher->addMatcher(
      functionDecl(
          anyOf(allOf(isDefinition(),
                      hasAncestor(
                          classTemplateSpecializationDecl().bind("spec"))),
                isDefinition()))
          .bind("func"),
      this);
  AstMatcher->addMatcher(lambdaExpr().bind("lambda"), this);
}

void NonParamInsideFunctionDeclChecker::check(
    const MatchFinder::MatchResult &Result) {
  static DenseSet<const FunctionDecl *> CheckedFunctionDecls;

  const FunctionDecl *func = Result.Nodes.getNodeAs<FunctionDecl>("func");
  if (!func) {
    const LambdaExpr *lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
    if (lambda) {
      func = lambda->getCallOperator();
    }
  }

  if (!func) {
    return;
  }

  if (func->isDeleted()) {
    return;
  }

  // We need to skip decls which have these types as parameters in system
  // headers, because presumably those headers act like an assertion that the
  // alignment will be preserved in that situation.
  if (getDeclarationNamespace(func) == "std") {
    return;
  }

  if (inThirdPartyPath(func)) {
    return;
  }

  // Don't report errors on the same declarations more than once.
  if (CheckedFunctionDecls.count(func)) {
    return;
  }
  CheckedFunctionDecls.insert(func);

  const ClassTemplateSpecializationDecl *Spec =
      Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("spec");

  for (ParmVarDecl *p : func->parameters()) {
    QualType T = p->getType().withoutLocalFastQualifiers();
    if (NonParam.hasEffectiveAnnotation(T)) {
      diag(p->getLocation(), "Type %0 must not be used as parameter",
           DiagnosticIDs::Error)
          << T;
      diag(p->getLocation(),
           "Please consider passing a const reference instead",
           DiagnosticIDs::Note);

      if (Spec) {
        diag(Spec->getPointOfInstantiation(),
             "The bad argument was passed to %0 here", DiagnosticIDs::Note)
            << Spec->getSpecializedTemplate();
      }

      NonParam.dumpAnnotationReason(*this, T, p->getLocation());
    }
  }
}