//===- unittests/StaticAnalyzer/NoStateChangeFuncVisitorTest.cpp ----------===// // // 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 "CheckerRegistration.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" #include "gtest/gtest.h" #include //===----------------------------------------------------------------------===// // Base classes for testing NoStateChangeFuncVisitor. // // Testing is done by observing a very simple trait change from one node to // another -- the checker sets the ErrorPrevented trait to true if // 'preventError()' is called in the source code, and sets it to false if // 'allowError()' is called. If this trait is false when 'error()' is called, // a warning is emitted. // // The checker then registers a simple NoStateChangeFuncVisitor to add notes to // inlined functions that could have, but neglected to prevent the error. //===----------------------------------------------------------------------===// REGISTER_TRAIT_WITH_PROGRAMSTATE(ErrorPrevented, bool) namespace clang { namespace ento { namespace { class ErrorNotPreventedFuncVisitor : public NoStateChangeFuncVisitor { public: ErrorNotPreventedFuncVisitor() : NoStateChangeFuncVisitor(bugreporter::TrackingKind::Thorough) {} virtual PathDiagnosticPieceRef maybeEmitNoteForObjCSelf(PathSensitiveBugReport &R, const ObjCMethodCall &Call, const ExplodedNode *N) override { return nullptr; } virtual PathDiagnosticPieceRef maybeEmitNoteForCXXThis(PathSensitiveBugReport &R, const CXXConstructorCall &Call, const ExplodedNode *N) override { return nullptr; } virtual PathDiagnosticPieceRef maybeEmitNoteForParameters(PathSensitiveBugReport &R, const CallEvent &Call, const ExplodedNode *N) override { PathDiagnosticLocation L = PathDiagnosticLocation::create( N->getLocation(), N->getState()->getStateManager().getContext().getSourceManager()); return std::make_shared( L, "Returning without prevening the error"); } void Profile(llvm::FoldingSetNodeID &ID) const override { static int Tag = 0; ID.AddPointer(&Tag); } }; template class StatefulChecker : public Checker { const BugType BT{this, "error()", categories::SecurityError}; public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const { if (CallDescription{{"preventError"}, 0}.matches(Call)) { C.addTransition(C.getState()->set(true)); return; } if (CallDescription{{"allowError"}, 0}.matches(Call)) { C.addTransition(C.getState()->set(false)); return; } if (CallDescription{{"error"}, 0}.matches(Call)) { if (C.getState()->get()) return; const ExplodedNode *N = C.generateErrorNode(); if (!N) return; auto R = std::make_unique(BT, "error() called", N); R->template addVisitor(); C.emitReport(std::move(R)); } } }; } // namespace } // namespace ento } // namespace clang //===----------------------------------------------------------------------===// // Non-thorough analysis: only the state right before and right after the // function call is checked for the difference in trait value. //===----------------------------------------------------------------------===// namespace clang { namespace ento { namespace { class NonThoroughErrorNotPreventedFuncVisitor : public ErrorNotPreventedFuncVisitor { public: virtual bool wasModifiedInFunction(const ExplodedNode *CallEnterN, const ExplodedNode *CallExitEndN) override { return CallEnterN->getState()->get() != CallExitEndN->getState()->get(); } }; void addNonThoroughStatefulChecker(AnalysisASTConsumer &AnalysisConsumer, AnalyzerOptions &AnOpts) { AnOpts.CheckersAndPackages = {{"test.StatefulChecker", true}}; AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { Registry .addChecker>( "test.StatefulChecker", "Description", ""); }); } TEST(NoStateChangeFuncVisitor, NonThoroughFunctionAnalysis) { std::string Diags; EXPECT_TRUE(runCheckerOnCode(R"( void error(); void preventError(); void allowError(); void g() { //preventError(); } void f() { g(); error(); } )", Diags)); EXPECT_EQ(Diags, "test.StatefulChecker: Calling 'g' | Returning without prevening " "the error | Returning from 'g' | error() called\n"); Diags.clear(); EXPECT_TRUE(runCheckerOnCode(R"( void error(); void preventError(); void allowError(); void g() { preventError(); allowError(); } void f() { g(); error(); } )", Diags)); EXPECT_EQ(Diags, "test.StatefulChecker: Calling 'g' | Returning without prevening " "the error | Returning from 'g' | error() called\n"); Diags.clear(); EXPECT_TRUE(runCheckerOnCode(R"( void error(); void preventError(); void allowError(); void g() { preventError(); } void f() { g(); error(); } )", Diags)); EXPECT_EQ(Diags, ""); } } // namespace } // namespace ento } // namespace clang //===----------------------------------------------------------------------===// // Thorough analysis: only the state right before and right after the // function call is checked for the difference in trait value. //===----------------------------------------------------------------------===// namespace clang { namespace ento { namespace { class ThoroughErrorNotPreventedFuncVisitor : public ErrorNotPreventedFuncVisitor { public: virtual bool wasModifiedBeforeCallExit(const ExplodedNode *CurrN, const ExplodedNode *CallExitBeginN) override { return CurrN->getState()->get() != CallExitBeginN->getState()->get(); } }; void addThoroughStatefulChecker(AnalysisASTConsumer &AnalysisConsumer, AnalyzerOptions &AnOpts) { AnOpts.CheckersAndPackages = {{"test.StatefulChecker", true}}; AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { Registry.addChecker>( "test.StatefulChecker", "Description", ""); }); } TEST(NoStateChangeFuncVisitor, ThoroughFunctionAnalysis) { std::string Diags; EXPECT_TRUE(runCheckerOnCode(R"( void error(); void preventError(); void allowError(); void g() { //preventError(); } void f() { g(); error(); } )", Diags)); EXPECT_EQ(Diags, "test.StatefulChecker: Calling 'g' | Returning without prevening " "the error | Returning from 'g' | error() called\n"); Diags.clear(); EXPECT_TRUE(runCheckerOnCode(R"( void error(); void preventError(); void allowError(); void g() { preventError(); allowError(); } void f() { g(); error(); } )", Diags)); EXPECT_EQ(Diags, "test.StatefulChecker: error() called\n"); Diags.clear(); EXPECT_TRUE(runCheckerOnCode(R"( void error(); void preventError(); void allowError(); void g() { preventError(); } void f() { g(); error(); } )", Diags)); EXPECT_EQ(Diags, ""); } } // namespace } // namespace ento } // namespace clang