173 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "StaticAssertCheck.h"10#include "../utils/Matchers.h"11#include "clang/AST/ASTContext.h"12#include "clang/AST/Expr.h"13#include "clang/ASTMatchers/ASTMatchFinder.h"14#include "clang/Frontend/CompilerInstance.h"15#include "clang/Lex/Lexer.h"16#include "llvm/ADT/StringRef.h"17#include <optional>18#include <string>19 20using namespace clang::ast_matchers;21 22namespace clang::tidy::misc {23 24StaticAssertCheck::StaticAssertCheck(StringRef Name, ClangTidyContext *Context)25 : ClangTidyCheck(Name, Context) {}26 27void StaticAssertCheck::registerMatchers(MatchFinder *Finder) {28 auto NegatedString = unaryOperator(29 hasOperatorName("!"), hasUnaryOperand(ignoringImpCasts(stringLiteral())));30 auto IsAlwaysFalse =31 expr(anyOf(cxxBoolLiteral(equals(false)), integerLiteral(equals(0)),32 cxxNullPtrLiteralExpr(), gnuNullExpr(), NegatedString))33 .bind("isAlwaysFalse");34 auto IsAlwaysFalseWithCast = ignoringParenImpCasts(anyOf(35 IsAlwaysFalse, cStyleCastExpr(has(ignoringParenImpCasts(IsAlwaysFalse)))36 .bind("castExpr")));37 auto AssertExprRoot = anyOf(38 binaryOperator(39 hasAnyOperatorName("&&", "=="),40 hasEitherOperand(ignoringImpCasts(stringLiteral().bind("assertMSG"))),41 optionally(binaryOperator(hasEitherOperand(IsAlwaysFalseWithCast))))42 .bind("assertExprRoot"),43 IsAlwaysFalse);44 auto NonConstexprFunctionCall =45 callExpr(hasDeclaration(functionDecl(unless(isConstexpr()))));46 auto NonConstexprVariableReference =47 declRefExpr(to(varDecl(unless(isConstexpr()))),48 unless(hasAncestor(expr(matchers::hasUnevaluatedContext()))),49 unless(hasAncestor(typeLoc())));50 51 auto NonConstexprCode =52 expr(anyOf(NonConstexprFunctionCall, NonConstexprVariableReference));53 auto AssertCondition =54 expr(optionally(expr(ignoringParenCasts(anyOf(55 AssertExprRoot, unaryOperator(hasUnaryOperand(56 ignoringParenCasts(AssertExprRoot))))))),57 unless(NonConstexprCode), unless(hasDescendant(NonConstexprCode)))58 .bind("condition");59 auto Condition =60 anyOf(ignoringParenImpCasts(callExpr(61 hasDeclaration(functionDecl(hasName("__builtin_expect"))),62 hasArgument(0, AssertCondition))),63 AssertCondition);64 65 Finder->addMatcher(conditionalOperator(hasCondition(Condition),66 unless(isInTemplateInstantiation()))67 .bind("condStmt"),68 this);69 70 Finder->addMatcher(71 ifStmt(hasCondition(Condition), unless(isInTemplateInstantiation()))72 .bind("condStmt"),73 this);74}75 76void StaticAssertCheck::check(const MatchFinder::MatchResult &Result) {77 const ASTContext *ASTCtx = Result.Context;78 const LangOptions &Opts = ASTCtx->getLangOpts();79 const SourceManager &SM = ASTCtx->getSourceManager();80 const auto *CondStmt = Result.Nodes.getNodeAs<Stmt>("condStmt");81 const auto *Condition = Result.Nodes.getNodeAs<Expr>("condition");82 const auto *IsAlwaysFalse = Result.Nodes.getNodeAs<Expr>("isAlwaysFalse");83 const auto *AssertMSG = Result.Nodes.getNodeAs<StringLiteral>("assertMSG");84 const auto *AssertExprRoot =85 Result.Nodes.getNodeAs<BinaryOperator>("assertExprRoot");86 const auto *CastExpr = Result.Nodes.getNodeAs<CStyleCastExpr>("castExpr");87 const SourceLocation AssertExpansionLoc = CondStmt->getBeginLoc();88 89 if (!AssertExpansionLoc.isValid() || !AssertExpansionLoc.isMacroID())90 return;91 92 const StringRef MacroName =93 Lexer::getImmediateMacroName(AssertExpansionLoc, SM, Opts);94 95 if (MacroName != "assert" || Condition->isValueDependent() ||96 Condition->isTypeDependent() || Condition->isInstantiationDependent() ||97 !Condition->isEvaluatable(*ASTCtx))98 return;99 100 // False literal is not the result of macro expansion.101 if (IsAlwaysFalse && (!CastExpr || CastExpr->getType()->isPointerType())) {102 const SourceLocation FalseLiteralLoc =103 SM.getImmediateSpellingLoc(IsAlwaysFalse->getExprLoc());104 if (!FalseLiteralLoc.isMacroID())105 return;106 107 const StringRef FalseMacroName =108 Lexer::getImmediateMacroName(FalseLiteralLoc, SM, Opts);109 if (FalseMacroName.compare_insensitive("false") == 0 ||110 FalseMacroName.compare_insensitive("null") == 0)111 return;112 }113 114 const SourceLocation AssertLoc =115 SM.getImmediateMacroCallerLoc(AssertExpansionLoc);116 117 SmallVector<FixItHint, 4> FixItHints;118 SourceLocation LastParenLoc;119 if (AssertLoc.isValid() && !AssertLoc.isMacroID() &&120 (LastParenLoc = getLastParenLoc(ASTCtx, AssertLoc)).isValid()) {121 FixItHints.push_back(122 FixItHint::CreateReplacement(SourceRange(AssertLoc), "static_assert"));123 124 if (AssertExprRoot) {125 FixItHints.push_back(FixItHint::CreateRemoval(126 SourceRange(AssertExprRoot->getOperatorLoc())));127 FixItHints.push_back(FixItHint::CreateRemoval(128 SourceRange(AssertMSG->getBeginLoc(), AssertMSG->getEndLoc())));129 FixItHints.push_back(FixItHint::CreateInsertion(130 LastParenLoc, (Twine(", \"") + AssertMSG->getString() + "\"").str()));131 } else if (!Opts.CPlusPlus17) {132 FixItHints.push_back(FixItHint::CreateInsertion(LastParenLoc, ", \"\""));133 }134 }135 136 diag(AssertLoc, "found assert() that could be replaced by static_assert()")137 << FixItHints;138}139 140SourceLocation StaticAssertCheck::getLastParenLoc(const ASTContext *ASTCtx,141 SourceLocation AssertLoc) {142 const LangOptions &Opts = ASTCtx->getLangOpts();143 const SourceManager &SM = ASTCtx->getSourceManager();144 145 std::optional<llvm::MemoryBufferRef> Buffer =146 SM.getBufferOrNone(SM.getFileID(AssertLoc));147 if (!Buffer)148 return {};149 150 const char *BufferPos = SM.getCharacterData(AssertLoc);151 152 Token Token;153 Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(AssertLoc)), Opts,154 Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd());155 156 // assert first left parenthesis157 if (Lexer.LexFromRawLexer(Token) || Lexer.LexFromRawLexer(Token) ||158 !Token.is(tok::l_paren))159 return {};160 161 unsigned int ParenCount = 1;162 while (ParenCount && !Lexer.LexFromRawLexer(Token)) {163 if (Token.is(tok::l_paren))164 ++ParenCount;165 else if (Token.is(tok::r_paren))166 --ParenCount;167 }168 169 return Token.getLocation();170}171 172} // namespace clang::tidy::misc173