Files
clang-p2996/clang/test/Analysis/Checkers/WebKit/ref-cntbl-base-virtual-dtor-templates.cpp
Ryosuke Niwa faef8b4aa2 [webkit.RefCntblBaseVirtualDtor] Allow CRTP classes without a virtual destructor. (#92837)
Exempt CRTP (Curiously Recurring Template Pattern) classes with a delete
operation acting on "this" pointer with an appropriate cast from the
requirement that a ref-countable superclass must have a virtual
destructor.

To do this, this PR introduces new DerefFuncDeleteExprVisitor, which
looks for a delete operation with an explicit cast to the derived class
in a base class.

This PR also changes the checker so that we only check a given class's
immediate base class instead of all ancestor base classes in the class
hierarchy. This is sufficient because the checker will eventually see
the definition for every class in the class hierarchy and transitively
proves every ref-counted base class has a virtual destructor or deref
function which casts this pointer back to the derived class before
deleting. Without this change, we would keep traversing the same list of
base classes whenever we encounter a new subclass, which is wholly
unnecessary.

It's possible for DerefFuncDeleteExprVisitor to come to a conclusoin that
there isn't enough information to determine whether a given templated
superclass invokes delete operation on a subclass when the template
isn't fully specialized for the subclass. In this case, we return
std::nullopt in HasSpecializedDelete, and visitCXXRecordDecl will skip
this declaration. This is okay because the checker will eventually see a
concreate fully specialized class definition if it ever gets
instantiated.
2024-05-25 09:32:48 -07:00

399 lines
9.7 KiB
C++

// RUN: %clang_analyze_cc1 -analyzer-checker=webkit.RefCntblBaseVirtualDtor -verify %s
struct RefCntblBase {
void ref() {}
void deref() {}
};
template<class T>
struct DerivedClassTmpl1 : T { };
// expected-warning@-1{{Struct 'RefCntblBase' is used as a base of struct 'DerivedClassTmpl1<RefCntblBase>' but doesn't have virtual destructor}}
DerivedClassTmpl1<RefCntblBase> a;
void foo(DerivedClassTmpl1<RefCntblBase>& obj) { obj.deref(); }
template<class T>
struct DerivedClassTmpl2 : T { };
// expected-warning@-1{{Struct 'RefCntblBase' is used as a base of struct 'DerivedClassTmpl2<RefCntblBase>' but doesn't have virtual destructor}}
template<class T> int foo(T) { DerivedClassTmpl2<T> f; return 42; }
int b = foo(RefCntblBase{});
template<class T>
struct DerivedClassTmpl3 : T { };
// expected-warning@-1{{Struct 'RefCntblBase' is used as a base of struct 'DerivedClassTmpl3<RefCntblBase>' but doesn't have virtual destructor}}
typedef DerivedClassTmpl3<RefCntblBase> Foo;
Foo c;
namespace WTF {
class RefCountedBase {
public:
void ref() const { ++count; }
protected:
bool derefBase() const
{
return !--count;
}
private:
mutable unsigned count;
};
template <typename T>
class RefCounted : public RefCountedBase {
public:
void deref() const {
if (derefBase())
delete const_cast<T*>(static_cast<const T*>(this));
}
protected:
RefCounted() { }
};
template <typename X, typename T>
class ExoticRefCounted : public RefCountedBase {
public:
void deref() const {
if (derefBase())
delete (const_cast<T*>(static_cast<const T*>(this)));
}
};
template <typename X, typename T>
class BadBase : RefCountedBase {
public:
void deref() const {
if (derefBase())
delete (const_cast<X*>(static_cast<const X*>(this)));
}
};
template <typename T>
class FancyDeref {
public:
void ref() const
{
++refCount;
}
void deref() const
{
--refCount;
if (refCount)
return;
auto deleteThis = [this] {
delete static_cast<const T*>(this);
};
deleteThis();
}
private:
mutable unsigned refCount { 0 };
};
namespace Detail {
template<typename Out, typename... In>
class CallableWrapperBase {
public:
virtual ~CallableWrapperBase() { }
virtual Out call(In...) = 0;
};
template<typename, typename, typename...> class CallableWrapper;
template<typename CallableType, typename Out, typename... In>
class CallableWrapper : public CallableWrapperBase<Out, In...> {
public:
explicit CallableWrapper(CallableType&& callable)
: m_callable(WTFMove(callable)) { }
CallableWrapper(const CallableWrapper&) = delete;
CallableWrapper& operator=(const CallableWrapper&) = delete;
Out call(In... in) final { return m_callable(in...); }
private:
CallableType m_callable;
};
} // namespace Detail
template<typename> class Function;
template <typename Out, typename... In>
class Function<Out(In...)> {
public:
using Impl = Detail::CallableWrapperBase<Out, In...>;
Function() = default;
template<typename CallableType>
Function(CallableType&& callable)
: m_callableWrapper(new Detail::CallableWrapper<CallableType, Out, In...>>(callable)) { }
template<typename FunctionType>
Function(FunctionType f)
: m_callableWrapper(new Detail::CallableWrapper<FunctionType, Out, In...>>(f)) { }
~Function() {
}
Out operator()(In... in) const {
ASSERT(m_callableWrapper);
return m_callableWrapper->call(in...);
}
explicit operator bool() const { return !!m_callableWrapper; }
private:
Impl* m_callableWrapper;
};
void ensureOnMainThread(const Function<void()>&& function);
enum class DestructionThread { Any, MainThread };
template <typename T, DestructionThread destructionThread = DestructionThread::Any>
class FancyDeref2 {
public:
void ref() const
{
++refCount;
}
void deref() const
{
--refCount;
if (refCount)
return;
const_cast<FancyDeref2<T, destructionThread>*>(this)->destroy();
}
private:
void destroy() {
delete static_cast<T*>(this);
}
mutable unsigned refCount { 0 };
};
template <typename S>
class DerivedFancyDeref2 : public FancyDeref2<S> {
};
template <typename T>
class BadFancyDeref {
public:
void ref() const
{
++refCount;
}
void deref() const
{
--refCount;
if (refCount)
return;
auto deleteThis = [this] {
delete static_cast<const T*>(this);
};
delete this;
}
private:
mutable unsigned refCount { 0 };
};
template <typename T>
class ThreadSafeRefCounted {
public:
void ref() const { ++refCount; }
void deref() const {
if (!--refCount)
delete const_cast<T*>(static_cast<const T*>(this));
}
private:
mutable unsigned refCount { 0 };
};
template <typename T>
class ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr {
public:
void ref() const { ++refCount; }
void deref() const {
if (!--refCount)
delete const_cast<T*>(static_cast<const T*>(this));
}
private:
mutable unsigned refCount { 0 };
};
} // namespace WTF
class DerivedClass4 : public WTF::RefCounted<DerivedClass4> { };
class DerivedClass4b : public WTF::ExoticRefCounted<int, DerivedClass4b> { };
class DerivedClass4cSub;
class DerivedClass4c : public WTF::BadBase<DerivedClass4cSub, DerivedClass4c> { };
// expected-warning@-1{{Class 'WTF::BadBase<DerivedClass4cSub, DerivedClass4c>' is used as a base of class 'DerivedClass4c' but doesn't have virtual destructor}}
class DerivedClass4cSub : public DerivedClass4c { };
void UseDerivedClass4c(DerivedClass4c &obj) { obj.deref(); }
class DerivedClass4d : public WTF::RefCounted<DerivedClass4d> {
public:
virtual ~DerivedClass4d() { }
};
class DerivedClass4dSub : public DerivedClass4d { };
class DerivedClass5 : public DerivedClass4 { };
// expected-warning@-1{{Class 'DerivedClass4' is used as a base of class 'DerivedClass5' but doesn't have virtual destructor}}
void UseDerivedClass5(DerivedClass5 &obj) { obj.deref(); }
class DerivedClass6 : public WTF::ThreadSafeRefCounted<DerivedClass6> { };
void UseDerivedClass6(DerivedClass6 &obj) { obj.deref(); }
class DerivedClass7 : public DerivedClass6 { };
// expected-warning@-1{{Class 'DerivedClass6' is used as a base of class 'DerivedClass7' but doesn't have virtual destructor}}
void UseDerivedClass7(DerivedClass7 &obj) { obj.deref(); }
class DerivedClass8 : public WTF::ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr<DerivedClass8> { };
void UseDerivedClass8(DerivedClass8 &obj) { obj.deref(); }
class DerivedClass9 : public DerivedClass8 { };
// expected-warning@-1{{Class 'DerivedClass8' is used as a base of class 'DerivedClass9' but doesn't have virtual destructor}}
void UseDerivedClass9(DerivedClass9 &obj) { obj.deref(); }
class DerivedClass10 : public WTF::FancyDeref<DerivedClass10> { };
void UseDerivedClass10(DerivedClass10 &obj) { obj.deref(); }
class DerivedClass10b : public WTF::DerivedFancyDeref2<DerivedClass10b> { };
void UseDerivedClass10b(DerivedClass10b &obj) { obj.deref(); }
class DerivedClass10c : public WTF::BadFancyDeref<DerivedClass10c> { };
// expected-warning@-1{{Class 'WTF::BadFancyDeref<DerivedClass10c>' is used as a base of class 'DerivedClass10c' but doesn't have virtual destructor}}
void UseDerivedClass10c(DerivedClass10c &obj) { obj.deref(); }
class BaseClass1 {
public:
void ref() const { ++refCount; }
void deref() const;
private:
enum class Type { Base, Derived } type { Type::Base };
mutable unsigned refCount { 0 };
};
class DerivedClass11 : public BaseClass1 { };
void BaseClass1::deref() const
{
--refCount;
if (refCount)
return;
switch (type) {
case Type::Base:
delete const_cast<BaseClass1*>(this);
break;
case Type::Derived:
delete const_cast<DerivedClass11*>(static_cast<const DerivedClass11*>(this));
break;
}
}
void UseDerivedClass11(DerivedClass11& obj) { obj.deref(); }
class BaseClass2;
static void deleteBase2(BaseClass2*);
class BaseClass2 {
public:
void ref() const { ++refCount; }
void deref() const
{
if (!--refCount)
deleteBase2(const_cast<BaseClass2*>(this));
}
virtual bool isDerived() { return false; }
private:
mutable unsigned refCount { 0 };
};
class DerivedClass12 : public BaseClass2 {
bool isDerived() final { return true; }
};
void UseDerivedClass11(DerivedClass12& obj) { obj.deref(); }
void deleteBase2(BaseClass2* obj) {
if (obj->isDerived())
delete static_cast<DerivedClass12*>(obj);
else
delete obj;
}
class BaseClass3 {
public:
void ref() const { ++refCount; }
void deref() const
{
if (!--refCount)
const_cast<BaseClass3*>(this)->destory();
}
virtual bool isDerived() { return false; }
private:
void destory();
mutable unsigned refCount { 0 };
};
class DerivedClass13 : public BaseClass3 {
bool isDerived() final { return true; }
};
void UseDerivedClass11(DerivedClass13& obj) { obj.deref(); }
void BaseClass3::destory() {
if (isDerived())
delete static_cast<DerivedClass13*>(this);
else
delete this;
}
class RecursiveBaseClass {
public:
void ref() const {
if (otherObject)
otherObject->ref();
else
++refCount;
}
void deref() const {
if (otherObject)
otherObject->deref();
else {
--refCount;
if (refCount)
return;
delete this;
}
}
private:
RecursiveBaseClass* otherObject { nullptr };
mutable unsigned refCount { 0 };
};
class RecursiveDerivedClass : public RecursiveBaseClass { };
// expected-warning@-1{{Class 'RecursiveBaseClass' is used as a base of class 'RecursiveDerivedClass' but doesn't have virtual destructor}}
class DerivedClass14 : public WTF::RefCounted<DerivedClass14> {
public:
virtual ~DerivedClass14() { }
};
void UseDerivedClass14(DerivedClass14& obj) { obj.deref(); }
class DerivedClass15 : public DerivedClass14 { };
void UseDerivedClass15(DerivedClass15& obj) { obj.deref(); }