diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h @@ -258,9 +258,9 @@ return CXXBaseListFactory.add(CBS, L); } - const PointerToMemberData *accumCXXBase( - llvm::iterator_range PathRange, - const nonloc::PointerToMember &PTM); + const PointerToMemberData * + accumCXXBase(llvm::iterator_range PathRange, + const nonloc::PointerToMember &PTM, const clang::CastKind &kind); const llvm::APSInt* evalAPSInt(BinaryOperator::Opcode Op, const llvm::APSInt& V1, diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h @@ -514,7 +514,8 @@ /// This SVal is represented by a DeclaratorDecl which can be a member function /// pointer or a member data pointer and a list of CXXBaseSpecifiers. This list /// is required to accumulate the pointer-to-member cast history to figure out -/// the correct subobject field. +/// the correct subobject field. In particular, implicit casts grow this list +/// and explicit casts like static_cast shrink this list. class PointerToMember : public NonLoc { friend class ento::SValBuilder; diff --git a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp --- a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp +++ b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp @@ -21,8 +21,11 @@ #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/ImmutableList.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include #include #include +#include #include using namespace clang; @@ -178,7 +181,11 @@ const PointerToMemberData *BasicValueFactory::accumCXXBase( llvm::iterator_range PathRange, - const nonloc::PointerToMember &PTM) { + const nonloc::PointerToMember &PTM, const CastKind &kind) { + assert((kind == CK_DerivedToBaseMemberPointer || + kind == CK_BaseToDerivedMemberPointer || + kind == CK_ReinterpretMemberPointer) && + "accumCXXBase called with wrong CastKind"); nonloc::PointerToMember::PTMDataType PTMDT = PTM.getPTMData(); const NamedDecl *ND = nullptr; llvm::ImmutableList PathList; @@ -195,8 +202,36 @@ PathList = PTMD->getCXXBaseList(); } - for (const auto &I : llvm::reverse(PathRange)) - PathList = prependCXXBase(I, PathList); + if (kind == CK_DerivedToBaseMemberPointer) { + // Here we pop off matching CXXBaseSpecifiers from PathList. + // Because, CK_DerivedToBaseMemberPointer comes from a static_cast and + // serves to remove a matching implicit cast. Note that static_cast's that + // are no-ops do not count since they produce an empty PathRange, a nice + // thing about Clang AST. + + llvm::SmallVector, 64> BaseList; + // Initially mark all CXXBaseSpecifier to be included + for (const auto &I : PathList) + BaseList.push_back({I, true}); + + for (const auto &I : PathRange) { + auto *it = std::find_if( + BaseList.begin(), BaseList.end(), [&](const auto &B) -> auto { + return I->getType() == B.first->getType(); + }); + it->second = false; + } + auto BaseListFilteredRange = llvm::make_filter_range( + llvm::make_range(BaseList.begin(), BaseList.end()), + [](const auto &I) -> bool { return I.second; }); + + PathList = CXXBaseListFactory.getEmptyList(); + for (const auto &I : BaseListFilteredRange) + PathList = CXXBaseListFactory.add(I.first, PathList); + } else { + for (const auto &I : llvm::reverse(PathRange)) + PathList = prependCXXBase(I, PathList); + } return getPointerToMemberData(ND, PathList); } diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp --- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -526,10 +526,11 @@ case CK_ReinterpretMemberPointer: { SVal V = state->getSVal(Ex, LCtx); if (auto PTMSV = V.getAs()) { - SVal CastedPTMSV = svalBuilder.makePointerToMember( - getBasicVals().accumCXXBase( + SVal CastedPTMSV = + svalBuilder.makePointerToMember(getBasicVals().accumCXXBase( llvm::make_range( - CastE->path_begin(), CastE->path_end()), *PTMSV)); + CastE->path_begin(), CastE->path_end()), + *PTMSV, CastE->getCastKind())); state = state->BindExpr(CastE, LCtx, CastedPTMSV); Bldr.generateNode(CastE, Pred, state); continue; diff --git a/clang/test/Analysis/pointer-to-member.cpp b/clang/test/Analysis/pointer-to-member.cpp --- a/clang/test/Analysis/pointer-to-member.cpp +++ b/clang/test/Analysis/pointer-to-member.cpp @@ -287,3 +287,25 @@ clang_analyzer_eval(a.*ep == 5); // expected-warning{{TRUE}} } } // namespace testAnonymousMember + +namespace testStaticCasting { +// From bug #48739 +struct Grandfather { + int field; +}; + +struct Father : public Grandfather {}; +struct Son : public Father {}; + +void test() { + int Son::*sf = &Son::field; + Grandfather grandpa; + grandpa.field = 10; + int Grandfather::*gpf1 = static_cast(sf); + int Grandfather::*gpf2 = static_cast(static_cast(sf)); + int Grandfather::*gpf3 = static_cast(static_cast(static_cast(sf))); + clang_analyzer_eval(grandpa.*gpf1 == 10); // expected-warning{{TRUE}} + clang_analyzer_eval(grandpa.*gpf2 == 10); // expected-warning{{TRUE}} + clang_analyzer_eval(grandpa.*gpf3 == 10); // expected-warning{{TRUE}} +} +} // namespace testStaticCasting