Skip to content

Commit 4affb2d

Browse files
authored
[analyzer] Use dynamic type when invalidating by a member function call (#111138)
When instantiating "callable<T>", the "class CallableType" nested type will only have a declaration in the copy for the instantiation - because it's not refereed to directly by any other code that would need a complete definition. However, in the past, when conservative eval calling member function, we took the static type of the "this" expr, and looked up the CXXRecordDecl it refereed to to see if it has any mutable members (to decide if it needs to refine invalidation or not). Unfortunately, that query needs a definition, and it asserts otherwise, thus we crashed. To fix this, we should consult the dynamic type of the object, because that will have the definition. I anyways added a check for "hasDefinition" just to be on the safe side. Fixes #77378
1 parent ea3534b commit 4affb2d

File tree

3 files changed

+80
-28
lines changed

3 files changed

+80
-28
lines changed

clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,11 @@ class CXXInstanceCall : public AnyFunctionCall {
690690
ValueList &Values,
691691
RegionAndSymbolInvalidationTraits *ETraits) const override;
692692

693+
/// Returns the decl refered to by the "dynamic type" of the current object
694+
/// and if the class can be a sub-class or not.
695+
/// If the Pointer is null, the flag has no meaning.
696+
std::pair<const CXXRecordDecl *, bool> getDeclForDynamicType() const;
697+
693698
public:
694699
/// Returns the expression representing the implicit 'this' object.
695700
virtual const Expr *getCXXThisExpr() const { return nullptr; }

clang/lib/StaticAnalyzer/Core/CallEvent.cpp

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -711,18 +711,17 @@ void CXXInstanceCall::getExtraInvalidatedValues(
711711
if (const auto *D = cast_or_null<CXXMethodDecl>(getDecl())) {
712712
if (!D->isConst())
713713
return;
714-
// Get the record decl for the class of 'This'. D->getParent() may return a
715-
// base class decl, rather than the class of the instance which needs to be
716-
// checked for mutable fields.
717-
// TODO: We might as well look at the dynamic type of the object.
718-
const Expr *Ex = getCXXThisExpr()->IgnoreParenBaseCasts();
719-
QualType T = Ex->getType();
720-
if (T->isPointerType()) // Arrow or implicit-this syntax?
721-
T = T->getPointeeType();
722-
const CXXRecordDecl *ParentRecord = T->getAsCXXRecordDecl();
723-
assert(ParentRecord);
714+
715+
// Get the record decl for the class of 'This'. D->getParent() may return
716+
// a base class decl, rather than the class of the instance which needs to
717+
// be checked for mutable fields.
718+
const CXXRecordDecl *ParentRecord = getDeclForDynamicType().first;
719+
if (!ParentRecord || !ParentRecord->hasDefinition())
720+
return;
721+
724722
if (ParentRecord->hasMutableFields())
725723
return;
724+
726725
// Preserve CXXThis.
727726
const MemRegion *ThisRegion = ThisVal.getAsRegion();
728727
if (!ThisRegion)
@@ -748,6 +747,21 @@ SVal CXXInstanceCall::getCXXThisVal() const {
748747
return ThisVal;
749748
}
750749

750+
std::pair<const CXXRecordDecl *, bool>
751+
CXXInstanceCall::getDeclForDynamicType() const {
752+
const MemRegion *R = getCXXThisVal().getAsRegion();
753+
if (!R)
754+
return {};
755+
756+
DynamicTypeInfo DynType = getDynamicTypeInfo(getState(), R);
757+
if (!DynType.isValid())
758+
return {};
759+
760+
assert(!DynType.getType()->getPointeeType().isNull());
761+
return {DynType.getType()->getPointeeCXXRecordDecl(),
762+
DynType.canBeASubClass()};
763+
}
764+
751765
RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const {
752766
// Do we have a decl at all?
753767
const Decl *D = getDecl();
@@ -759,21 +773,7 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const {
759773
if (!MD->isVirtual())
760774
return AnyFunctionCall::getRuntimeDefinition();
761775

762-
// Do we know the implicit 'this' object being called?
763-
const MemRegion *R = getCXXThisVal().getAsRegion();
764-
if (!R)
765-
return {};
766-
767-
// Do we know anything about the type of 'this'?
768-
DynamicTypeInfo DynType = getDynamicTypeInfo(getState(), R);
769-
if (!DynType.isValid())
770-
return {};
771-
772-
// Is the type a C++ class? (This is mostly a defensive check.)
773-
QualType RegionType = DynType.getType()->getPointeeType();
774-
assert(!RegionType.isNull() && "DynamicTypeInfo should always be a pointer.");
775-
776-
const CXXRecordDecl *RD = RegionType->getAsCXXRecordDecl();
776+
auto [RD, CanBeSubClass] = getDeclForDynamicType();
777777
if (!RD || !RD->hasDefinition())
778778
return {};
779779

@@ -800,16 +800,17 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const {
800800
// Does the decl that we found have an implementation?
801801
const FunctionDecl *Definition;
802802
if (!Result->hasBody(Definition)) {
803-
if (!DynType.canBeASubClass())
803+
if (!CanBeSubClass)
804804
return AnyFunctionCall::getRuntimeDefinition();
805805
return {};
806806
}
807807

808808
// We found a definition. If we're not sure that this devirtualization is
809809
// actually what will happen at runtime, make sure to provide the region so
810810
// that ExprEngine can decide what to do with it.
811-
if (DynType.canBeASubClass())
812-
return RuntimeDefinition(Definition, R->StripCasts());
811+
if (CanBeSubClass)
812+
return RuntimeDefinition(Definition,
813+
getCXXThisVal().getAsRegion()->StripCasts());
813814
return RuntimeDefinition(Definition, /*DispatchRegion=*/nullptr);
814815
}
815816

clang/test/Analysis/const-method-call.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,49 @@ void checkThatConstMethodCallDoesInvalidateObjectForCircularReferences() {
271271
// FIXME: Should be UNKNOWN.
272272
clang_analyzer_eval(t.x); // expected-warning{{TRUE}}
273273
}
274+
275+
namespace gh77378 {
276+
template <typename Signature> class callable;
277+
278+
template <typename R> class callable<R()> {
279+
struct CallableType {
280+
bool operator()();
281+
};
282+
using MethodType = R (CallableType::*)();
283+
CallableType *object_{nullptr};
284+
MethodType method_;
285+
286+
public:
287+
callable() = default;
288+
289+
template <typename T>
290+
constexpr callable(const T &obj)
291+
: object_{reinterpret_cast<CallableType *>(&const_cast<T &>(obj))},
292+
method_{reinterpret_cast<MethodType>(
293+
static_cast<bool (T::*)() const>(&T::operator()))} {}
294+
295+
constexpr bool const_method() const {
296+
return (object_->*(method_))();
297+
}
298+
299+
callable call() const & {
300+
static const auto L = [this]() {
301+
while (true) {
302+
// This should not crash when conservative eval calling the member function
303+
// when it unwinds the call stack due to exhausting the budget or reaching
304+
// the inlining limit.
305+
if (this->const_method()) {
306+
break;
307+
}
308+
}
309+
return true;
310+
};
311+
return L;
312+
}
313+
};
314+
315+
void entry() {
316+
callable<bool()>{}.call().const_method();
317+
// expected-warning@-1 {{Address of stack memory associated with temporary object of type 'callable<bool ()>' is still referred to by the static variable 'L' upon returning to the caller. This will be a dangling reference}}
318+
}
319+
} // namespace gh77378

0 commit comments

Comments
 (0)