void NamespaceCommentCheck::registerMatchers(MatchFinder *Finder) { // Only register the matchers for C++; the functionality currently does not // provide any benefit to other languages, despite being benign. if (getLangOpts().CPlusPlus) Finder->addMatcher(namespaceDecl().bind("namespace"), this); }
void ProTypeConstCastCheck::registerMatchers(MatchFinder *Finder) { if (!getLangOpts().CPlusPlus) return; Finder->addMatcher(cxxConstCastExpr().bind("cast"), this); }
void UseOverrideCheck::registerMatchers(MatchFinder *Finder) { // Only register the matcher for C++11. if (getLangOpts().CPlusPlus11) Finder->addMatcher(cxxMethodDecl(isOverride()).bind("method"), this); }
void UseOverrideCheck::check(const MatchFinder::MatchResult &Result) { const auto *Method = Result.Nodes.getNodeAs<FunctionDecl>("method"); const SourceManager &Sources = *Result.SourceManager; assert(Method != nullptr); if (Method->getInstantiatedFromMemberFunction() != nullptr) Method = Method->getInstantiatedFromMemberFunction(); if (Method->isImplicit() || Method->getLocation().isMacroID() || Method->isOutOfLine()) return; bool HasVirtual = Method->isVirtualAsWritten(); bool HasOverride = Method->getAttr<OverrideAttr>(); bool HasFinal = Method->getAttr<FinalAttr>(); bool OnlyVirtualSpecified = HasVirtual && !HasOverride && !HasFinal; unsigned KeywordCount = HasVirtual + HasOverride + HasFinal; if (!OnlyVirtualSpecified && KeywordCount == 1) return; // Nothing to do. std::string Message; if (OnlyVirtualSpecified) { Message = "prefer using 'override' or (rarely) 'final' instead of 'virtual'"; } else if (KeywordCount == 0) { Message = "annotate this function with 'override' or (rarely) 'final'"; } else { StringRef Redundant = HasVirtual ? (HasOverride && HasFinal ? "'virtual' and 'override' are" : "'virtual' is") : "'override' is"; StringRef Correct = HasFinal ? "'final'" : "'override'"; Message = (llvm::Twine(Redundant) + " redundant since the function is already declared " + Correct) .str(); } DiagnosticBuilder Diag = diag(Method->getLocation(), Message); CharSourceRange FileRange = Lexer::makeFileCharRange( CharSourceRange::getTokenRange(Method->getSourceRange()), Sources, getLangOpts()); if (!FileRange.isValid()) return; // FIXME: Instead of re-lexing and looking for specific macros such as // 'ABSTRACT', properly store the location of 'virtual' and '= 0' in each // FunctionDecl. SmallVector<Token, 16> Tokens = ParseTokens(FileRange, Result); // Add 'override' on inline declarations that don't already have it. if (!HasFinal && !HasOverride) { SourceLocation InsertLoc; StringRef ReplacementText = "override "; SourceLocation MethodLoc = Method->getLocation(); for (Token T : Tokens) { if (T.is(tok::kw___attribute) && !Sources.isBeforeInTranslationUnit(T.getLocation(), MethodLoc)) { InsertLoc = T.getLocation(); break; } } if (Method->hasAttrs()) { for (const clang::Attr *A : Method->getAttrs()) { if (!A->isImplicit() && !A->isInherited()) { SourceLocation Loc = Sources.getExpansionLoc(A->getRange().getBegin()); if ((!InsertLoc.isValid() || Sources.isBeforeInTranslationUnit(Loc, InsertLoc)) && !Sources.isBeforeInTranslationUnit(Loc, MethodLoc)) InsertLoc = Loc; } } } if (InsertLoc.isInvalid() && Method->doesThisDeclarationHaveABody() && Method->getBody() && !Method->isDefaulted()) { // For methods with inline definition, add the override keyword at the // end of the declaration of the function, but prefer to put it on the // same line as the declaration if the beginning brace for the start of // the body falls on the next line. Token LastNonCommentToken; for (Token T : Tokens) { if (!T.is(tok::comment)) { LastNonCommentToken = T; } } InsertLoc = LastNonCommentToken.getEndLoc(); ReplacementText = " override"; } if (!InsertLoc.isValid()) { // For declarations marked with "= 0" or "= [default|delete]", the end // location will point until after those markings. Therefore, the override // keyword shouldn't be inserted at the end, but before the '='. if (Tokens.size() > 2 && (GetText(Tokens.back(), Sources) == "0" || Tokens.back().is(tok::kw_default) || Tokens.back().is(tok::kw_delete)) && GetText(Tokens[Tokens.size() - 2], Sources) == "=") { InsertLoc = Tokens[Tokens.size() - 2].getLocation(); // Check if we need to insert a space. if ((Tokens[Tokens.size() - 2].getFlags() & Token::LeadingSpace) == 0) ReplacementText = " override "; } else if (GetText(Tokens.back(), Sources) == "ABSTRACT") { InsertLoc = Tokens.back().getLocation(); } } if (!InsertLoc.isValid()) { InsertLoc = FileRange.getEnd(); ReplacementText = " override"; } Diag << FixItHint::CreateInsertion(InsertLoc, ReplacementText); } if (HasFinal && HasOverride) { SourceLocation OverrideLoc = Method->getAttr<OverrideAttr>()->getLocation(); Diag << FixItHint::CreateRemoval( CharSourceRange::getTokenRange(OverrideLoc, OverrideLoc)); } if (HasVirtual) { for (Token Tok : Tokens) { if (Tok.is(tok::kw_virtual)) { Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( Tok.getLocation(), Tok.getLocation())); break; } } } }
void UnaryStaticAssertCheck::registerMatchers(MatchFinder *Finder) { if (!getLangOpts().CPlusPlus17) return; Finder->addMatcher(staticAssertDecl().bind("static_assert"), this); }
void UpgradeDurationConversionsCheck::registerMatchers(MatchFinder *Finder) { if (!getLangOpts().CPlusPlus) return; // For the arithmetic calls, we match only the uses of the templated operators // where the template parameter is not a built-in type. This means the // instantiation makes use of an available user defined conversion to // `int64_t`. // // The implementation of these templates will be updated to fail SFINAE for // non-integral types. We match them to suggest an explicit cast. // Match expressions like `a *= b` and `a /= b` where `a` has type // `absl::Duration` and `b` is not of a built-in type. Finder->addMatcher( cxxOperatorCallExpr( argumentCountIs(2), hasArgument( 0, expr(hasType(cxxRecordDecl(hasName("::absl::Duration"))))), hasArgument(1, expr().bind("arg")), callee(functionDecl( hasParent(functionTemplateDecl()), unless(hasTemplateArgument(0, refersToType(builtinType()))), hasAnyName("operator*=", "operator/=")))), this); // Match expressions like `a.operator*=(b)` and `a.operator/=(b)` where `a` // has type `absl::Duration` and `b` is not of a built-in type. Finder->addMatcher( cxxMemberCallExpr( callee(cxxMethodDecl( ofClass(cxxRecordDecl(hasName("::absl::Duration"))), hasParent(functionTemplateDecl()), unless(hasTemplateArgument(0, refersToType(builtinType()))), hasAnyName("operator*=", "operator/="))), argumentCountIs(1), hasArgument(0, expr().bind("arg"))), this); // Match expressions like `a * b`, `a / b`, `operator*(a, b)`, and // `operator/(a, b)` where `a` has type `absl::Duration` and `b` is not of a // built-in type. Finder->addMatcher( callExpr(callee(functionDecl( hasParent(functionTemplateDecl()), unless(hasTemplateArgument(0, refersToType(builtinType()))), hasAnyName("::absl::operator*", "::absl::operator/"))), argumentCountIs(2), hasArgument(0, expr(hasType( cxxRecordDecl(hasName("::absl::Duration"))))), hasArgument(1, expr().bind("arg"))), this); // Match expressions like `a * b` and `operator*(a, b)` where `a` is not of a // built-in type and `b` has type `absl::Duration`. Finder->addMatcher( callExpr(callee(functionDecl( hasParent(functionTemplateDecl()), unless(hasTemplateArgument(0, refersToType(builtinType()))), hasName("::absl::operator*"))), argumentCountIs(2), hasArgument(0, expr().bind("arg")), hasArgument(1, expr(hasType(cxxRecordDecl( hasName("::absl::Duration")))))), this); // For the factory functions, we match only the non-templated overloads that // take an `int64_t` parameter. Within these calls, we care about implicit // casts through a user defined conversion to `int64_t`. // // The factory functions will be updated to be templated and SFINAE on whether // the template parameter is an integral type. This complements the already // existing templated overloads that only accept floating point types. // Match calls like: // `absl::Nanoseconds(x)` // `absl::Microseconds(x)` // `absl::Milliseconds(x)` // `absl::Seconds(x)` // `absl::Minutes(x)` // `absl::Hours(x)` // where `x` is not of a built-in type. Finder->addMatcher( implicitCastExpr( anyOf(hasCastKind(CK_UserDefinedConversion), has(implicitCastExpr(hasCastKind(CK_UserDefinedConversion)))), hasParent(callExpr( callee(functionDecl(DurationFactoryFunction(), unless(hasParent(functionTemplateDecl())))), hasArgument(0, expr().bind("arg"))))), this); }
void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) { const auto *MemberCall = Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr"); const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp"); const auto *E = Result.Nodes.getNodeAs<Expr>("STLObject"); FixItHint Hint; std::string ReplacementText = Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()), *Result.SourceManager, getLangOpts()); if (E->getType()->isPointerType()) ReplacementText += "->empty()"; else ReplacementText += ".empty()"; if (BinaryOp) { // Determine the correct transformation. bool Negation = false; const bool ContainerIsLHS = !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts()); const auto OpCode = BinaryOp->getOpcode(); uint64_t Value = 0; if (ContainerIsLHS) { if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>( BinaryOp->getRHS()->IgnoreImpCasts())) Value = Literal->getValue().getLimitedValue(); else return; } else { Value = llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts()) ->getValue() .getLimitedValue(); } // Constant that is not handled. if (Value > 1) return; if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ || OpCode == BinaryOperatorKind::BO_NE)) return; // Always true, no warnings for that. if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) || (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS)) return; // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size. if (Value == 1) { if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) || (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS)) return; if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) || (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS)) return; } if (OpCode == BinaryOperatorKind::BO_NE && Value == 0) Negation = true; if ((OpCode == BinaryOperatorKind::BO_GT || OpCode == BinaryOperatorKind::BO_GE) && ContainerIsLHS) Negation = true; if ((OpCode == BinaryOperatorKind::BO_LT || OpCode == BinaryOperatorKind::BO_LE) && !ContainerIsLHS) Negation = true; if (Negation) ReplacementText = "!" + ReplacementText; Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(), ReplacementText); } else { // If there is a conversion above the size call to bool, it is safe to just // replace size with empty. if (const auto *UnaryOp = Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize")) Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(), ReplacementText); else Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(), "!" + ReplacementText); } diag(MemberCall->getLocStart(), "the 'empty' method should be used to check " "for emptiness instead of 'size'") << Hint; const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container"); const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty"); diag(Empty->getLocation(), "method %0::empty() defined here", DiagnosticIDs::Note) << Container; }
void ProTypeCstyleCastCheck::check(const MatchFinder::MatchResult &Result) { const auto *MatchedCast = Result.Nodes.getNodeAs<CStyleCastExpr>("cast"); if (MatchedCast->getCastKind() == CK_BitCast || MatchedCast->getCastKind() == CK_LValueBitCast || MatchedCast->getCastKind() == CK_IntegralToPointer || MatchedCast->getCastKind() == CK_PointerToIntegral || MatchedCast->getCastKind() == CK_ReinterpretMemberPointer) { diag(MatchedCast->getLocStart(), "do not use C-style cast to convert between unrelated types"); return; } QualType SourceType = MatchedCast->getSubExpr()->getType(); if (MatchedCast->getCastKind() == CK_BaseToDerived) { const auto *SourceDecl = SourceType->getPointeeCXXRecordDecl(); if (!SourceDecl) // The cast is from object to reference. SourceDecl = SourceType->getAsCXXRecordDecl(); if (!SourceDecl) return; if (SourceDecl->isPolymorphic()) { // Leave type spelling exactly as it was (unlike // getTypeAsWritten().getAsString() which would spell enum types 'enum // X'). StringRef DestTypeString = Lexer::getSourceText( CharSourceRange::getTokenRange( MatchedCast->getLParenLoc().getLocWithOffset(1), MatchedCast->getRParenLoc().getLocWithOffset(-1)), *Result.SourceManager, getLangOpts()); auto diag_builder = diag( MatchedCast->getLocStart(), "do not use C-style cast to downcast from a base to a derived class; " "use dynamic_cast instead"); const Expr *SubExpr = MatchedCast->getSubExprAsWritten()->IgnoreImpCasts(); std::string CastText = ("dynamic_cast<" + DestTypeString + ">").str(); if (!isa<ParenExpr>(SubExpr)) { CastText.push_back('('); diag_builder << FixItHint::CreateInsertion( Lexer::getLocForEndOfToken(SubExpr->getLocEnd(), 0, *Result.SourceManager, getLangOpts()), ")"); } auto ParenRange = CharSourceRange::getTokenRange( MatchedCast->getLParenLoc(), MatchedCast->getRParenLoc()); diag_builder << FixItHint::CreateReplacement(ParenRange, CastText); } else { diag( MatchedCast->getLocStart(), "do not use C-style cast to downcast from a base to a derived class"); } return; } if (MatchedCast->getCastKind() == CK_NoOp && needsConstCast(SourceType, MatchedCast->getType())) { diag(MatchedCast->getLocStart(), "do not use C-style cast to cast away constness"); } }
void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) { const auto *CastExpr = Result.Nodes.getNodeAs<CStyleCastExpr>("cast"); // Ignore casts in macros. if (CastExpr->getExprLoc().isMacroID()) return; // Casting to void is an idiomatic way to mute "unused variable" and similar // warnings. if (CastExpr->getCastKind() == CK_ToVoid) return; auto isFunction = [](QualType T) { T = T.getCanonicalType().getNonReferenceType(); return T->isFunctionType() || T->isFunctionPointerType() || T->isMemberFunctionPointerType(); }; const QualType DestTypeAsWritten = CastExpr->getTypeAsWritten().getUnqualifiedType(); const QualType SourceTypeAsWritten = CastExpr->getSubExprAsWritten()->getType().getUnqualifiedType(); const QualType SourceType = SourceTypeAsWritten.getCanonicalType(); const QualType DestType = DestTypeAsWritten.getCanonicalType(); auto ReplaceRange = CharSourceRange::getCharRange( CastExpr->getLParenLoc(), CastExpr->getSubExprAsWritten()->getBeginLoc()); bool FnToFnCast = isFunction(SourceTypeAsWritten) && isFunction(DestTypeAsWritten); if (CastExpr->getCastKind() == CK_NoOp && !FnToFnCast) { // Function pointer/reference casts may be needed to resolve ambiguities in // case of overloaded functions, so detection of redundant casts is trickier // in this case. Don't emit "redundant cast" warnings for function // pointer/reference types. if (SourceTypeAsWritten == DestTypeAsWritten) { diag(CastExpr->getBeginLoc(), "redundant cast to the same type") << FixItHint::CreateRemoval(ReplaceRange); return; } } // The rest of this check is only relevant to C++. // We also disable it for Objective-C++. if (!getLangOpts().CPlusPlus || getLangOpts().ObjC1 || getLangOpts().ObjC2) return; // Ignore code inside extern "C" {} blocks. if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context) .empty()) return; // Ignore code in .c files and headers included from them, even if they are // compiled as C++. if (getCurrentMainFile().endswith(".c")) return; SourceManager &SM = *Result.SourceManager; // Ignore code in .c files #included in other files (which shouldn't be done, // but people still do this for test and other purposes). if (SM.getFilename(SM.getSpellingLoc(CastExpr->getBeginLoc())).endswith(".c")) return; // Leave type spelling exactly as it was (unlike // getTypeAsWritten().getAsString() which would spell enum types 'enum X'). StringRef DestTypeString = Lexer::getSourceText(CharSourceRange::getTokenRange( CastExpr->getLParenLoc().getLocWithOffset(1), CastExpr->getRParenLoc().getLocWithOffset(-1)), SM, getLangOpts()); auto Diag = diag(CastExpr->getBeginLoc(), "C-style casts are discouraged; use %0"); auto ReplaceWithCast = [&](std::string CastText) { const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts(); if (!isa<ParenExpr>(SubExpr)) { CastText.push_back('('); Diag << FixItHint::CreateInsertion( Lexer::getLocForEndOfToken(SubExpr->getEndLoc(), 0, SM, getLangOpts()), ")"); } Diag << FixItHint::CreateReplacement(ReplaceRange, CastText); }; auto ReplaceWithNamedCast = [&](StringRef CastType) { Diag << CastType; ReplaceWithCast((CastType + "<" + DestTypeString + ">").str()); }; // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics. switch (CastExpr->getCastKind()) { case CK_FunctionToPointerDecay: ReplaceWithNamedCast("static_cast"); return; case CK_ConstructorConversion: if (!CastExpr->getTypeAsWritten().hasQualifiers() && DestTypeAsWritten->isRecordType() && !DestTypeAsWritten->isElaboratedTypeSpecifier()) { Diag << "constructor call syntax"; // FIXME: Validate DestTypeString, maybe. ReplaceWithCast(DestTypeString.str()); } else { ReplaceWithNamedCast("static_cast"); } return; case CK_NoOp: if (FnToFnCast) { ReplaceWithNamedCast("static_cast"); return; } if (SourceType == DestType) { Diag << "static_cast (if needed, the cast may be redundant)"; ReplaceWithCast(("static_cast<" + DestTypeString + ">").str()); return; } if (needsConstCast(SourceType, DestType) && pointedUnqualifiedTypesAreEqual(SourceType, DestType)) { ReplaceWithNamedCast("const_cast"); return; } if (DestType->isReferenceType()) { QualType Dest = DestType.getNonReferenceType(); QualType Source = SourceType.getNonReferenceType(); if (Source == Dest.withConst() || SourceType.getNonReferenceType() == DestType.getNonReferenceType()) { ReplaceWithNamedCast("const_cast"); return; } break; } // FALLTHROUGH case clang::CK_IntegralCast: // Convert integral and no-op casts between builtin types and enums to // static_cast. A cast from enum to integer may be unnecessary, but it's // still retained. if ((SourceType->isBuiltinType() || SourceType->isEnumeralType()) && (DestType->isBuiltinType() || DestType->isEnumeralType())) { ReplaceWithNamedCast("static_cast"); return; } break; case CK_BitCast: // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement. if (!needsConstCast(SourceType, DestType)) { if (SourceType->isVoidPointerType()) ReplaceWithNamedCast("static_cast"); else ReplaceWithNamedCast("reinterpret_cast"); return; } break; default: break; } Diag << "static_cast/const_cast/reinterpret_cast"; }
void UseUsingCheck::registerMatchers(MatchFinder *Finder) { if (!getLangOpts().CPlusPlus11) return; Finder->addMatcher(typedefDecl().bind("typedef"), this); }
void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) { constexpr char WarningMessage[] = "%0 must be marked explicit to avoid unintentional implicit conversions"; if (const auto *Conversion = Result.Nodes.getNodeAs<CXXConversionDecl>("conversion")) { if (Conversion->isOutOfLine()) return; SourceLocation Loc = Conversion->getLocation(); // Ignore all macros until we learn to ignore specific ones (e.g. used in // gmock to define matchers). if (Loc.isMacroID()) return; diag(Loc, WarningMessage) << Conversion << FixItHint::CreateInsertion(Loc, "explicit "); return; } const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor"); if (Ctor->isOutOfLine() || Ctor->getNumParams() == 0 || Ctor->getMinRequiredArguments() > 1) return; bool takesInitializerList = isStdInitializerList( Ctor->getParamDecl(0)->getType().getNonReferenceType()); if (Ctor->isExplicit() && (Ctor->isCopyOrMoveConstructor() || takesInitializerList)) { auto isKWExplicit = [](const Token &Tok) { return Tok.is(tok::raw_identifier) && Tok.getRawIdentifier() == "explicit"; }; SourceRange ExplicitTokenRange = FindToken(*Result.SourceManager, getLangOpts(), Ctor->getOuterLocStart(), Ctor->getEndLoc(), isKWExplicit); StringRef ConstructorDescription; if (Ctor->isMoveConstructor()) ConstructorDescription = "move"; else if (Ctor->isCopyConstructor()) ConstructorDescription = "copy"; else ConstructorDescription = "initializer-list"; auto Diag = diag(Ctor->getLocation(), "%0 constructor should not be declared explicit") << ConstructorDescription; if (ExplicitTokenRange.isValid()) { Diag << FixItHint::CreateRemoval( CharSourceRange::getCharRange(ExplicitTokenRange)); } return; } if (Ctor->isExplicit() || Ctor->isCopyOrMoveConstructor() || takesInitializerList) return; bool SingleArgument = Ctor->getNumParams() == 1 && !Ctor->getParamDecl(0)->isParameterPack(); SourceLocation Loc = Ctor->getLocation(); diag(Loc, WarningMessage) << (SingleArgument ? "single-argument constructors" : "constructors that are callable with a single argument") << FixItHint::CreateInsertion(Loc, "explicit "); }