//===--- SuspiciousStringCompareCheck.cpp - clang-tidy---------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "SuspiciousStringCompareCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace misc { AST_MATCHER(BinaryOperator, isComparisonOperator) { return Node.isComparisonOp(); } constexpr char KnownStringCompareFunctions[] = "__builtin_memcmp;" "__builtin_strcasecmp;" "__builtin_strcmp;" "__builtin_strncasecmp;" "__builtin_strncmp;" "_mbscmp;" "_mbscmp_l;" "_mbsicmp;" "_mbsicmp_l;" "_mbsnbcmp;" "_mbsnbcmp_l;" "_mbsnbicmp;" "_mbsnbicmp_l;" "_mbsncmp;" "_mbsncmp_l;" "_mbsnicmp;" "_mbsnicmp_l;" "_memicmp;" "_memicmp_l;" "_stricmp;" "_stricmp_l;" "_strnicmp;" "_strnicmp_l;" "_wcsicmp;" "_wcsicmp_l;" "_wcsnicmp;" "_wcsnicmp_l;" "lstrcmp;" "lstrcmpi;" "memcmp;" "memicmp;" "strcasecmp;" "strcmp;" "strcmpi;" "stricmp;" "strncasecmp;" "strncmp;" "strnicmp;" "wcscasecmp;" "wcscmp;" "wcsicmp;" "wcsncmp;" "wcsnicmp;" "wmemcmp;"; static const char StringCompareLikeFunctionsDelimiter[] = ";"; static void ParseFunctionNames(StringRef Option, std::vector *Result) { SmallVector Functions; Option.split(Functions, StringCompareLikeFunctionsDelimiter); for (StringRef &Function : Functions) { Function = Function.trim(); if (!Function.empty()) Result->push_back(Function); } } SuspiciousStringCompareCheck::SuspiciousStringCompareCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), WarnOnImplicitComparison(Options.get("WarnOnImplicitComparison", 1)), WarnOnLogicalNotComparison(Options.get("WarnOnLogicalNotComparison", 1)), StringCompareLikeFunctions( Options.get("StringCompareLikeFunctions", "")) {} void SuspiciousStringCompareCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "WarnOnImplicitComparison", WarnOnImplicitComparison); Options.store(Opts, "WarnOnLogicalNotComparison", WarnOnLogicalNotComparison); Options.store(Opts, "StringCompareLikeFunctions", StringCompareLikeFunctions); } void SuspiciousStringCompareCheck::registerMatchers(MatchFinder *Finder) { // Match relational operators. const auto ComparisonUnaryOperator = unaryOperator(hasOperatorName("!")); const auto ComparisonBinaryOperator = binaryOperator(isComparisonOperator()); const auto ComparisonOperator = expr(anyOf(ComparisonUnaryOperator, ComparisonBinaryOperator)); // Add the list of known string compare-like functions and add user-defined // functions. std::vector FunctionNames; ParseFunctionNames(KnownStringCompareFunctions, &FunctionNames); ParseFunctionNames(StringCompareLikeFunctions, &FunctionNames); const auto FunctionCompareDecl = functionDecl(hasAnyName(std::vector(FunctionNames.begin(), FunctionNames.end()))) .bind("decl"); // Match a call to a string compare functions. const auto StringCompareCallExpr = callExpr(hasDeclaration(FunctionCompareDecl)).bind("call"); if (WarnOnImplicitComparison) { // Detect suspicious calls to string compare (missing comparator) [only C]: // 'if (strcmp())' -> 'if (strcmp() != 0)' Finder->addMatcher( stmt(anyOf(ifStmt(hasCondition(StringCompareCallExpr)), whileStmt(hasCondition(StringCompareCallExpr)), doStmt(hasCondition(StringCompareCallExpr)), forStmt(hasCondition(StringCompareCallExpr)))) .bind("missing-comparison"), this); Finder->addMatcher(expr(StringCompareCallExpr, unless(hasParent(ComparisonOperator)), unless(hasParent(implicitCastExpr()))) .bind("missing-comparison"), this); // Detect suspicious calls to string compare with implicit comparison: // 'if (strcmp())' -> 'if (strcmp() != 0)' // 'if (!strcmp())' is considered valid (see WarnOnLogicalNotComparison) Finder->addMatcher( implicitCastExpr(hasType(isInteger()), hasSourceExpression(StringCompareCallExpr), unless(hasParent(ComparisonOperator))) .bind("missing-comparison"), this); // Detect suspicious cast to an inconsistant type. Finder->addMatcher( implicitCastExpr(unless(hasType(isInteger())), hasSourceExpression(StringCompareCallExpr)) .bind("invalid-conversion"), this); } if (WarnOnLogicalNotComparison) { // Detect suspicious calls to string compared with '!' operator: // 'if (!strcmp())' -> 'if (strcmp() == 0)' Finder->addMatcher(unaryOperator(hasOperatorName("!"), hasUnaryOperand(ignoringParenImpCasts( StringCompareCallExpr))) .bind("logical-not-comparison"), this); } // Detect suspicious calls to string compare functions: 'strcmp() == -1'. const auto InvalidLiteral = ignoringParenImpCasts( anyOf(integerLiteral(unless(equals(0))), unaryOperator(hasOperatorName("-"), has(integerLiteral(unless(equals(0))))), characterLiteral(), cxxBoolLiteral())); Finder->addMatcher(binaryOperator(isComparisonOperator(), hasEitherOperand(StringCompareCallExpr), hasEitherOperand(InvalidLiteral)) .bind("invalid-comparison"), this); } void SuspiciousStringCompareCheck::check( const MatchFinder::MatchResult &Result) { const auto *Decl = Result.Nodes.getNodeAs("decl"); const auto *Call = Result.Nodes.getNodeAs("call"); assert(Decl != nullptr && Call != nullptr); if (Result.Nodes.getNodeAs("missing-comparison")) { SourceLocation EndLoc = Lexer::getLocForEndOfToken( Call->getRParenLoc(), 0, Result.Context->getSourceManager(), Result.Context->getLangOpts()); diag(Call->getLocStart(), "function %0 is called without explicitly comparing result") << Decl << FixItHint::CreateInsertion(EndLoc, " != 0"); } if (const auto *E = Result.Nodes.getNodeAs("logical-not-comparison")) { SourceLocation EndLoc = Lexer::getLocForEndOfToken( Call->getRParenLoc(), 0, Result.Context->getSourceManager(), Result.Context->getLangOpts()); SourceLocation NotLoc = E->getLocStart(); diag(Call->getLocStart(), "function %0 is compared using logical not operator") << Decl << FixItHint::CreateRemoval( CharSourceRange::getTokenRange(NotLoc, NotLoc)) << FixItHint::CreateInsertion(EndLoc, " == 0"); } if (Result.Nodes.getNodeAs("invalid-comparison")) { diag(Call->getLocStart(), "function %0 is compared to a suspicious constant") << Decl; } if (Result.Nodes.getNodeAs("invalid-conversion")) { diag(Call->getLocStart(), "function %0 has suspicious implicit cast") << Decl; } } } // namespace misc } // namespace tidy } // namespace clang