https://github.com/steakhal created https://github.com/llvm/llvm-project/pull/100405
CPP-5269 >From 95c0cd7f5d1c1a5c87707a5a63141dc3008191ed Mon Sep 17 00:00:00 2001 From: Balazs Benics <benicsbal...@gmail.com> Date: Wed, 24 Jul 2024 17:11:54 +0200 Subject: [PATCH] [analyzer] Don't invalidate super region when a std object ctor runs CPP-5269 --- clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 19 ++++ clang/test/Analysis/call-invalidation.cpp | 115 ++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 0e317ec765ec0..eba224b8ec01c 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -923,12 +923,31 @@ SVal AnyCXXConstructorCall::getCXXThisVal() const { return UnknownVal(); } +static bool isWithinStdNamespace(const Decl *D) { + const DeclContext *DC = D->getDeclContext(); + while (DC) { + if (const auto *NS = dyn_cast<NamespaceDecl>(DC); + NS && NS->isStdNamespace()) + return true; + DC = DC->getParent(); + } + return false; +} + void AnyCXXConstructorCall::getExtraInvalidatedValues(ValueList &Values, RegionAndSymbolInvalidationTraits *ETraits) const { SVal V = getCXXThisVal(); if (SymbolRef Sym = V.getAsSymbol(true)) ETraits->setTrait(Sym, RegionAndSymbolInvalidationTraits::TK_SuppressEscape); + + // Standard classes don't reinterpret-cast and modify super regions. + const bool IsStdClassCtor = isWithinStdNamespace(getDecl()); + if (const MemRegion *Obj = V.getAsRegion(); Obj && IsStdClassCtor) { + ETraits->setTrait( + Obj, RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion); + } + Values.push_back(V); } diff --git a/clang/test/Analysis/call-invalidation.cpp b/clang/test/Analysis/call-invalidation.cpp index 727217f228b05..fb2b892b31a1f 100644 --- a/clang/test/Analysis/call-invalidation.cpp +++ b/clang/test/Analysis/call-invalidation.cpp @@ -1,5 +1,6 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify -analyzer-config eagerly-assume=false %s +template <class T> void clang_analyzer_dump(T); void clang_analyzer_eval(bool); void usePointer(int * const *); @@ -165,3 +166,117 @@ void testMixedConstNonConstCalls() { useFirstNonConstSecondConst(&(s2.x), &(s2.y)); clang_analyzer_eval(s2.y == 1); // expected-warning{{UNKNOWN}} } + +namespace std { +class Opaque { +public: + Opaque(); + int nested_member; +}; +} // namespace std + +struct StdWrappingOpaque { + std::Opaque o; // first member + int uninit; +}; +struct StdWrappingOpaqueSwapped { + int uninit; // first member + std::Opaque o; +}; + +int testStdCtorDoesNotInvalidateParentObject() { + StdWrappingOpaque obj; + int x = obj.o.nested_member; // no-garbage: std::Opaque::ctor might initialized this + int y = obj.uninit; // FIXME: We should have a garbage read here. Read the details. + // As the first member ("obj.o") is invalidated, a conjured default binding is bound + // to the offset 0 within cluster "obj", and this masks every uninitialized fields + // that follows. We need a better store with extents to fix this. + return x + y; +} + +int testStdCtorDoesNotInvalidateParentObjectSwapped() { + StdWrappingOpaqueSwapped obj; + int x = obj.o.nested_member; // no-garbage: std::Opaque::ctor might initialized this + int y = obj.uninit; // expected-warning {{Assigned value is garbage or undefined}} + return x + y; +} + +class UserProvidedOpaque { +public: + UserProvidedOpaque(); // might reinterpret_cast(this) + int nested_member; +}; + +struct WrappingUserProvidedOpaque { + UserProvidedOpaque o; // first member + int uninit; +}; +struct WrappingUserProvidedOpaqueSwapped { + int uninit; // first member + UserProvidedOpaque o; +}; + +int testUserProvidedCtorInvalidatesParentObject() { + WrappingUserProvidedOpaque obj; + int x = obj.o.nested_member; // no-garbage: UserProvidedOpaque::ctor might initialized this + int y = obj.uninit; // no-garbage: UserProvidedOpaque::ctor might reinterpret_cast(this) and write to the "uninit" member. + return x + y; +} + +int testUserProvidedCtorInvalidatesParentObjectSwapped() { + WrappingUserProvidedOpaqueSwapped obj; + int x = obj.o.nested_member; // no-garbage: same as above + int y = obj.uninit; // no-garbage: same as above + return x + y; +} + +struct WrappingStdWrappingOpaqueOuterInits { + int first = 1; + std::Opaque second; + int third = 3; + WrappingStdWrappingOpaqueOuterInits() { + clang_analyzer_dump(first); // expected-warning {{1 S32b}} + clang_analyzer_dump(second.nested_member); // expected-warning {{derived_}} + clang_analyzer_dump(third); // expected-warning {{3 S32b}} + } +}; + +struct WrappingUserProvidedOpaqueOuterInits { + int first = 1; // Potentially overwritten by UserProvidedOpaque::ctor + UserProvidedOpaque second; // Invalidates the object so far. + int third = 3; // Happens after UserProvidedOpaque::ctor, thus preserved! + WrappingUserProvidedOpaqueOuterInits() { + clang_analyzer_dump(first); // expected-warning {{derived_}} + clang_analyzer_dump(second.nested_member); // expected-warning {{derived_}} + clang_analyzer_dump(third); // expected-warning {{3 S32b}} + } +}; + +extern "C++" { +namespace std { +inline namespace v1 { +namespace custom_ranges { +struct Fancy { +struct iterator { +struct Opaque { + Opaque(); + int nested_member; +}; // struct Opaque +}; // struct iterator +}; // struct Fancy +} // namespace custom_ranges +} // namespace v1 +} // namespace std +} // extern "C++" + +struct StdWrappingFancyOpaque { + int uninit; + std::custom_ranges::Fancy::iterator::Opaque o; +}; + +int testNestedStdNamespacesAndRecords() { + StdWrappingFancyOpaque obj; + int x = obj.o.nested_member; // no-garbage: ctor + int y = obj.uninit; // expected-warning {{Assigned value is garbage or undefined}} + return x + y; +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits