tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

NonParamInsideFunctionDeclChecker.cpp (7898B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "NonParamInsideFunctionDeclChecker.h"
      6 #include "CustomMatchers.h"
      7 #include "clang/Basic/TargetInfo.h"
      8 
      9 class NonParamAnnotation : public CustomTypeAnnotation {
     10 public:
     11  NonParamAnnotation() : CustomTypeAnnotation(moz_non_param, "non-param"){};
     12 
     13 protected:
     14  // Helper for checking if a Decl has an explicitly specified alignment.
     15  // Returns the alignment, in char units, of the largest alignment attribute,
     16  // if it exceeds pointer alignment, and 0 otherwise.
     17  static unsigned checkExplicitAlignment(const Decl *D) {
     18    ASTContext &Context = D->getASTContext();
     19 #if CLANG_VERSION_FULL >= 1600
     20    unsigned PointerAlign = Context.getTargetInfo().getPointerAlign(LangAS::Default);
     21 #else
     22    unsigned PointerAlign = Context.getTargetInfo().getPointerAlign(0);
     23 #endif
     24 
     25    // getMaxAlignment gets the largest alignment, in bits, specified by an
     26    // alignment attribute directly on the declaration. If no alignment
     27    // attribute is specified, it will return `0`.
     28    unsigned MaxAlign = D->getMaxAlignment();
     29    if (MaxAlign > PointerAlign) {
     30      return Context.toCharUnitsFromBits(MaxAlign).getQuantity();
     31    }
     32    return 0;
     33  }
     34 
     35  // This is directly derived from the logic in Clang's `canPassInRegisters`
     36  // function, from `SemaDeclCXX`. It is used instead of `canPassInRegisters` to
     37  // behave consistently on 64-bit windows platforms which are overly
     38  // permissive, allowing too many types to be passed in registers.
     39  //
     40  // Types which can be passed in registers will be re-aligned in the called
     41  // function by clang, so aren't impacted by the win32 object passing ABI
     42  // alignment issue.
     43  static bool canPassAsTemporary(const CXXRecordDecl *D) {
     44    // Per C++ [class.temporary]p3:
     45    //
     46    // When an object of class type X is passed to or returned from a function,
     47    // if X has at least one eligible copy or move constructor ([special]), each
     48    // such constructor is trivial, and the destructor of X is either trivial or
     49    // deleted, implementations are permitted to create a temporary object to
     50    // hold the function parameter or result object.
     51    //
     52    // The temporary object is constructed from the function argument or return
     53    // value, respectively, and the function's parameter or return object is
     54    // initialized as if by using the eligible trivial constructor to copy the
     55    // temporary (even if that constructor is inaccessible or would not be
     56    // selected by overload resolution to perform a copy or move of the object).
     57    bool HasNonDeletedCopyOrMove = false;
     58 
     59    if (D->needsImplicitCopyConstructor() &&
     60        !D->defaultedCopyConstructorIsDeleted()) {
     61      if (!D->hasTrivialCopyConstructorForCall())
     62        return false;
     63      HasNonDeletedCopyOrMove = true;
     64    }
     65 
     66    if (D->needsImplicitMoveConstructor() &&
     67        !D->defaultedMoveConstructorIsDeleted()) {
     68      if (!D->hasTrivialMoveConstructorForCall())
     69        return false;
     70      HasNonDeletedCopyOrMove = true;
     71    }
     72 
     73    if (D->needsImplicitDestructor() && !D->defaultedDestructorIsDeleted() &&
     74        !D->hasTrivialDestructorForCall())
     75      return false;
     76 
     77    for (const CXXMethodDecl *MD : D->methods()) {
     78      if (MD->isDeleted())
     79        continue;
     80 
     81      auto *CD = dyn_cast<CXXConstructorDecl>(MD);
     82      if (CD && CD->isCopyOrMoveConstructor())
     83        HasNonDeletedCopyOrMove = true;
     84      else if (!isa<CXXDestructorDecl>(MD))
     85        continue;
     86 
     87      if (!MD->isTrivialForCall())
     88        return false;
     89    }
     90 
     91    return HasNonDeletedCopyOrMove;
     92  }
     93 
     94  // Adding alignas(_) on a struct implicitly marks it as MOZ_NON_PARAM, due to
     95  // MSVC limitations which prevent passing explcitly aligned types by value as
     96  // parameters. This overload of hasFakeAnnotation injects fake MOZ_NON_PARAM
     97  // annotations onto these types.
     98  std::string getImplicitReason(const TagDecl *D,
     99                                VisitFlags &ToVisit) const override {
    100    // Some stdlib types are known to have alignments over the pointer size on
    101    // non-win32 platforms, but should not be linted against. Clear any
    102    // annotations on those types.
    103    if (!D->getASTContext().getTargetInfo().getCXXABI().isMicrosoft() &&
    104        getDeclarationNamespace(D) == "std") {
    105      StringRef Name = getNameChecked(D);
    106      if (Name == "function") {
    107        ToVisit = VISIT_NONE;
    108        return "";
    109      }
    110    }
    111 
    112    // If the type doesn't have a destructor and can be passed with a temporary,
    113    // clang will handle re-aligning it for us automatically, and we don't need
    114    // to worry about the passed alignment.
    115    auto RD = dyn_cast<CXXRecordDecl>(D);
    116    if (RD && RD->isCompleteDefinition() && canPassAsTemporary(RD)) {
    117      return "";
    118    }
    119 
    120    // Check if the decl itself has an explicit alignment on it.
    121    if (unsigned ExplicitAlign = checkExplicitAlignment(D)) {
    122      return "it has an explicit alignment of '" +
    123             std::to_string(ExplicitAlign) + "'";
    124    }
    125 
    126    // Check if any of the decl's fields have an explicit alignment on them.
    127    if (auto RD = dyn_cast<RecordDecl>(D)) {
    128      for (auto F : RD->fields()) {
    129        if (unsigned ExplicitAlign = checkExplicitAlignment(F)) {
    130          return ("member '" + F->getName() +
    131                  "' has an explicit alignment of '" +
    132                  std::to_string(ExplicitAlign) + "'")
    133              .str();
    134        }
    135      }
    136    }
    137 
    138    // We don't need to check the types of fields, as the CustomTypeAnnotation
    139    // infrastructure will handle that for us.
    140    return "";
    141  }
    142 };
    143 NonParamAnnotation NonParam;
    144 
    145 void NonParamInsideFunctionDeclChecker::registerMatchers(
    146    MatchFinder *AstMatcher) {
    147  AstMatcher->addMatcher(
    148      functionDecl(
    149          anyOf(allOf(isDefinition(),
    150                      hasAncestor(
    151                          classTemplateSpecializationDecl().bind("spec"))),
    152                isDefinition()))
    153          .bind("func"),
    154      this);
    155  AstMatcher->addMatcher(lambdaExpr().bind("lambda"), this);
    156 }
    157 
    158 void NonParamInsideFunctionDeclChecker::check(
    159    const MatchFinder::MatchResult &Result) {
    160  static DenseSet<const FunctionDecl *> CheckedFunctionDecls;
    161 
    162  const FunctionDecl *func = Result.Nodes.getNodeAs<FunctionDecl>("func");
    163  if (!func) {
    164    const LambdaExpr *lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
    165    if (lambda) {
    166      func = lambda->getCallOperator();
    167    }
    168  }
    169 
    170  if (!func) {
    171    return;
    172  }
    173 
    174  if (func->isDeleted()) {
    175    return;
    176  }
    177 
    178  // We need to skip decls which have these types as parameters in system
    179  // headers, because presumably those headers act like an assertion that the
    180  // alignment will be preserved in that situation.
    181  if (getDeclarationNamespace(func) == "std") {
    182    return;
    183  }
    184 
    185  if (inThirdPartyPath(func)) {
    186    return;
    187  }
    188 
    189  // Don't report errors on the same declarations more than once.
    190  if (CheckedFunctionDecls.count(func)) {
    191    return;
    192  }
    193  CheckedFunctionDecls.insert(func);
    194 
    195  const ClassTemplateSpecializationDecl *Spec =
    196      Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("spec");
    197 
    198  for (ParmVarDecl *p : func->parameters()) {
    199    QualType T = p->getType().withoutLocalFastQualifiers();
    200    if (NonParam.hasEffectiveAnnotation(T)) {
    201      diag(p->getLocation(), "Type %0 must not be used as parameter",
    202           DiagnosticIDs::Error)
    203          << T;
    204      diag(p->getLocation(),
    205           "Please consider passing a const reference instead",
    206           DiagnosticIDs::Note);
    207 
    208      if (Spec) {
    209        diag(Spec->getPointOfInstantiation(),
    210             "The bad argument was passed to %0 here", DiagnosticIDs::Note)
    211            << Spec->getSpecializedTemplate();
    212      }
    213 
    214      NonParam.dumpAnnotationReason(*this, T, p->getLocation());
    215    }
    216  }
    217 }