vsavchenko created this revision. vsavchenko added reviewers: NoQ, xazax.hun, martong, steakhal, Szelethus, ASDenysPetrov, manas, RedDocMD. Herald added subscribers: dkrupp, donat.nagy, mikhail.ramalho, a.sidorin, rnkovacs, szepet, baloghadamsoftware, mgorny. vsavchenko requested review of this revision. Herald added a project: clang. Herald added a subscriber: cfe-commits.
This commit adds a function to the top-class of SVal hierarchy to provide type information about the value. That can be extremely useful when this is the only piece of information that the user is actually caring about. Additionally, this commit introduces a testing framework for writing unit-tests for symbolic values. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D104550 Files: clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h clang/lib/StaticAnalyzer/Core/SVals.cpp clang/unittests/StaticAnalyzer/CMakeLists.txt clang/unittests/StaticAnalyzer/SValTest.cpp
Index: clang/unittests/StaticAnalyzer/SValTest.cpp =================================================================== --- /dev/null +++ clang/unittests/StaticAnalyzer/SValTest.cpp @@ -0,0 +1,303 @@ +//===- unittests/StaticAnalyzer/SvalTest.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/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclGroup.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/Type.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" +#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/raw_ostream.h" +#include "gtest/gtest.h" + +namespace clang { + +// getType() tests include whole bunch of type comparisons, +// so when something is wrong, it's good to have gtest telling us +// what are those types. +LLVM_ATTRIBUTE_UNUSED std::ostream &operator<<(std::ostream &OS, + const QualType &T) { + return OS << T.getAsString(); +} + +namespace ento { +namespace { + +//===----------------------------------------------------------------------===// +// Testing framework implementation +//===----------------------------------------------------------------------===// + +/// A simple map from variable names to symbolic values used to init them. +using SVals = llvm::StringMap<SVal>; + +/// SValCollector is the barebone of all tests. +/// +/// It is implemented as a checker and reacts to binds, so we find +/// symbolic values of interest, and to end analysis, where we actually +/// can test whatever we gathered. +class SValCollector : public Checker<check::Bind, check::EndAnalysis> { +public: + void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &C) const { + // Skip instantly if we finished testing. + // Also, we care only for binds happening in variable initializations. + if (Tested || !isa<DeclStmt>(S)) + return; + + if (const auto *VR = llvm::dyn_cast_or_null<VarRegion>(Loc.getAsRegion())) { + CollectedSVals[VR->getDescriptiveName(false)] = Val; + } + } + + void checkEndAnalysis(ExplodedGraph &G, BugReporter &B, + ExprEngine &Engine) const { + if (!Tested) { + test(Engine); + Tested = true; + CollectedSVals.clear(); + } + } + + /// Helper function for tests to access bound symbolic values. + SVal getByName(StringRef Name) const { return CollectedSVals[Name]; } + +private: + /// Entry point for tests. + virtual void test(ExprEngine &Engine) const = 0; + + mutable bool Tested = false; + mutable SVals CollectedSVals; +}; + +// SVAL_TEST is a combined way of providing a short code snippet and +// to test some programmatic predicates on symbolic values produced by the +// engine for the actual code. +// +// Each test has a NAME. One can think of it as a name for normal gtests. +// +// Each test should provide a CODE snippet. Code snippets might contain any +// valid C/C++, but have ONLY ONE defined function. There are no requirements +// about function's name or parameters. It can even be a class method. The +// body of the function must contain a set of variable declarations. Each +// variable declaration gets bound to a symbolic value, so for the following +// example: +// +// int x = <expr>; +// +// `x` will be bound to whatever symbolic value the engine produced for <expr>. +// LIVENESS and REASSIGNMENTS don't affect this binding. +// +// During the test the actual values can be accessed via `getByName` function, +// and, for the `x`-bound value, one must use "x" as its name. +// +// Example: +// SVAL_TEST(SimpleSValTest, R"( +// void foo() { +// int x = 42; +// })") { +// SVal X = getByName("x"); +// EXPECT_TRUE(X.isConstant(42)); +// } +#define SVAL_TEST(NAME, CODE) \ + class NAME##SValCollector final : public SValCollector { \ + public: \ + void test(ExprEngine &Engine) const override; \ + }; \ + \ + void add##NAME##SValCollector(AnalysisASTConsumer &AnalysisConsumer, \ + AnalyzerOptions &AnOpts) { \ + AnOpts.CheckersAndPackages = {{"test.##NAME##SValCollector", true}}; \ + AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { \ + Registry.addChecker<NAME##SValCollector>("test.##NAME##SValCollector", \ + "Description", ""); \ + }); \ + } \ + \ + TEST(SValTest, NAME) { runCheckerOnCode<add##NAME##SValCollector>(CODE); } \ + void NAME##SValCollector::test(ExprEngine &Engine) const + +//===----------------------------------------------------------------------===// +// Actual tests +//===----------------------------------------------------------------------===// + +SVAL_TEST(GetTypeConst, R"( +void foo() { + int x = 42; +})") { + SVal X = getByName("x"); + // TODO: Find a way how to get type of nonloc::ConcreteInt + EXPECT_FALSE(X.getType().hasValue()); +} + +SVAL_TEST(GetSymExprType, R"( +void foo(int a, int b) { + int x = a; + int y = a + b; +})") { + QualType Int = Engine.getContext().IntTy; + + SVal X = getByName("x"); + ASSERT_TRUE(X.getType().hasValue()); + EXPECT_EQ(Int, *X.getType()); + + SVal Y = getByName("y"); + ASSERT_TRUE(Y.getType().hasValue()); + EXPECT_EQ(Int, *Y.getType()); +} + +SVAL_TEST(GetPointerType, R"( +int *bar(); +int &foobar(); +struct Z { + int a; + int *b; +}; +void foo(int x, int *y, Z z) { + int &a = x; + int &b = *y; + int &c = *bar(); + int &d = foobar(); + int &e = z.a; + int &f = *z.b; +})") { + QualType Int = Engine.getContext().IntTy; + + SVal A = getByName("a"); + ASSERT_TRUE(A.getType().hasValue()); + const auto *APtrTy = dyn_cast<PointerType>(*A.getType()); + ASSERT_NE(APtrTy, nullptr); + EXPECT_EQ(Int, APtrTy->getPointeeType()); + + SVal B = getByName("b"); + ASSERT_TRUE(B.getType().hasValue()); + const auto *BPtrTy = dyn_cast<PointerType>(*B.getType()); + ASSERT_NE(BPtrTy, nullptr); + EXPECT_EQ(Int, BPtrTy->getPointeeType()); + + SVal C = getByName("c"); + ASSERT_TRUE(C.getType().hasValue()); + const auto *CPtrTy = dyn_cast<PointerType>(*C.getType()); + ASSERT_NE(CPtrTy, nullptr); + EXPECT_EQ(Int, CPtrTy->getPointeeType()); + + SVal D = getByName("d"); + ASSERT_TRUE(D.getType().hasValue()); + const auto *DRefTy = dyn_cast<LValueReferenceType>(*D.getType()); + ASSERT_NE(DRefTy, nullptr); + EXPECT_EQ(Int, DRefTy->getPointeeType()); + + SVal E = getByName("e"); + ASSERT_TRUE(E.getType().hasValue()); + const auto *EPtrTy = dyn_cast<PointerType>(*E.getType()); + ASSERT_NE(EPtrTy, nullptr); + EXPECT_EQ(Int, EPtrTy->getPointeeType()); + + SVal F = getByName("f"); + ASSERT_TRUE(F.getType().hasValue()); + const auto *FPtrTy = dyn_cast<PointerType>(*F.getType()); + ASSERT_NE(FPtrTy, nullptr); + EXPECT_EQ(Int, FPtrTy->getPointeeType()); +} + +SVAL_TEST(GetCompoundType, R"( +struct TestStruct { + int a, b; +}; +union TestUnion { + int a; + float b; + TestStruct c; +}; +void foo(int x) { + int a[] = {1, x, 2}; + TestStruct b = {x, 42}; + TestUnion c = {42}; + TestUnion d = {.c=b}; +} +)") { + SVal A = getByName("a"); + ASSERT_TRUE(A.getType().hasValue()); + const auto *AArrayType = dyn_cast<ArrayType>(*A.getType()); + ASSERT_NE(AArrayType, nullptr); + EXPECT_EQ(Engine.getContext().IntTy, AArrayType->getElementType()); + + SVal B = getByName("b"); + ASSERT_TRUE(B.getType().hasValue()); + const auto *BRecordType = dyn_cast<RecordType>(*B.getType()); + ASSERT_NE(BRecordType, nullptr); + EXPECT_EQ("TestStruct", BRecordType->getDecl()->getName()); + + SVal C = getByName("c"); + ASSERT_TRUE(C.getType().hasValue()); + const auto *CRecordType = dyn_cast<RecordType>(*C.getType()); + ASSERT_NE(CRecordType, nullptr); + EXPECT_EQ("TestUnion", CRecordType->getDecl()->getName()); + + SVal D = getByName("d"); + ASSERT_TRUE(D.getType().hasValue()); + const auto *DRecordType = dyn_cast<RecordType>(*D.getType()); + ASSERT_NE(DRecordType, nullptr); + EXPECT_EQ("TestUnion", DRecordType->getDecl()->getName()); +} + +SVAL_TEST(GetStringType, R"( +void foo() { + const char *a = "Hello, world!"; +} +)") { + SVal A = getByName("a"); + ASSERT_TRUE(A.getType().hasValue()); + const auto *APtrTy = dyn_cast<PointerType>(*A.getType()); + ASSERT_NE(APtrTy, nullptr); + EXPECT_EQ(Engine.getContext().CharTy, APtrTy->getPointeeType()); +} + +SVAL_TEST(GetThisType, R"( +class TestClass { + void foo(); +}; +void TestClass::foo() { + const auto *a = this; +} +)") { + SVal A = getByName("a"); + ASSERT_TRUE(A.getType().hasValue()); + const auto *APtrTy = dyn_cast<PointerType>(*A.getType()); + ASSERT_NE(APtrTy, nullptr); + const auto *ARecordType = dyn_cast<RecordType>(APtrTy->getPointeeType()); + ASSERT_NE(ARecordType, nullptr); + EXPECT_EQ("TestClass", ARecordType->getDecl()->getName()); +} + +SVAL_TEST(GetFunctionPtrType, R"( +void bar(); +void foo() { + auto *a = &bar; +} +)") { + SVal A = getByName("a"); + ASSERT_TRUE(A.getType().hasValue()); + const auto *APtrTy = dyn_cast<PointerType>(*A.getType()); + ASSERT_NE(APtrTy, nullptr); + ASSERT_TRUE(isa<FunctionProtoType>(APtrTy->getPointeeType())); +} + +} // namespace +} // namespace ento +} // namespace clang Index: clang/unittests/StaticAnalyzer/CMakeLists.txt =================================================================== --- clang/unittests/StaticAnalyzer/CMakeLists.txt +++ clang/unittests/StaticAnalyzer/CMakeLists.txt @@ -11,8 +11,9 @@ ParamRegionTest.cpp RangeSetTest.cpp RegisterCustomCheckersTest.cpp - StoreTest.cpp + StoreTest.cpp SymbolReaperTest.cpp + SValTest.cpp TestReturnValueUnderConstruction.cpp ) Index: clang/lib/StaticAnalyzer/Core/SVals.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/SVals.cpp +++ clang/lib/StaticAnalyzer/Core/SVals.cpp @@ -21,6 +21,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValVisitor.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "llvm/ADT/Optional.h" @@ -136,6 +137,37 @@ return nullptr; } +namespace { +class TypeRetrievingVisitor + : public FullSValVisitor<TypeRetrievingVisitor, Optional<QualType>> { +public: + Optional<QualType> VisitLocMemRegionVal(loc::MemRegionVal MRV) { + return Visit(MRV.getRegion()); + } + Optional<QualType> VisitNonLocCompoundVal(nonloc::CompoundVal CV) { + return CV.getValue()->getType(); + } + Optional<QualType> VisitNonLocLazyCompoundVal(nonloc::LazyCompoundVal LCV) { + return Visit(LCV.getRegion()); + } + Optional<QualType> VisitNonLocSymbolVal(nonloc::SymbolVal SV) { + return Visit(SV.getSymbol()); + } + Optional<QualType> VisitSymbolicRegion(const SymbolicRegion *SR) { + return Visit(SR->getSymbol()); + } + Optional<QualType> VisitTypedRegion(const TypedRegion *TR) { + return TR->getLocationType(); + } + Optional<QualType> VisitSymExpr(const SymExpr *SE) { return SE->getType(); } +}; +} // end anonymous namespace + +Optional<QualType> SVal::getType() const { + TypeRetrievingVisitor TRV; + return TRV.Visit(*this); +} + const MemRegion *loc::MemRegionVal::stripCasts(bool StripBaseCasts) const { const MemRegion *R = getRegion(); return R ? R->StripCasts(StripBaseCasts) : nullptr; Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h @@ -201,6 +201,9 @@ SymExpr::symbol_iterator symbol_end() const { return SymExpr::symbol_end(); } + + /// Try to get a reasonable type for the given value. + llvm::Optional<QualType> getType() const; }; inline raw_ostream &operator<<(raw_ostream &os, clang::ento::SVal V) { Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h @@ -52,6 +52,8 @@ iterator begin() const { return L.begin(); } iterator end() const { return L.end(); } + QualType getType() const { return T; } + static void Profile(llvm::FoldingSetNodeID& ID, QualType T, llvm::ImmutableList<SVal> L);
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits