[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:
parent
4447e255a9
commit
3ad31e12cc
@ -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
|
||||
------------------
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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: {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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));
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user