| //===--- SuspiciousStringCompareCheck.cpp - clang-tidy---------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "SuspiciousStringCompareCheck.h" |
| #include "../utils/Matchers.h" |
| #include "../utils/OptionsUtils.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 bugprone { |
| |
| // Semicolon separated list of known string compare-like functions. The list |
| // must ends with a semicolon. |
| static const 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;"; |
| |
| SuspiciousStringCompareCheck::SuspiciousStringCompareCheck( |
| StringRef Name, ClangTidyContext *Context) |
| : ClangTidyCheck(Name, Context), |
| WarnOnImplicitComparison(Options.get("WarnOnImplicitComparison", true)), |
| WarnOnLogicalNotComparison( |
| Options.get("WarnOnLogicalNotComparison", false)), |
| 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<std::string> FunctionNames = utils::options::parseStringList( |
| (llvm::Twine(KnownStringCompareFunctions) + StringCompareLikeFunctions) |
| .str()); |
| |
| // Match a call to a string compare functions. |
| const auto FunctionCompareDecl = |
| functionDecl(hasAnyName(std::vector<StringRef>(FunctionNames.begin(), |
| FunctionNames.end()))) |
| .bind("decl"); |
| const auto DirectStringCompareCallExpr = |
| callExpr(hasDeclaration(FunctionCompareDecl)).bind("call"); |
| const auto MacroStringCompareCallExpr = conditionalOperator(anyOf( |
| hasTrueExpression(ignoringParenImpCasts(DirectStringCompareCallExpr)), |
| hasFalseExpression(ignoringParenImpCasts(DirectStringCompareCallExpr)))); |
| // The implicit cast is not present in C. |
| const auto StringCompareCallExpr = ignoringParenImpCasts( |
| anyOf(DirectStringCompareCallExpr, MacroStringCompareCallExpr)); |
| |
| if (WarnOnImplicitComparison) { |
| // Detect suspicious calls to string compare: |
| // 'if (strcmp())' -> 'if (strcmp() != 0)' |
| Finder->addMatcher( |
| stmt(anyOf(mapAnyOf(ifStmt, whileStmt, doStmt, forStmt) |
| .with(hasCondition(StringCompareCallExpr)), |
| binaryOperator(hasAnyOperatorName("&&", "||"), |
| hasEitherOperand(StringCompareCallExpr)))) |
| .bind("missing-comparison"), |
| 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 cast to an inconsistent type (i.e. not integer type). |
| Finder->addMatcher( |
| traverse(TK_AsIs, |
| implicitCastExpr(unless(hasType(isInteger())), |
| hasSourceExpression(StringCompareCallExpr)) |
| .bind("invalid-conversion")), |
| this); |
| |
| // Detect suspicious operator with string compare function as operand. |
| Finder->addMatcher( |
| binaryOperator(unless(anyOf(isComparisonOperator(), hasOperatorName("&&"), |
| hasOperatorName("||"), hasOperatorName("="))), |
| hasEitherOperand(StringCompareCallExpr)) |
| .bind("suspicious-operator"), |
| this); |
| |
| // Detect comparison to invalid constant: 'strcmp() == -1'. |
| const auto InvalidLiteral = ignoringParenImpCasts( |
| anyOf(integerLiteral(unless(equals(0))), |
| unaryOperator( |
| hasOperatorName("-"), |
| has(ignoringParenImpCasts(integerLiteral(unless(equals(0)))))), |
| characterLiteral(), cxxBoolLiteral())); |
| |
| Finder->addMatcher( |
| binaryOperator(isComparisonOperator(), |
| hasOperands(StringCompareCallExpr, InvalidLiteral)) |
| .bind("invalid-comparison"), |
| this); |
| } |
| |
| void SuspiciousStringCompareCheck::check( |
| const MatchFinder::MatchResult &Result) { |
| const auto *Decl = Result.Nodes.getNodeAs<FunctionDecl>("decl"); |
| const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call"); |
| assert(Decl != nullptr && Call != nullptr); |
| |
| if (Result.Nodes.getNodeAs<Stmt>("missing-comparison")) { |
| SourceLocation EndLoc = Lexer::getLocForEndOfToken( |
| Call->getRParenLoc(), 0, Result.Context->getSourceManager(), |
| getLangOpts()); |
| |
| diag(Call->getBeginLoc(), |
| "function %0 is called without explicitly comparing result") |
| << Decl << FixItHint::CreateInsertion(EndLoc, " != 0"); |
| } |
| |
| if (const auto *E = Result.Nodes.getNodeAs<Expr>("logical-not-comparison")) { |
| SourceLocation EndLoc = Lexer::getLocForEndOfToken( |
| Call->getRParenLoc(), 0, Result.Context->getSourceManager(), |
| getLangOpts()); |
| SourceLocation NotLoc = E->getBeginLoc(); |
| |
| diag(Call->getBeginLoc(), |
| "function %0 is compared using logical not operator") |
| << Decl |
| << FixItHint::CreateRemoval( |
| CharSourceRange::getTokenRange(NotLoc, NotLoc)) |
| << FixItHint::CreateInsertion(EndLoc, " == 0"); |
| } |
| |
| if (Result.Nodes.getNodeAs<Stmt>("invalid-comparison")) { |
| diag(Call->getBeginLoc(), |
| "function %0 is compared to a suspicious constant") |
| << Decl; |
| } |
| |
| if (const auto *BinOp = |
| Result.Nodes.getNodeAs<BinaryOperator>("suspicious-operator")) { |
| diag(Call->getBeginLoc(), "results of function %0 used by operator '%1'") |
| << Decl << BinOp->getOpcodeStr(); |
| } |
| |
| if (Result.Nodes.getNodeAs<Stmt>("invalid-conversion")) { |
| diag(Call->getBeginLoc(), "function %0 has suspicious implicit cast") |
| << Decl; |
| } |
| } |
| |
| } // namespace bugprone |
| } // namespace tidy |
| } // namespace clang |