554 lines · cpp
1//===- unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.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 "clang/Analysis/FlowSensitive/DataflowEnvironment.h"10#include "TestingSupport.h"11#include "clang/AST/DeclCXX.h"12#include "clang/AST/ExprCXX.h"13#include "clang/AST/Stmt.h"14#include "clang/ASTMatchers/ASTMatchFinder.h"15#include "clang/ASTMatchers/ASTMatchers.h"16#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"17#include "clang/Analysis/FlowSensitive/StorageLocation.h"18#include "clang/Analysis/FlowSensitive/Value.h"19#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"20#include "clang/Tooling/Tooling.h"21#include "gmock/gmock.h"22#include "gtest/gtest.h"23#include <memory>24#include <string>25 26namespace {27 28using namespace clang;29using namespace dataflow;30using ::clang::dataflow::test::findValueDecl;31using ::clang::dataflow::test::getFieldValue;32using ::testing::Contains;33using ::testing::IsNull;34using ::testing::NotNull;35 36class EnvironmentTest : public ::testing::Test {37protected:38 EnvironmentTest() : DAContext(std::make_unique<WatchedLiteralsSolver>()) {}39 40 DataflowAnalysisContext DAContext;41};42 43TEST_F(EnvironmentTest, FlowCondition) {44 Environment Env(DAContext);45 auto &A = Env.arena();46 47 EXPECT_TRUE(Env.proves(A.makeLiteral(true)));48 EXPECT_TRUE(Env.allows(A.makeLiteral(true)));49 EXPECT_FALSE(Env.proves(A.makeLiteral(false)));50 EXPECT_FALSE(Env.allows(A.makeLiteral(false)));51 52 auto &X = A.makeAtomRef(A.makeAtom());53 EXPECT_FALSE(Env.proves(X));54 EXPECT_TRUE(Env.allows(X));55 56 Env.assume(X);57 EXPECT_TRUE(Env.proves(X));58 EXPECT_TRUE(Env.allows(X));59 60 auto &NotX = A.makeNot(X);61 EXPECT_FALSE(Env.proves(NotX));62 EXPECT_FALSE(Env.allows(NotX));63}64 65TEST_F(EnvironmentTest, SetAndGetValueOnCfgOmittedNodes) {66 // Check that we can set a value on an expression that is omitted from the CFG67 // (see `ignoreCFGOmittedNodes()`), then retrieve that same value from the68 // expression. This is a regression test; `setValue()` and `getValue()`69 // previously did not use `ignoreCFGOmittedNodes()` consistently.70 71 using namespace ast_matchers;72 73 std::string Code = R"cc(74 struct S {75 int f();76 };77 void target() {78 // Method call on a temporary produces an `ExprWithCleanups`.79 S().f();80 (1);81 }82 )cc";83 84 auto Unit =85 tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++17"});86 auto &Context = Unit->getASTContext();87 88 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);89 90 const ExprWithCleanups *WithCleanups = selectFirst<ExprWithCleanups>(91 "cleanups",92 match(exprWithCleanups(hasType(isInteger())).bind("cleanups"), Context));93 ASSERT_NE(WithCleanups, nullptr);94 95 const ParenExpr *Paren = selectFirst<ParenExpr>(96 "paren", match(parenExpr(hasType(isInteger())).bind("paren"), Context));97 ASSERT_NE(Paren, nullptr);98 99 Environment Env(DAContext);100 IntegerValue *Val1 =101 cast<IntegerValue>(Env.createValue(Unit->getASTContext().IntTy));102 Env.setValue(*WithCleanups, *Val1);103 EXPECT_EQ(Env.getValue(*WithCleanups), Val1);104 105 IntegerValue *Val2 =106 cast<IntegerValue>(Env.createValue(Unit->getASTContext().IntTy));107 Env.setValue(*Paren, *Val2);108 EXPECT_EQ(Env.getValue(*Paren), Val2);109}110 111TEST_F(EnvironmentTest, CreateValueRecursiveType) {112 using namespace ast_matchers;113 114 std::string Code = R"cc(115 struct Recursive {116 bool X;117 Recursive *R;118 };119 // Use both fields to force them to be created with `createValue`.120 void Usage(Recursive R) { (void)R.X; (void)R.R; }121 )cc";122 123 auto Unit =124 tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"});125 auto &Context = Unit->getASTContext();126 127 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);128 129 auto Results =130 match(qualType(hasDeclaration(recordDecl(131 hasName("Recursive"),132 has(fieldDecl(hasName("R")).bind("field-r")))))133 .bind("target"),134 Context);135 const QualType *TyPtr = selectFirst<QualType>("target", Results);136 ASSERT_THAT(TyPtr, NotNull());137 QualType Ty = *TyPtr;138 ASSERT_FALSE(Ty.isNull());139 140 const FieldDecl *R = selectFirst<FieldDecl>("field-r", Results);141 ASSERT_THAT(R, NotNull());142 143 Results = match(functionDecl(hasName("Usage")).bind("fun"), Context);144 const auto *Fun = selectFirst<FunctionDecl>("fun", Results);145 ASSERT_THAT(Fun, NotNull());146 147 // Verify that the struct and the field (`R`) with first appearance of the148 // type is created successfully.149 Environment Env(DAContext, *Fun);150 Env.initialize();151 auto &SLoc = cast<RecordStorageLocation>(Env.createObject(Ty));152 PointerValue *PV = cast_or_null<PointerValue>(getFieldValue(&SLoc, *R, Env));153 EXPECT_THAT(PV, NotNull());154}155 156TEST_F(EnvironmentTest, DifferentReferenceLocInJoin) {157 // This tests the case where the storage location for a reference-type158 // variable is different for two states being joined. We used to believe this159 // could not happen and therefore had an assertion disallowing this; this test160 // exists to demonstrate that we can handle this condition without a failing161 // assertion. See also the discussion here:162 // https://discourse.llvm.org/t/70086/6163 164 using namespace ast_matchers;165 166 std::string Code = R"cc(167 void f(int &ref) {}168 )cc";169 170 auto Unit =171 tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"});172 auto &Context = Unit->getASTContext();173 174 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);175 176 const ValueDecl *Ref = findValueDecl(Context, "ref");177 178 Environment Env1(DAContext);179 StorageLocation &Loc1 = Env1.createStorageLocation(Context.IntTy);180 Env1.setStorageLocation(*Ref, Loc1);181 182 Environment Env2(DAContext);183 StorageLocation &Loc2 = Env2.createStorageLocation(Context.IntTy);184 Env2.setStorageLocation(*Ref, Loc2);185 186 EXPECT_NE(&Loc1, &Loc2);187 188 Environment::ValueModel Model;189 Environment EnvJoined =190 Environment::join(Env1, Env2, Model, Environment::DiscardExprState);191 192 // Joining environments with different storage locations for the same193 // declaration results in the declaration being removed from the joined194 // environment.195 EXPECT_EQ(EnvJoined.getStorageLocation(*Ref), nullptr);196}197 198TEST_F(EnvironmentTest, InitGlobalVarsFun) {199 using namespace ast_matchers;200 201 std::string Code = R"cc(202 int Global = 0;203 int Target () { return Global; }204 )cc";205 206 auto Unit =207 tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"});208 auto &Context = Unit->getASTContext();209 210 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);211 212 auto Results =213 match(decl(anyOf(varDecl(hasName("Global")).bind("global"),214 functionDecl(hasName("Target")).bind("target"))),215 Context);216 const auto *Fun = selectFirst<FunctionDecl>("target", Results);217 const auto *Var = selectFirst<VarDecl>("global", Results);218 ASSERT_THAT(Fun, NotNull());219 ASSERT_THAT(Var, NotNull());220 221 // Verify the global variable is populated when we analyze `Target`.222 Environment Env(DAContext, *Fun);223 Env.initialize();224 EXPECT_THAT(Env.getValue(*Var), NotNull());225}226 227// Tests that fields mentioned only in default member initializers are included228// in the set of tracked fields.229TEST_F(EnvironmentTest, IncludeFieldsFromDefaultInitializers) {230 using namespace ast_matchers;231 232 std::string Code = R"cc(233 struct S {234 S() {}235 int X = 3;236 int Y = X;237 };238 S foo();239 )cc";240 241 auto Unit =242 tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"});243 auto &Context = Unit->getASTContext();244 245 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);246 247 auto Results = match(248 qualType(hasDeclaration(249 cxxRecordDecl(hasName("S"),250 hasMethod(cxxConstructorDecl().bind("target")))251 .bind("struct")))252 .bind("ty"),253 Context);254 const auto *Constructor = selectFirst<FunctionDecl>("target", Results);255 const auto *Rec = selectFirst<RecordDecl>("struct", Results);256 const auto QTy = *selectFirst<QualType>("ty", Results);257 ASSERT_THAT(Constructor, NotNull());258 ASSERT_THAT(Rec, NotNull());259 ASSERT_FALSE(QTy.isNull());260 261 auto Fields = Rec->fields();262 FieldDecl *XDecl = nullptr;263 for (FieldDecl *Field : Fields) {264 if (Field->getNameAsString() == "X") {265 XDecl = Field;266 break;267 }268 }269 ASSERT_THAT(XDecl, NotNull());270 271 // Verify that the `X` field of `S` is populated when analyzing the272 // constructor, even though it is not referenced directly in the constructor.273 Environment Env(DAContext, *Constructor);274 Env.initialize();275 auto &Loc = cast<RecordStorageLocation>(Env.createObject(QTy));276 EXPECT_THAT(getFieldValue(&Loc, *XDecl, Env), NotNull());277}278 279TEST_F(EnvironmentTest, InitGlobalVarsFieldFun) {280 using namespace ast_matchers;281 282 std::string Code = R"cc(283 struct S { int Bar; };284 S Global = {0};285 int Target () { return Global.Bar; }286 )cc";287 288 auto Unit =289 tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"});290 auto &Context = Unit->getASTContext();291 292 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);293 294 auto Results =295 match(decl(anyOf(varDecl(hasName("Global")).bind("global"),296 functionDecl(hasName("Target")).bind("target"))),297 Context);298 const auto *Fun = selectFirst<FunctionDecl>("target", Results);299 const auto *GlobalDecl = selectFirst<VarDecl>("global", Results);300 ASSERT_THAT(Fun, NotNull());301 ASSERT_THAT(GlobalDecl, NotNull());302 303 ASSERT_TRUE(GlobalDecl->getType()->isStructureType());304 auto GlobalFields = GlobalDecl->getType()->getAsRecordDecl()->fields();305 306 FieldDecl *BarDecl = nullptr;307 for (FieldDecl *Field : GlobalFields) {308 if (Field->getNameAsString() == "Bar") {309 BarDecl = Field;310 break;311 }312 FAIL() << "Unexpected field: " << Field->getNameAsString();313 }314 ASSERT_THAT(BarDecl, NotNull());315 316 // Verify the global variable is populated when we analyze `Target`.317 Environment Env(DAContext, *Fun);318 Env.initialize();319 const auto *GlobalLoc =320 cast<RecordStorageLocation>(Env.getStorageLocation(*GlobalDecl));321 auto *BarVal = getFieldValue(GlobalLoc, *BarDecl, Env);322 EXPECT_TRUE(isa<IntegerValue>(BarVal));323}324 325TEST_F(EnvironmentTest, InitGlobalVarsConstructor) {326 using namespace ast_matchers;327 328 std::string Code = R"cc(329 int Global = 0;330 struct Target {331 Target() : Field(Global) {}332 int Field;333 };334 )cc";335 336 auto Unit =337 tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"});338 auto &Context = Unit->getASTContext();339 340 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);341 342 auto Results =343 match(decl(anyOf(344 varDecl(hasName("Global")).bind("global"),345 cxxConstructorDecl(ofClass(hasName("Target"))).bind("target"))),346 Context);347 const auto *Ctor = selectFirst<CXXConstructorDecl>("target", Results);348 const auto *Var = selectFirst<VarDecl>("global", Results);349 ASSERT_TRUE(Ctor != nullptr);350 ASSERT_THAT(Var, NotNull());351 352 // Verify the global variable is populated when we analyze `Target`.353 Environment Env(DAContext, *Ctor);354 Env.initialize();355 EXPECT_THAT(Env.getValue(*Var), NotNull());356}357 358// Pointers to Members are a tricky case of accessor calls, complicated further359// when using templates where the pointer to the member is a template argument.360// This is a repro of a failure case seen in the wild.361TEST_F(EnvironmentTest,362 ModelMemberForAccessorUsingMethodPointerThroughTemplate) {363 using namespace ast_matchers;364 365 std::string Code = R"cc(366 struct S {367 int accessor() {return member;}368 369 int member = 0;370 };371 372 template <auto method>373 int Target(S* S) {374 return (S->*method)();375 }376 377 // We want to analyze the instantiation of Target for the accessor.378 int Instantiator () {S S; return Target<&S::accessor>(&S); }379 )cc";380 381 auto Unit =382 // C++17 for the simplifying use of auto in the template declaration.383 tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++17"});384 auto &Context = Unit->getASTContext();385 386 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);387 388 auto Results = match(389 decl(anyOf(functionDecl(hasName("Target"), isTemplateInstantiation())390 .bind("target"),391 fieldDecl(hasName("member")).bind("member"),392 recordDecl(hasName("S")).bind("struct"))),393 Context);394 const auto *Fun = selectFirst<FunctionDecl>("target", Results);395 const auto *Struct = selectFirst<RecordDecl>("struct", Results);396 const auto *Member = selectFirst<FieldDecl>("member", Results);397 ASSERT_THAT(Fun, NotNull());398 ASSERT_THAT(Struct, NotNull());399 ASSERT_THAT(Member, NotNull());400 401 // Verify that `member` is modeled for `S` when we analyze402 // `Target<&S::accessor>`.403 Environment Env(DAContext, *Fun);404 Env.initialize();405 EXPECT_THAT(DAContext.getModeledFields(Context.getCanonicalTagType(Struct)),406 Contains(Member));407}408 409// This is a repro of a failure case seen in the wild.410TEST_F(EnvironmentTest, CXXDefaultInitExprResultObjIsWrappedExprResultObj) {411 using namespace ast_matchers;412 413 std::string Code = R"cc(414 struct Inner {};415 416 struct S {417 S() {}418 419 Inner i = {};420 };421 )cc";422 423 auto Unit =424 tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"});425 auto &Context = Unit->getASTContext();426 427 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);428 429 auto Results =430 match(cxxConstructorDecl(431 hasAnyConstructorInitializer(cxxCtorInitializer(432 withInitializer(expr().bind("default_init_expr")))))433 .bind("ctor"),434 Context);435 const auto *Constructor = selectFirst<CXXConstructorDecl>("ctor", Results);436 const auto *DefaultInit =437 selectFirst<CXXDefaultInitExpr>("default_init_expr", Results);438 439 Environment Env(DAContext, *Constructor);440 Env.initialize();441 EXPECT_EQ(&Env.getResultObjectLocation(*DefaultInit),442 &Env.getResultObjectLocation(*DefaultInit->getExpr()));443}444 445// This test verifies the behavior of `getResultObjectLocation()` in446// scenarios involving inherited constructors.447// Since the specific AST node of interest `CXXConstructorDecl` is implicitly448// generated, we cannot annotate any statements inside of it as we do in tests449// within TransferTest. Thus, the only way to get the right `Environment` is by450// explicitly initializing it as we do in tests within EnvironmentTest.451// This is why this test is not inside TransferTest, where most of the tests for452// `getResultObjectLocation()` are located.453TEST_F(EnvironmentTest, ResultObjectLocationForInheritedCtorInitExpr) {454 using namespace ast_matchers;455 456 std::string Code = R"(457 struct Base {458 Base(int b) {}459 };460 struct Derived : Base {461 using Base::Base;462 };463 464 Derived d = Derived(0);465 )";466 467 auto Unit =468 tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++20"});469 auto &Context = Unit->getASTContext();470 471 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);472 473 auto Results =474 match(cxxConstructorDecl(475 hasAnyConstructorInitializer(cxxCtorInitializer(476 withInitializer(expr().bind("inherited_ctor_init_expr")))))477 .bind("ctor"),478 Context);479 const auto *Constructor = selectFirst<CXXConstructorDecl>("ctor", Results);480 const auto *InheritedCtorInit = selectFirst<CXXInheritedCtorInitExpr>(481 "inherited_ctor_init_expr", Results);482 483 EXPECT_EQ(InheritedCtorInit->child_begin(), InheritedCtorInit->child_end());484 485 Environment Env(DAContext, *Constructor);486 Env.initialize();487 488 RecordStorageLocation &Loc = Env.getResultObjectLocation(*InheritedCtorInit);489 EXPECT_NE(&Loc, nullptr);490 491 EXPECT_EQ(&Loc, Env.getThisPointeeStorageLocation());492}493 494TEST_F(EnvironmentTest, Stmt) {495 using namespace ast_matchers;496 497 std::string Code = R"cc(498 struct S { int i; };499 void foo() {500 S AnS = S{1};501 }502 )cc";503 auto Unit =504 tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"});505 auto &Context = Unit->getASTContext();506 507 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);508 509 auto *DeclStatement = const_cast<DeclStmt *>(selectFirst<DeclStmt>(510 "d", match(declStmt(hasSingleDecl(varDecl(hasName("AnS")))).bind("d"),511 Context)));512 ASSERT_THAT(DeclStatement, NotNull());513 auto *Init = (cast<VarDecl>(*DeclStatement->decl_begin()))->getInit();514 ASSERT_THAT(Init, NotNull());515 516 // Verify that we can retrieve the result object location for the initializer517 // expression when we analyze the DeclStmt for `AnS`.518 Environment Env(DAContext, *DeclStatement);519 // Don't crash when initializing.520 Env.initialize();521 // And don't crash when retrieving the result object location.522 Env.getResultObjectLocation(*Init);523}524 525// This is a crash repro.526TEST_F(EnvironmentTest, LambdaCapturingThisInFieldInitializer) {527 using namespace ast_matchers;528 std::string Code = R"cc(529 struct S {530 int f{[this]() { return 1; }()};531 };532 )cc";533 534 auto Unit =535 tooling::buildASTFromCodeWithArgs(Code, {"-fsyntax-only", "-std=c++11"});536 auto &Context = Unit->getASTContext();537 538 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);539 540 auto *LambdaCallOperator = selectFirst<CXXMethodDecl>(541 "method", match(cxxMethodDecl(hasName("operator()"),542 ofClass(cxxRecordDecl(isLambda())))543 .bind("method"),544 Context));545 546 Environment Env(DAContext, *LambdaCallOperator);547 // Don't crash when initializing.548 Env.initialize();549 // And initialize the captured `this` pointee.550 ASSERT_NE(nullptr, Env.getThisPointeeStorageLocation());551}552 553} // namespace554