brintos

brintos / llvm-project-archived public Read only

0
0
Text · 21.3 KiB · e2007a9 Raw
656 lines · cpp
1//===- unittests/StaticAnalyzer/CallDescriptionTest.cpp -------------------===//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 "CheckerRegistration.h"10#include "Reusables.h"11 12#include "clang/AST/ExprCXX.h"13#include "clang/Analysis/PathDiagnostic.h"14#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"15#include "clang/StaticAnalyzer/Core/Checker.h"16#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"17#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"18#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"19#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"20#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"21#include "clang/Tooling/Tooling.h"22#include "gtest/gtest.h"23#include <type_traits>24 25namespace clang {26namespace ento {27namespace {28 29// A wrapper around CallDescriptionMap<bool> that allows verifying that30// all functions have been found. This is needed because CallDescriptionMap31// isn't supposed to support iteration.32class ResultMap {33  size_t Found, Total;34  CallDescriptionMap<bool> Impl;35 36public:37  ResultMap(std::initializer_list<std::pair<CallDescription, bool>> Data)38      : Found(0), Total(llvm::count(llvm::make_second_range(Data), true)),39        Impl(std::move(Data)) {}40 41  const bool *lookup(const CallEvent &Call) {42    const bool *Result = Impl.lookup(Call);43    // If it's a function we expected to find, remember that we've found it.44    if (Result && *Result)45      ++Found;46    return Result;47  }48 49  // Fail the test if we haven't found all the true-calls we were looking for.50  ~ResultMap() { EXPECT_EQ(Found, Total); }51};52 53// Scan the code body for call expressions and see if we find all calls that54// we were supposed to find ("true" in the provided ResultMap) and that we55// don't find the ones that we weren't supposed to find56// ("false" in the ResultMap).57template <typename MatchedExprT>58class CallDescriptionConsumer : public ExprEngineConsumer {59  ResultMap &RM;60  void performTest(const Decl *D) {61    using namespace ast_matchers;62    using T = MatchedExprT;63 64    if (!D->hasBody())65      return;66 67    const StackFrameContext *SFC =68        Eng.getAnalysisDeclContextManager().getStackFrame(D);69    const ProgramStateRef State = Eng.getInitialState(SFC);70 71    // FIXME: Maybe use std::variant and std::visit for these.72    const auto MatcherCreator = []() {73      if (std::is_same<T, CallExpr>::value)74        return callExpr();75      if (std::is_same<T, CXXConstructExpr>::value)76        return cxxConstructExpr();77      if (std::is_same<T, CXXMemberCallExpr>::value)78        return cxxMemberCallExpr();79      if (std::is_same<T, CXXOperatorCallExpr>::value)80        return cxxOperatorCallExpr();81      llvm_unreachable("Only these expressions are supported for now.");82    };83 84    const Expr *E = findNode<T>(D, MatcherCreator());85 86    CallEventManager &CEMgr = Eng.getStateManager().getCallEventManager();87    CallEventRef<> Call = [=, &CEMgr]() -> CallEventRef<CallEvent> {88      CFGBlock::ConstCFGElementRef ElemRef = {SFC->getCallSiteBlock(),89                                              SFC->getIndex()};90      if (std::is_base_of<CallExpr, T>::value)91        return CEMgr.getCall(E, State, SFC, ElemRef);92      if (std::is_same<T, CXXConstructExpr>::value)93        return CEMgr.getCXXConstructorCall(cast<CXXConstructExpr>(E),94                                           /*Target=*/nullptr, State, SFC,95                                           ElemRef);96      llvm_unreachable("Only these expressions are supported for now.");97    }();98 99    // If the call actually matched, check if we really expected it to match.100    const bool *LookupResult = RM.lookup(*Call);101    EXPECT_TRUE(!LookupResult || *LookupResult);102 103    // ResultMap is responsible for making sure that we've found *all* calls.104  }105 106public:107  CallDescriptionConsumer(CompilerInstance &C,108                          ResultMap &RM)109      : ExprEngineConsumer(C), RM(RM) {}110 111  bool HandleTopLevelDecl(DeclGroupRef DG) override {112    for (const auto *D : DG)113      performTest(D);114    return true;115  }116};117 118template <typename MatchedExprT = CallExpr>119class CallDescriptionAction : public ASTFrontendAction {120  ResultMap RM;121 122public:123  CallDescriptionAction(124      std::initializer_list<std::pair<CallDescription, bool>> Data)125      : RM(Data) {}126 127  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,128                                                 StringRef File) override {129    return std::make_unique<CallDescriptionConsumer<MatchedExprT>>(Compiler,130                                                                   RM);131  }132};133 134TEST(CallDescription, SimpleNameMatching) {135  EXPECT_TRUE(tooling::runToolOnCode(136      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({137          {{CDM::SimpleFunc, {"bar"}},138           false}, // false: there's no call to 'bar' in this code.139          {{CDM::SimpleFunc, {"foo"}},140           true}, // true: there's a call to 'foo' in this code.141      })),142      "void foo(); void bar() { foo(); }"));143}144 145TEST(CallDescription, RequiredArguments) {146  EXPECT_TRUE(tooling::runToolOnCode(147      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({148          {{CDM::SimpleFunc, {"foo"}, 1}, true},149          {{CDM::SimpleFunc, {"foo"}, 2}, false},150      })),151      "void foo(int); void foo(int, int); void bar() { foo(1); }"));152}153 154TEST(CallDescription, LackOfRequiredArguments) {155  EXPECT_TRUE(tooling::runToolOnCode(156      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({157          {{CDM::SimpleFunc, {"foo"}, std::nullopt}, true},158          {{CDM::SimpleFunc, {"foo"}, 2}, false},159      })),160      "void foo(int); void foo(int, int); void bar() { foo(1); }"));161}162 163constexpr StringRef MockStdStringHeader = R"code(164  namespace std { inline namespace __1 {165    template<typename T> class basic_string {166      class Allocator {};167    public:168      basic_string();169      explicit basic_string(const char*, const Allocator & = Allocator());170      ~basic_string();171      T *c_str();172    };173  } // namespace __1174  using string = __1::basic_string<char>;175  } // namespace std176)code";177 178TEST(CallDescription, QualifiedNames) {179  constexpr StringRef AdditionalCode = R"code(180    void foo() {181      using namespace std;182      basic_string<char> s;183      s.c_str();184    })code";185  const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str();186  EXPECT_TRUE(tooling::runToolOnCode(187      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({188          {{CDM::CXXMethod, {"std", "basic_string", "c_str"}}, true},189      })),190      Code));191}192 193TEST(CallDescription, MatchConstructor) {194  constexpr StringRef AdditionalCode = R"code(195    void foo() {196      using namespace std;197      basic_string<char> s("hello");198    })code";199  const std::string Code = (Twine{MockStdStringHeader} + AdditionalCode).str();200  EXPECT_TRUE(tooling::runToolOnCode(201      std::unique_ptr<FrontendAction>(202          new CallDescriptionAction<CXXConstructExpr>({203              {{CDM::CXXMethod, {"std", "basic_string", "basic_string"}, 2, 2},204               true},205          })),206      Code));207}208 209// FIXME: Test matching destructors: {"std", "basic_string", "~basic_string"}210//        This feature is actually implemented, but the test infra is not yet211//        sophisticated enough for testing this. To do that, we will need to212//        implement a much more advanced dispatching mechanism using the CFG for213//        the implicit destructor events.214 215TEST(CallDescription, MatchConversionOperator) {216  constexpr StringRef Code = R"code(217    namespace aaa {218    namespace bbb {219    struct Bar {220      operator int();221    };222    } // bbb223    } // aaa224    void foo() {225      aaa::bbb::Bar x;226      int tmp = x;227    })code";228  EXPECT_TRUE(tooling::runToolOnCode(229      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({230          {{CDM::CXXMethod, {"aaa", "bbb", "Bar", "operator int"}}, true},231      })),232      Code));233}234 235TEST(CallDescription, RejectOverQualifiedNames) {236  constexpr auto Code = R"code(237    namespace my {238    namespace std {239      struct container {240        const char *data() const;241      };242    } // namespace std243    } // namespace my244 245    void foo() {246      using namespace my;247      std::container v;248      v.data();249    })code";250 251  // FIXME: We should **not** match.252  EXPECT_TRUE(tooling::runToolOnCode(253      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({254          {{CDM::CXXMethod, {"std", "container", "data"}}, true},255      })),256      Code));257}258 259TEST(CallDescription, DontSkipNonInlineNamespaces) {260  constexpr auto Code = R"code(261    namespace my {262    /*not inline*/ namespace v1 {263      void bar();264    } // namespace v1265    } // namespace my266    void foo() {267      my::v1::bar();268    })code";269 270  {271    SCOPED_TRACE("my v1 bar");272    EXPECT_TRUE(tooling::runToolOnCode(273        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({274            {{CDM::SimpleFunc, {"my", "v1", "bar"}}, true},275        })),276        Code));277  }278  {279    // FIXME: We should **not** skip non-inline namespaces.280    SCOPED_TRACE("my bar");281    EXPECT_TRUE(tooling::runToolOnCode(282        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({283            {{CDM::SimpleFunc, {"my", "bar"}}, true},284        })),285        Code));286  }287}288 289TEST(CallDescription, SkipTopInlineNamespaces) {290  constexpr auto Code = R"code(291    inline namespace my {292    namespace v1 {293      void bar();294    } // namespace v1295    } // namespace my296    void foo() {297      using namespace v1;298      bar();299    })code";300 301  {302    SCOPED_TRACE("my v1 bar");303    EXPECT_TRUE(tooling::runToolOnCode(304        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({305            {{CDM::SimpleFunc, {"my", "v1", "bar"}}, true},306        })),307        Code));308  }309  {310    SCOPED_TRACE("v1 bar");311    EXPECT_TRUE(tooling::runToolOnCode(312        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({313            {{CDM::SimpleFunc, {"v1", "bar"}}, true},314        })),315        Code));316  }317}318 319TEST(CallDescription, SkipAnonimousNamespaces) {320  constexpr auto Code = R"code(321    namespace {322    namespace std {323    namespace {324    inline namespace {325      struct container {326        const char *data() const { return nullptr; };327      };328    } // namespace inline anonymous329    } // namespace anonymous330    } // namespace std331    } // namespace anonymous332 333    void foo() {334      std::container v;335      v.data();336    })code";337 338  EXPECT_TRUE(tooling::runToolOnCode(339      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({340          {{CDM::CXXMethod, {"std", "container", "data"}}, true},341      })),342      Code));343}344 345TEST(CallDescription, AliasNames) {346  constexpr StringRef AliasNamesCode = R"code(347  namespace std {348    struct container {349      const char *data() const;350    };351    using cont = container;352  } // std353)code";354 355  constexpr StringRef UseAliasInSpelling = R"code(356    void foo() {357      std::cont v;358      v.data();359    })code";360  const std::string UseAliasInSpellingCode =361      (Twine{AliasNamesCode} + UseAliasInSpelling).str();362 363  // Test if the code spells the alias, wile we match against the struct name,364  // and again matching against the alias.365  {366    SCOPED_TRACE("Using alias in spelling");367    {368      SCOPED_TRACE("std container data");369      EXPECT_TRUE(tooling::runToolOnCode(370          std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({371              {{CDM::CXXMethod, {"std", "container", "data"}}, true},372          })),373          UseAliasInSpellingCode));374    }375    {376      // FIXME: We should be able to see-through aliases.377      SCOPED_TRACE("std cont data");378      EXPECT_TRUE(tooling::runToolOnCode(379          std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({380              {{CDM::CXXMethod, {"std", "cont", "data"}}, false},381          })),382          UseAliasInSpellingCode));383    }384  }385 386  // Test if the code spells the struct name, wile we match against the struct387  // name, and again matching against the alias.388  {389    SCOPED_TRACE("Using struct name in spelling");390    {391      SCOPED_TRACE("std container data");392      EXPECT_TRUE(tooling::runToolOnCode(393          std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({394              {{CDM::CXXMethod, {"std", "container", "data"}}, true},395          })),396          UseAliasInSpellingCode));397    }398    {399      // FIXME: We should be able to see-through aliases.400      SCOPED_TRACE("std cont data");401      EXPECT_TRUE(tooling::runToolOnCode(402          std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({403              {{CDM::CXXMethod, {"std", "cont", "data"}}, false},404          })),405          UseAliasInSpellingCode));406    }407  }408}409 410TEST(CallDescription, AliasSingleNamespace) {411  constexpr StringRef Code = R"code(412    namespace aaa {413    namespace bbb {414    namespace ccc {415      void bar();416    }} // namespace bbb::ccc417    namespace bbb_alias = bbb;418    } // namespace aaa419    void foo() {420      aaa::bbb_alias::ccc::bar();421    })code";422  {423    SCOPED_TRACE("aaa bbb ccc bar");424    EXPECT_TRUE(tooling::runToolOnCode(425        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({426            {{CDM::SimpleFunc, {"aaa", "bbb", "ccc", "bar"}}, true},427        })),428        Code));429  }430  {431    // FIXME: We should be able to see-through namespace aliases.432    SCOPED_TRACE("aaa bbb_alias ccc bar");433    EXPECT_TRUE(tooling::runToolOnCode(434        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({435            {{CDM::SimpleFunc, {"aaa", "bbb_alias", "ccc", "bar"}}, false},436        })),437        Code));438  }439}440 441TEST(CallDescription, AliasMultipleNamespaces) {442  constexpr StringRef Code = R"code(443    namespace aaa {444    namespace bbb {445    namespace ccc {446      void bar();447    }}} // namespace aaa::bbb::ccc448    namespace aaa_bbb_ccc = aaa::bbb::ccc;449    void foo() {450      using namespace aaa_bbb_ccc;451      bar();452    })code";453  {454    SCOPED_TRACE("aaa bbb ccc bar");455    EXPECT_TRUE(tooling::runToolOnCode(456        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({457            {{CDM::SimpleFunc, {"aaa", "bbb", "ccc", "bar"}}, true},458        })),459        Code));460  }461  {462    // FIXME: We should be able to see-through namespace aliases.463    SCOPED_TRACE("aaa_bbb_ccc bar");464    EXPECT_TRUE(tooling::runToolOnCode(465        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({466            {{CDM::SimpleFunc, {"aaa_bbb_ccc", "bar"}}, false},467        })),468        Code));469  }470}471 472TEST(CallDescription, NegativeMatchQualifiedNames) {473  EXPECT_TRUE(tooling::runToolOnCode(474      std::unique_ptr<FrontendAction>(new CallDescriptionAction<>({475          {{CDM::Unspecified, {"foo", "bar"}}, false},476          {{CDM::Unspecified, {"bar", "foo"}}, false},477          {{CDM::Unspecified, {"foo"}}, true},478      })),479      "void foo(); struct bar { void foo(); }; void test() { foo(); }"));480}481 482TEST(CallDescription, MatchBuiltins) {483  // Test the matching modes CDM::CLibrary and CDM::CLibraryMaybeHardened,484  // which can recognize builtin variants of C library functions.485  {486    SCOPED_TRACE("hardened variants of functions");487    EXPECT_TRUE(tooling::runToolOnCode(488        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(489            {{{CDM::Unspecified, {"memset"}, 3}, false},490             {{CDM::CLibrary, {"memset"}, 3}, false},491             {{CDM::CLibraryMaybeHardened, {"memset"}, 3}, true}})),492        "void foo() {"493        "  int x;"494        "  __builtin___memset_chk(&x, 0, sizeof(x),"495        "                         __builtin_object_size(&x, 0));"496        "}"));497  }498  {499    SCOPED_TRACE("multiple similar builtins");500    EXPECT_TRUE(tooling::runToolOnCode(501        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(502            {{{CDM::CLibrary, {"memcpy"}, 3}, false},503             {{CDM::CLibrary, {"wmemcpy"}, 3}, true}})),504        R"(void foo(wchar_t *x, wchar_t *y) {505            __builtin_wmemcpy(x, y, sizeof(wchar_t));506          })"));507  }508  {509    SCOPED_TRACE("multiple similar builtins reversed order");510    EXPECT_TRUE(tooling::runToolOnCode(511        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(512            {{{CDM::CLibrary, {"wmemcpy"}, 3}, true},513             {{CDM::CLibrary, {"memcpy"}, 3}, false}})),514        R"(void foo(wchar_t *x, wchar_t *y) {515            __builtin_wmemcpy(x, y, sizeof(wchar_t));516          })"));517  }518  {519    SCOPED_TRACE("multiple similar builtins with hardened variant");520    EXPECT_TRUE(tooling::runToolOnCode(521        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(522            {{{CDM::CLibraryMaybeHardened, {"memcpy"}, 3}, false},523             {{CDM::CLibraryMaybeHardened, {"wmemcpy"}, 3}, true}})),524        R"(typedef __typeof(sizeof(int)) size_t;525          extern wchar_t *__wmemcpy_chk (wchar_t *__restrict __s1,526                                          const wchar_t *__restrict __s2,527                                          size_t __n, size_t __ns1);528          void foo(wchar_t *x, wchar_t *y) {529            __wmemcpy_chk(x, y, sizeof(wchar_t), 1234);530          })"));531  }532  {533    SCOPED_TRACE(534        "multiple similar builtins with hardened variant reversed order");535    EXPECT_TRUE(tooling::runToolOnCode(536        std::unique_ptr<FrontendAction>(new CallDescriptionAction<>(537            {{{CDM::CLibraryMaybeHardened, {"wmemcpy"}, 3}, true},538             {{CDM::CLibraryMaybeHardened, {"memcpy"}, 3}, false}})),539        R"(typedef __typeof(sizeof(int)) size_t;540          extern wchar_t *__wmemcpy_chk (wchar_t *__restrict __s1,541                                          const wchar_t *__restrict __s2,542                                          size_t __n, size_t __ns1);543          void foo(wchar_t *x, wchar_t *y) {544            __wmemcpy_chk(x, y, sizeof(wchar_t), 1234);545          })"));546  }547  {548    SCOPED_TRACE("lookbehind and lookahead mismatches");549    EXPECT_TRUE(tooling::runToolOnCode(550        std::unique_ptr<FrontendAction>(551            new CallDescriptionAction<>({{{CDM::CLibrary, {"func"}}, false}})),552        R"(553          void funcXXX();554          void XXXfunc();555          void XXXfuncXXX();556          void test() {557            funcXXX();558            XXXfunc();559            XXXfuncXXX();560          })"));561  }562  {563    SCOPED_TRACE("lookbehind and lookahead matches");564    EXPECT_TRUE(tooling::runToolOnCode(565        std::unique_ptr<FrontendAction>(566            new CallDescriptionAction<>({{{CDM::CLibrary, {"func"}}, true}})),567        R"(568          void func();569          void func_XXX();570          void XXX_func();571          void XXX_func_XXX();572 573          void test() {574            func(); // exact match575            func_XXX();576            XXX_func();577            XXX_func_XXX();578          })"));579  }580}581 582//===----------------------------------------------------------------------===//583// Testing through a checker interface.584//585// Above, the static analyzer isn't run properly, only the bare minimum to586// create CallEvents. This causes CallEvents through function pointers to not587// refer to the pointee function, but this works fine if we run588// AnalysisASTConsumer.589//===----------------------------------------------------------------------===//590 591class CallDescChecker592    : public Checker<check::PreCall, check::PreStmt<CallExpr>> {593  CallDescriptionSet Set = {{CDM::SimpleFunc, {"bar"}, 0}};594 595public:596  void checkPreCall(const CallEvent &Call, CheckerContext &C) const {597    if (Set.contains(Call)) {598      C.getBugReporter().EmitBasicReport(599          Call.getDecl(), this, "CallEvent match", categories::LogicError,600          "CallEvent match",601          PathDiagnosticLocation{Call.getDecl(), C.getSourceManager()});602    }603  }604 605  void checkPreStmt(const CallExpr *CE, CheckerContext &C) const {606    if (Set.containsAsWritten(*CE)) {607      C.getBugReporter().EmitBasicReport(608          CE->getCalleeDecl(), this, "CallExpr match", categories::LogicError,609          "CallExpr match",610          PathDiagnosticLocation{CE->getCalleeDecl(), C.getSourceManager()});611    }612  }613};614 615void addCallDescChecker(AnalysisASTConsumer &AnalysisConsumer,616                        AnalyzerOptions &AnOpts) {617  AnOpts.CheckersAndPackages = {{"test.CallDescChecker", true}};618  AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) {619    Registry.addChecker<CallDescChecker>("test.CallDescChecker",620                                         "MockDescription");621  });622}623 624TEST(CallDescription, CheckCallExprMatching) {625  // Imprecise matching shouldn't catch the call to bar, because its obscured626  // by a function pointer.627  constexpr StringRef FnPtrCode = R"code(628    void bar();629    void foo() {630      void (*fnptr)() = bar;631      fnptr();632    })code";633  std::string Diags;634  EXPECT_TRUE(runCheckerOnCode<addCallDescChecker>(FnPtrCode.str(), Diags,635                                                   /*OnlyEmitWarnings*/ true));636  EXPECT_EQ("test.CallDescChecker: CallEvent match\n", Diags);637 638  // This should be caught properly by imprecise matching, as the call is done639  // purely through syntactic means.640  constexpr StringRef Code = R"code(641    void bar();642    void foo() {643      bar();644    })code";645  Diags.clear();646  EXPECT_TRUE(runCheckerOnCode<addCallDescChecker>(Code.str(), Diags,647                                                   /*OnlyEmitWarnings*/ true));648  EXPECT_EQ("test.CallDescChecker: CallEvent match\n"649            "test.CallDescChecker: CallExpr match\n",650            Diags);651}652 653} // namespace654} // namespace ento655} // namespace clang656