diff --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp index e13f880896fc0..26e0973490572 100644 --- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp +++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp @@ -288,6 +288,15 @@ static void insertIfFunction(const Decl &D, Funcs.insert(FD); } +static MemberExpr *getMemberForAccessor(const CXXMemberCallExpr &C) { + auto *Body = dyn_cast_or_null(C.getMethodDecl()->getBody()); + if (!Body || Body->size() != 1) + return nullptr; + if (auto *RS = dyn_cast(*Body->body_begin())) + return dyn_cast(RS->getRetValue()->IgnoreParenImpCasts()); + return nullptr; +} + static void getFieldsGlobalsAndFuncs(const Decl &D, FieldSet &Fields, llvm::DenseSet &Vars, @@ -324,6 +333,12 @@ getFieldsGlobalsAndFuncs(const Stmt &S, FieldSet &Fields, } else if (auto *E = dyn_cast(&S)) { insertIfGlobal(*E->getDecl(), Vars); insertIfFunction(*E->getDecl(), Funcs); + } else if (const auto *C = dyn_cast(&S)) { + // If this is a method that returns a member variable but does nothing else, + // model the field of the return value. + if (MemberExpr *E = getMemberForAccessor(*C)) + if (const auto *FD = dyn_cast(E->getMemberDecl())) + Fields.insert(FD); } else if (auto *E = dyn_cast(&S)) { // FIXME: should we be using `E->getFoundDecl()`? const ValueDecl *VD = E->getMemberDecl(); diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp index cdb1bc3cd16ac..14188f5acd5b3 100644 --- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp @@ -1446,6 +1446,55 @@ TEST(TransferTest, BaseClassInitializer) { llvm::Succeeded()); } +TEST(TransferTest, StructModeledFieldsWithAccessor) { + std::string Code = R"( + class S { + int *Ptr; + int *PtrNonConst; + int Int; + int IntWithInc; + int IntNotAccessed; + int IntRef; + public: + int *getPtr() const { return Ptr; } + int *getPtrNonConst() { return PtrNonConst; } + int getInt(int i) const { return Int; } + int getWithInc(int i) { IntWithInc += i; return IntWithInc; } + int getIntNotAccessed() const { return IntNotAccessed; } + int getIntNoDefinition() const; + int &getIntRef() { return IntRef; } + }; + + void target() { + S s; + int *p1 = s.getPtr(); + int *p2 = s.getPtrNonConst(); + int i1 = s.getInt(1); + int i2 = s.getWithInc(1); + int i3 = s.getIntNoDefinition(); + int &iref = s.getIntRef(); + // [[p]] + } + )"; + runDataflow( + Code, + [](const llvm::StringMap> &Results, + ASTContext &ASTCtx) { + const Environment &Env = + getEnvironmentAtAnnotation(Results, "p"); + auto &SLoc = getLocForDecl(ASTCtx, Env, "s"); + std::vector Fields; + for (auto [Field, _] : SLoc.children()) + Fields.push_back(Field); + // Only the fields that have simple accessor methods (that have a + // single statement body that returns the member variable) should be + // modeled. + ASSERT_THAT(Fields, UnorderedElementsAre( + findValueDecl(ASTCtx, "Ptr"), findValueDecl(ASTCtx, "PtrNonConst"), + findValueDecl(ASTCtx, "Int"), findValueDecl(ASTCtx, "IntRef"))); + }); +} + TEST(TransferTest, StructModeledFieldsWithComplicatedInheritance) { std::string Code = R"( struct Base1 {