[Clang] Introduce CXXTypeidExpr::hasNullCheck (#95718)

Used to implement CWG2191 where `typeid` for a polymorphic glvalue only
becomes potentially-throwing if the `typeid` operand was already
potentially throwing or a `nullptr` check was inserted:
https://cplusplus.github.io/CWG/issues/2191.html

Also change `Expr::hasSideEffects` for `CXXTypeidExpr` to check the
operand for side-effects instead of always reporting that there are
side-effects

Remove `IsDeref` parameter of `CGCXXABI::shouldTypeidBeNullChecked`
because it should never return `true` if `!IsDeref` (we shouldn't add a
null check that wasn't there in the first place)
This commit is contained in:
Mital Ashok 2024-06-17 18:31:54 +01:00 committed by GitHub
parent 4447e255a9
commit 3ad31e12cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 111 additions and 75 deletions

View File

@ -268,6 +268,9 @@ Resolutions to C++ Defect Reports
- Clang now requires a template argument list after a template keyword.
(`CWG96: Syntactic disambiguation using the template keyword <https://cplusplus.github.io/CWG/issues/96.html>`_).
- Clang now considers ``noexcept(typeid(expr))`` more carefully, instead of always assuming that ``std::bad_typeid`` can be thrown.
(`CWG2191: Incorrect result for noexcept(typeid(v)) <https://cplusplus.github.io/CWG/issues/2191.html>`_).
C Language Changes
------------------

View File

@ -919,6 +919,10 @@ public:
reinterpret_cast<Stmt **>(&const_cast<CXXTypeidExpr *>(this)->Operand);
return const_child_range(begin, begin + 1);
}
/// Whether this is of a form like "typeid(*ptr)" that can throw a
/// std::bad_typeid if a pointer is a null pointer ([expr.typeid]p2)
bool hasNullCheck() const;
};
/// A member reference to an MSPropertyDecl.

View File

@ -3769,10 +3769,18 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
break;
}
case CXXTypeidExprClass:
// typeid might throw if its subexpression is potentially-evaluated, so has
// side-effects in that case whether or not its subexpression does.
return cast<CXXTypeidExpr>(this)->isPotentiallyEvaluated();
case CXXTypeidExprClass: {
const auto *TE = cast<CXXTypeidExpr>(this);
if (!TE->isPotentiallyEvaluated())
return false;
// If this type id expression can throw because of a null pointer, that is a
// side-effect independent of if the operand has a side-effect
if (IncludePossibleEffects && TE->hasNullCheck())
return true;
break;
}
case CXXConstructExprClass:
case CXXTemporaryObjectExprClass: {

View File

@ -166,6 +166,53 @@ QualType CXXTypeidExpr::getTypeOperand(ASTContext &Context) const {
Operand.get<TypeSourceInfo *>()->getType().getNonReferenceType(), Quals);
}
static bool isGLValueFromPointerDeref(const Expr *E) {
E = E->IgnoreParens();
if (const auto *CE = dyn_cast<CastExpr>(E)) {
if (!CE->getSubExpr()->isGLValue())
return false;
return isGLValueFromPointerDeref(CE->getSubExpr());
}
if (const auto *OVE = dyn_cast<OpaqueValueExpr>(E))
return isGLValueFromPointerDeref(OVE->getSourceExpr());
if (const auto *BO = dyn_cast<BinaryOperator>(E))
if (BO->getOpcode() == BO_Comma)
return isGLValueFromPointerDeref(BO->getRHS());
if (const auto *ACO = dyn_cast<AbstractConditionalOperator>(E))
return isGLValueFromPointerDeref(ACO->getTrueExpr()) ||
isGLValueFromPointerDeref(ACO->getFalseExpr());
// C++11 [expr.sub]p1:
// The expression E1[E2] is identical (by definition) to *((E1)+(E2))
if (isa<ArraySubscriptExpr>(E))
return true;
if (const auto *UO = dyn_cast<UnaryOperator>(E))
if (UO->getOpcode() == UO_Deref)
return true;
return false;
}
bool CXXTypeidExpr::hasNullCheck() const {
if (!isPotentiallyEvaluated())
return false;
// C++ [expr.typeid]p2:
// If the glvalue expression is obtained by applying the unary * operator to
// a pointer and the pointer is a null pointer value, the typeid expression
// throws the std::bad_typeid exception.
//
// However, this paragraph's intent is not clear. We choose a very generous
// interpretation which implores us to consider comma operators, conditional
// operators, parentheses and other such constructs.
return isGLValueFromPointerDeref(getExprOperand());
}
QualType CXXUuidofExpr::getTypeOperand(ASTContext &Context) const {
assert(isTypeOperand() && "Cannot call getTypeOperand for __uuidof(expr)");
Qualifiers Quals;

View File

@ -274,8 +274,7 @@ public:
getAddrOfCXXCatchHandlerType(QualType Ty, QualType CatchHandlerType) = 0;
virtual CatchTypeInfo getCatchAllTypeInfo();
virtual bool shouldTypeidBeNullChecked(bool IsDeref,
QualType SrcRecordTy) = 0;
virtual bool shouldTypeidBeNullChecked(QualType SrcRecordTy) = 0;
virtual void EmitBadTypeidCall(CodeGenFunction &CGF) = 0;
virtual llvm::Value *EmitTypeid(CodeGenFunction &CGF, QualType SrcRecordTy,
Address ThisPtr,

View File

@ -2143,40 +2143,9 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) {
}
}
static bool isGLValueFromPointerDeref(const Expr *E) {
E = E->IgnoreParens();
if (const auto *CE = dyn_cast<CastExpr>(E)) {
if (!CE->getSubExpr()->isGLValue())
return false;
return isGLValueFromPointerDeref(CE->getSubExpr());
}
if (const auto *OVE = dyn_cast<OpaqueValueExpr>(E))
return isGLValueFromPointerDeref(OVE->getSourceExpr());
if (const auto *BO = dyn_cast<BinaryOperator>(E))
if (BO->getOpcode() == BO_Comma)
return isGLValueFromPointerDeref(BO->getRHS());
if (const auto *ACO = dyn_cast<AbstractConditionalOperator>(E))
return isGLValueFromPointerDeref(ACO->getTrueExpr()) ||
isGLValueFromPointerDeref(ACO->getFalseExpr());
// C++11 [expr.sub]p1:
// The expression E1[E2] is identical (by definition) to *((E1)+(E2))
if (isa<ArraySubscriptExpr>(E))
return true;
if (const auto *UO = dyn_cast<UnaryOperator>(E))
if (UO->getOpcode() == UO_Deref)
return true;
return false;
}
static llvm::Value *EmitTypeidFromVTable(CodeGenFunction &CGF, const Expr *E,
llvm::Type *StdTypeInfoPtrTy) {
llvm::Type *StdTypeInfoPtrTy,
bool HasNullCheck) {
// Get the vtable pointer.
Address ThisPtr = CGF.EmitLValue(E).getAddress();
@ -2189,16 +2158,11 @@ static llvm::Value *EmitTypeidFromVTable(CodeGenFunction &CGF, const Expr *E,
CGF.EmitTypeCheck(CodeGenFunction::TCK_DynamicOperation, E->getExprLoc(),
ThisPtr, SrcRecordTy);
// C++ [expr.typeid]p2:
// If the glvalue expression is obtained by applying the unary * operator to
// a pointer and the pointer is a null pointer value, the typeid expression
// throws the std::bad_typeid exception.
//
// However, this paragraph's intent is not clear. We choose a very generous
// interpretation which implores us to consider comma operators, conditional
// operators, parentheses and other such constructs.
if (CGF.CGM.getCXXABI().shouldTypeidBeNullChecked(
isGLValueFromPointerDeref(E), SrcRecordTy)) {
// Whether we need an explicit null pointer check. For example, with the
// Microsoft ABI, if this is a call to __RTtypeid, the null pointer check and
// exception throw is inside the __RTtypeid(nullptr) call
if (HasNullCheck &&
CGF.CGM.getCXXABI().shouldTypeidBeNullChecked(SrcRecordTy)) {
llvm::BasicBlock *BadTypeidBlock =
CGF.createBasicBlock("typeid.bad_typeid");
llvm::BasicBlock *EndBlock = CGF.createBasicBlock("typeid.end");
@ -2244,7 +2208,8 @@ llvm::Value *CodeGenFunction::EmitCXXTypeidExpr(const CXXTypeidExpr *E) {
// type) to which the glvalue refers.
// If the operand is already most derived object, no need to look up vtable.
if (E->isPotentiallyEvaluated() && !E->isMostDerived(getContext()))
return EmitTypeidFromVTable(*this, E->getExprOperand(), PtrTy);
return EmitTypeidFromVTable(*this, E->getExprOperand(), PtrTy,
E->hasNullCheck());
QualType OperandTy = E->getExprOperand()->getType();
return MaybeASCast(CGM.GetAddrOfRTTIDescriptor(OperandTy));

View File

@ -178,7 +178,7 @@ public:
return CatchTypeInfo{getAddrOfRTTIDescriptor(Ty), 0};
}
bool shouldTypeidBeNullChecked(bool IsDeref, QualType SrcRecordTy) override;
bool shouldTypeidBeNullChecked(QualType SrcRecordTy) override;
void EmitBadTypeidCall(CodeGenFunction &CGF) override;
llvm::Value *EmitTypeid(CodeGenFunction &CGF, QualType SrcRecordTy,
Address ThisPtr,
@ -1419,9 +1419,8 @@ static llvm::FunctionCallee getBadTypeidFn(CodeGenFunction &CGF) {
return CGF.CGM.CreateRuntimeFunction(FTy, "__cxa_bad_typeid");
}
bool ItaniumCXXABI::shouldTypeidBeNullChecked(bool IsDeref,
QualType SrcRecordTy) {
return IsDeref;
bool ItaniumCXXABI::shouldTypeidBeNullChecked(QualType SrcRecordTy) {
return true;
}
void ItaniumCXXABI::EmitBadTypeidCall(CodeGenFunction &CGF) {

View File

@ -144,7 +144,7 @@ public:
return CatchTypeInfo{nullptr, 0x40};
}
bool shouldTypeidBeNullChecked(bool IsDeref, QualType SrcRecordTy) override;
bool shouldTypeidBeNullChecked(QualType SrcRecordTy) override;
void EmitBadTypeidCall(CodeGenFunction &CGF) override;
llvm::Value *EmitTypeid(CodeGenFunction &CGF, QualType SrcRecordTy,
Address ThisPtr,
@ -977,11 +977,9 @@ MicrosoftCXXABI::performBaseAdjustment(CodeGenFunction &CGF, Address Value,
PolymorphicBase);
}
bool MicrosoftCXXABI::shouldTypeidBeNullChecked(bool IsDeref,
QualType SrcRecordTy) {
bool MicrosoftCXXABI::shouldTypeidBeNullChecked(QualType SrcRecordTy) {
const CXXRecordDecl *SrcDecl = SrcRecordTy->getAsCXXRecordDecl();
return IsDeref &&
!getContext().getASTRecordLayout(SrcDecl).hasExtendableVFPtr();
return !getContext().getASTRecordLayout(SrcDecl).hasExtendableVFPtr();
}
static llvm::CallBase *emitRTtypeidCall(CodeGenFunction &CGF,

View File

@ -1114,21 +1114,10 @@ static CanThrowResult canTypeidThrow(Sema &S, const CXXTypeidExpr *DC) {
if (DC->isTypeOperand())
return CT_Cannot;
Expr *Op = DC->getExprOperand();
if (Op->isTypeDependent())
if (DC->isValueDependent())
return CT_Dependent;
const RecordType *RT = Op->getType()->getAs<RecordType>();
if (!RT)
return CT_Cannot;
if (!cast<CXXRecordDecl>(RT->getDecl())->isPolymorphic())
return CT_Cannot;
if (Op->Classify(S.Context).isPRValue())
return CT_Cannot;
return CT_Can;
return DC->hasNullCheck() ? CT_Can : CT_Cannot;
}
CanThrowResult Sema::canThrow(const Stmt *S) {
@ -1157,8 +1146,9 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
}
case Expr::CXXTypeidExprClass:
// - a potentially evaluated typeid expression applied to a glvalue
// expression whose type is a polymorphic class type
// - a potentially evaluated typeid expression applied to a (possibly
// parenthesized) built-in unary * operator applied to a pointer to a
// polymorphic class type
return canTypeidThrow(*this, cast<CXXTypeidExpr>(S));
// - a potentially evaluated call to a function, member function, function

View File

@ -11,6 +11,10 @@
// cxx98-error@-1 {{variadic macros are a C99 feature}}
#endif
namespace std {
struct type_info;
}
namespace cwg2100 { // cwg2100: 12
template<const int *P, bool = true> struct X {};
template<typename T> struct A {
@ -231,6 +235,15 @@ static_assert(!__is_trivially_assignable(NonConstCopy &&, NonConstCopy &&), "");
#endif
} // namespace cwg2171
namespace cwg2191 { // cwg2191: 19
#if __cplusplus >= 201103L
struct B { virtual void f() { } };
struct D : B { } d;
static_assert(noexcept(typeid(d)), "");
static_assert(!noexcept(typeid(*static_cast<D*>(nullptr))), "");
#endif
} // namespace cwg2191
namespace cwg2180 { // cwg2180: yes
class A {
A &operator=(const A &); // #cwg2180-A-copy

View File

@ -102,6 +102,16 @@ void f() {
Bad b;
(void)typeid(b.f()); // expected-warning {{expression with side effects will be evaluated despite being used as an operand to 'typeid'}}
extern Bad * pb;
// This typeid can throw but that is not a side-effect that we care about
// warning for since this is idiomatic code
(void)typeid(*pb);
(void)sizeof(typeid(*pb));
(void)typeid(*++pb); // expected-warning {{expression with side effects will be evaluated despite being used as an operand to 'typeid'}}
(void)sizeof(typeid(*++pb)); // expected-warning {{expression with side effects has no effect in an unevaluated context}}
// FIXME: we should not warn about this in an unevaluated context
// expected-warning@-2 {{expression with side effects will be evaluated despite being used as an operand to 'typeid'}}
// A dereference of a volatile pointer is a side effecting operation, however
// since it is idiomatic code, and the alternatives induce higher maintenance
// costs, it is allowed.

View File

@ -12954,7 +12954,7 @@ and <I>POD class</I></td>
<td><a href="https://cplusplus.github.io/CWG/issues/2191.html">2191</a></td>
<td>C++17</td>
<td>Incorrect result for <TT>noexcept(typeid(v))</TT></td>
<td class="unknown" align="center">Unknown</td>
<td class="unreleased" align="center">Clang 19</td>
</tr>
<tr class="open" id="2192">
<td><a href="https://cplusplus.github.io/CWG/issues/2192.html">2192</a></td>