1 //===- unittests/StaticAnalyzer/SvalTest.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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "CheckerRegistration.h" 10 11 #include "clang/AST/ASTContext.h" 12 #include "clang/AST/Decl.h" 13 #include "clang/AST/DeclGroup.h" 14 #include "clang/AST/RecursiveASTVisitor.h" 15 #include "clang/AST/Stmt.h" 16 #include "clang/AST/Type.h" 17 #include "clang/StaticAnalyzer/Core/Checker.h" 18 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 19 #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" 20 #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" 21 #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" 22 #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" 23 #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" 24 #include "clang/Testing/TestClangConfig.h" 25 #include "clang/Tooling/Tooling.h" 26 #include "llvm/ADT/STLExtras.h" 27 #include "llvm/ADT/StringRef.h" 28 #include "llvm/Support/Casting.h" 29 #include "llvm/Support/raw_ostream.h" 30 #include "gtest/gtest.h" 31 32 namespace clang { 33 34 // getType() tests include whole bunch of type comparisons, 35 // so when something is wrong, it's good to have gtest telling us 36 // what are those types. 37 LLVM_ATTRIBUTE_UNUSED std::ostream &operator<<(std::ostream &OS, 38 const QualType &T) { 39 return OS << T.getAsString(); 40 } 41 42 LLVM_ATTRIBUTE_UNUSED std::ostream &operator<<(std::ostream &OS, 43 const CanQualType &T) { 44 return OS << QualType{T}; 45 } 46 47 namespace ento { 48 namespace { 49 50 //===----------------------------------------------------------------------===// 51 // Testing framework implementation 52 //===----------------------------------------------------------------------===// 53 54 /// A simple map from variable names to symbolic values used to init them. 55 using SVals = llvm::StringMap<SVal>; 56 57 /// SValCollector is the barebone of all tests. 58 /// 59 /// It is implemented as a checker and reacts to binds, so we find 60 /// symbolic values of interest, and to end analysis, where we actually 61 /// can test whatever we gathered. 62 class SValCollector : public Checker<check::Bind, check::EndAnalysis> { 63 public: 64 void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &C) const { 65 // Skip instantly if we finished testing. 66 // Also, we care only for binds happening in variable initializations. 67 if (Tested || !isa<DeclStmt>(S)) 68 return; 69 70 if (const auto *VR = llvm::dyn_cast_or_null<VarRegion>(Loc.getAsRegion())) { 71 CollectedSVals[VR->getDescriptiveName(false)] = Val; 72 } 73 } 74 75 void checkEndAnalysis(ExplodedGraph &G, BugReporter &B, 76 ExprEngine &Engine) const { 77 if (!Tested) { 78 test(Engine, Engine.getContext()); 79 Tested = true; 80 CollectedSVals.clear(); 81 } 82 } 83 84 /// Helper function for tests to access bound symbolic values. 85 SVal getByName(StringRef Name) const { return CollectedSVals[Name]; } 86 87 private: 88 /// Entry point for tests. 89 virtual void test(ExprEngine &Engine, const ASTContext &Context) const = 0; 90 91 mutable bool Tested = false; 92 mutable SVals CollectedSVals; 93 }; 94 95 // Fixture class for parameterized SValTest 96 class SValTest : public testing::TestWithParam<TestClangConfig> { 97 protected: 98 // FIXME: The tests "GetConstType" and "GetLocAsIntType" infer the type of 99 // integrals based on their bitwidth. This is not a reliable method on 100 // platforms where different integrals have the same width. 101 bool skipTest(StringRef TestName) { 102 std::string target = GetParam().Target; 103 return (target == "powerpc-ibm-aix" || target == "i686-apple-darwin9" || 104 target == "wasm32-unknown-unknown" || 105 target == "wasm64-unknown-unknown") && 106 (TestName == "GetConstType" || TestName == "GetLocAsIntType"); 107 } 108 }; 109 110 // SVAL_TEST is a combined way of providing a short code snippet and 111 // to test some programmatic predicates on symbolic values produced by the 112 // engine for the actual code. 113 // 114 // Each test has a NAME. One can think of it as a name for normal gtests. 115 // 116 // Each test should provide a CODE snippet. Code snippets might contain any 117 // valid C/C++, but have ONLY ONE defined function. There are no requirements 118 // about function's name or parameters. It can even be a class method. The 119 // body of the function must contain a set of variable declarations. Each 120 // variable declaration gets bound to a symbolic value, so for the following 121 // example: 122 // 123 // int x = <expr>; 124 // 125 // `x` will be bound to whatever symbolic value the engine produced for <expr>. 126 // LIVENESS and REASSIGNMENTS don't affect this binding. 127 // 128 // During the test the actual values can be accessed via `getByName` function, 129 // and, for the `x`-bound value, one must use "x" as its name. 130 // 131 // Example: 132 // SVAL_TEST(SimpleSValTest, R"( 133 // void foo() { 134 // int x = 42; 135 // })") { 136 // SVal X = getByName("x"); 137 // EXPECT_TRUE(X.isConstant(42)); 138 // } 139 #define SVAL_TEST(NAME, CODE) \ 140 class NAME##SValCollector final : public SValCollector { \ 141 public: \ 142 void test(ExprEngine &Engine, const ASTContext &Context) const override; \ 143 }; \ 144 \ 145 void add##NAME##SValCollector(AnalysisASTConsumer &AnalysisConsumer, \ 146 AnalyzerOptions &AnOpts) { \ 147 AnOpts.CheckersAndPackages = {{"test.##NAME##SValCollector", true}}; \ 148 AnalysisConsumer.AddCheckerRegistrationFn([](CheckerRegistry &Registry) { \ 149 Registry.addChecker<NAME##SValCollector>("test.##NAME##SValCollector", \ 150 "Description", ""); \ 151 }); \ 152 } \ 153 \ 154 TEST_P(SValTest, NAME) { \ 155 if (skipTest(#NAME)) { \ 156 std::string target = GetParam().Target; \ 157 GTEST_SKIP() << "certain integrals have the same bitwidth on " \ 158 << target; \ 159 return; \ 160 } \ 161 runCheckerOnCodeWithArgs<add##NAME##SValCollector>( \ 162 CODE, GetParam().getCommandLineArgs()); \ 163 } \ 164 void NAME##SValCollector::test(ExprEngine &Engine, \ 165 const ASTContext &Context) const 166 167 //===----------------------------------------------------------------------===// 168 // Actual tests 169 //===----------------------------------------------------------------------===// 170 171 SVAL_TEST(GetConstType, R"( 172 void foo() { 173 int x = 42; 174 int *y = nullptr; 175 })") { 176 SVal X = getByName("x"); 177 ASSERT_FALSE(X.getType(Context).isNull()); 178 EXPECT_EQ(Context.IntTy, X.getType(Context)); 179 180 SVal Y = getByName("y"); 181 ASSERT_FALSE(Y.getType(Context).isNull()); 182 EXPECT_EQ(Context.getUIntPtrType(), Y.getType(Context)); 183 } 184 185 SVAL_TEST(GetLocAsIntType, R"( 186 void foo(int *x) { 187 long int a = (long int)x; 188 unsigned b = (long unsigned)&a; 189 int c = (long int)nullptr; 190 })") { 191 SVal A = getByName("a"); 192 ASSERT_FALSE(A.getType(Context).isNull()); 193 // TODO: Turn it into signed long 194 EXPECT_EQ(Context.getUIntPtrType(), A.getType(Context)); 195 196 SVal B = getByName("b"); 197 ASSERT_FALSE(B.getType(Context).isNull()); 198 EXPECT_EQ(Context.UnsignedIntTy, B.getType(Context)); 199 200 SVal C = getByName("c"); 201 ASSERT_FALSE(C.getType(Context).isNull()); 202 EXPECT_EQ(Context.IntTy, C.getType(Context)); 203 } 204 205 SVAL_TEST(GetSymExprType, R"( 206 void foo(int a, int b) { 207 int x = a; 208 int y = a + b; 209 long z = a; 210 })") { 211 QualType Int = Context.IntTy; 212 213 SVal X = getByName("x"); 214 ASSERT_FALSE(X.getType(Context).isNull()); 215 EXPECT_EQ(Int, X.getType(Context)); 216 217 SVal Y = getByName("y"); 218 ASSERT_FALSE(Y.getType(Context).isNull()); 219 EXPECT_EQ(Int, Y.getType(Context)); 220 221 // TODO: Change to Long when we support symbolic casts 222 SVal Z = getByName("z"); 223 ASSERT_FALSE(Z.getType(Context).isNull()); 224 EXPECT_EQ(Int, Z.getType(Context)); 225 } 226 227 SVAL_TEST(GetPointerType, R"( 228 int *bar(); 229 int &foobar(); 230 struct Z { 231 int a; 232 int *b; 233 }; 234 void foo(int x, int *y, Z z) { 235 int &a = x; 236 int &b = *y; 237 int &c = *bar(); 238 int &d = foobar(); 239 int &e = z.a; 240 int &f = *z.b; 241 })") { 242 QualType Int = Context.IntTy; 243 244 SVal A = getByName("a"); 245 ASSERT_FALSE(A.getType(Context).isNull()); 246 const auto *APtrTy = dyn_cast<PointerType>(A.getType(Context)); 247 ASSERT_NE(APtrTy, nullptr); 248 EXPECT_EQ(Int, APtrTy->getPointeeType()); 249 250 SVal B = getByName("b"); 251 ASSERT_FALSE(B.getType(Context).isNull()); 252 const auto *BPtrTy = dyn_cast<PointerType>(B.getType(Context)); 253 ASSERT_NE(BPtrTy, nullptr); 254 EXPECT_EQ(Int, BPtrTy->getPointeeType()); 255 256 SVal C = getByName("c"); 257 ASSERT_FALSE(C.getType(Context).isNull()); 258 const auto *CPtrTy = dyn_cast<PointerType>(C.getType(Context)); 259 ASSERT_NE(CPtrTy, nullptr); 260 EXPECT_EQ(Int, CPtrTy->getPointeeType()); 261 262 SVal D = getByName("d"); 263 ASSERT_FALSE(D.getType(Context).isNull()); 264 const auto *DRefTy = dyn_cast<LValueReferenceType>(D.getType(Context)); 265 ASSERT_NE(DRefTy, nullptr); 266 EXPECT_EQ(Int, DRefTy->getPointeeType()); 267 268 SVal E = getByName("e"); 269 ASSERT_FALSE(E.getType(Context).isNull()); 270 const auto *EPtrTy = dyn_cast<PointerType>(E.getType(Context)); 271 ASSERT_NE(EPtrTy, nullptr); 272 EXPECT_EQ(Int, EPtrTy->getPointeeType()); 273 274 SVal F = getByName("f"); 275 ASSERT_FALSE(F.getType(Context).isNull()); 276 const auto *FPtrTy = dyn_cast<PointerType>(F.getType(Context)); 277 ASSERT_NE(FPtrTy, nullptr); 278 EXPECT_EQ(Int, FPtrTy->getPointeeType()); 279 } 280 281 SVAL_TEST(GetCompoundType, R"( 282 struct TestStruct { 283 int a, b; 284 }; 285 union TestUnion { 286 int a; 287 float b; 288 TestStruct c; 289 }; 290 void foo(int x) { 291 int a[] = {1, x, 2}; 292 TestStruct b = {x, 42}; 293 TestUnion c = {42}; 294 TestUnion d = {.c=b}; 295 } 296 )") { 297 SVal A = getByName("a"); 298 ASSERT_FALSE(A.getType(Context).isNull()); 299 const auto *AArrayType = dyn_cast<ArrayType>(A.getType(Context)); 300 ASSERT_NE(AArrayType, nullptr); 301 EXPECT_EQ(Context.IntTy, AArrayType->getElementType()); 302 303 SVal B = getByName("b"); 304 ASSERT_FALSE(B.getType(Context).isNull()); 305 const auto *BRecordType = dyn_cast<RecordType>(B.getType(Context)); 306 ASSERT_NE(BRecordType, nullptr); 307 EXPECT_EQ("TestStruct", BRecordType->getDecl()->getName()); 308 309 SVal C = getByName("c"); 310 ASSERT_FALSE(C.getType(Context).isNull()); 311 const auto *CRecordType = dyn_cast<RecordType>(C.getType(Context)); 312 ASSERT_NE(CRecordType, nullptr); 313 EXPECT_EQ("TestUnion", CRecordType->getDecl()->getName()); 314 315 auto D = getByName("d").getAs<nonloc::CompoundVal>(); 316 ASSERT_TRUE(D.hasValue()); 317 auto Begin = D->begin(); 318 ASSERT_NE(D->end(), Begin); 319 ++Begin; 320 ASSERT_EQ(D->end(), Begin); 321 auto LD = D->begin()->getAs<nonloc::LazyCompoundVal>(); 322 ASSERT_TRUE(LD.hasValue()); 323 auto LDT = LD->getType(Context); 324 ASSERT_FALSE(LDT.isNull()); 325 const auto *DRecordType = dyn_cast<RecordType>(LDT); 326 ASSERT_NE(DRecordType, nullptr); 327 EXPECT_EQ("TestStruct", DRecordType->getDecl()->getName()); 328 } 329 330 SVAL_TEST(GetStringType, R"( 331 void foo() { 332 const char *a = "Hello, world!"; 333 } 334 )") { 335 SVal A = getByName("a"); 336 ASSERT_FALSE(A.getType(Context).isNull()); 337 const auto *APtrTy = dyn_cast<PointerType>(A.getType(Context)); 338 ASSERT_NE(APtrTy, nullptr); 339 EXPECT_EQ(Context.CharTy, APtrTy->getPointeeType()); 340 } 341 342 SVAL_TEST(GetThisType, R"( 343 class TestClass { 344 void foo(); 345 }; 346 void TestClass::foo() { 347 const auto *a = this; 348 } 349 )") { 350 SVal A = getByName("a"); 351 ASSERT_FALSE(A.getType(Context).isNull()); 352 const auto *APtrTy = dyn_cast<PointerType>(A.getType(Context)); 353 ASSERT_NE(APtrTy, nullptr); 354 const auto *ARecordType = dyn_cast<RecordType>(APtrTy->getPointeeType()); 355 ASSERT_NE(ARecordType, nullptr); 356 EXPECT_EQ("TestClass", ARecordType->getDecl()->getName()); 357 } 358 359 SVAL_TEST(GetFunctionPtrType, R"( 360 void bar(); 361 void foo() { 362 auto *a = &bar; 363 } 364 )") { 365 SVal A = getByName("a"); 366 ASSERT_FALSE(A.getType(Context).isNull()); 367 const auto *APtrTy = dyn_cast<PointerType>(A.getType(Context)); 368 ASSERT_NE(APtrTy, nullptr); 369 ASSERT_TRUE(isa<FunctionProtoType>(APtrTy->getPointeeType())); 370 } 371 372 SVAL_TEST(GetLabelType, R"( 373 void foo() { 374 entry: 375 void *a = &&entry; 376 char *b = (char *)&&entry; 377 } 378 )") { 379 SVal A = getByName("a"); 380 ASSERT_FALSE(A.getType(Context).isNull()); 381 EXPECT_EQ(Context.VoidPtrTy, A.getType(Context)); 382 383 SVal B = getByName("a"); 384 ASSERT_FALSE(B.getType(Context).isNull()); 385 // TODO: Change to CharTy when we support symbolic casts 386 EXPECT_EQ(Context.VoidPtrTy, B.getType(Context)); 387 } 388 389 std::vector<TestClangConfig> allTestClangConfigs() { 390 std::vector<TestClangConfig> all_configs; 391 TestClangConfig config; 392 config.Language = Lang_CXX14; 393 for (std::string target : 394 {"i686-pc-windows-msvc", "i686-apple-darwin9", 395 "x86_64-apple-darwin9", "x86_64-scei-ps4", 396 "x86_64-windows-msvc", "x86_64-unknown-linux", 397 "x86_64-apple-macosx", "x86_64-apple-ios14.0", 398 "wasm32-unknown-unknown", "wasm64-unknown-unknown", 399 "thumb-pc-win32", "sparc64-none-openbsd", 400 "sparc-none-none", "riscv64-unknown-linux", 401 "ppc64-windows-msvc", "powerpc-ibm-aix", 402 "powerpc64-ibm-aix", "s390x-ibm-zos", 403 "armv7-pc-windows-msvc", "aarch64-pc-windows-msvc", 404 "xcore-xmos-elf"}) { 405 config.Target = target; 406 all_configs.push_back(config); 407 } 408 return all_configs; 409 } 410 411 INSTANTIATE_TEST_SUITE_P(SValTests, SValTest, 412 testing::ValuesIn(allTestClangConfigs())); 413 414 } // namespace 415 } // namespace ento 416 } // namespace clang 417