Skip to content

Commit 3ad31e1

Browse files
authored
[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)
1 parent 4447e25 commit 3ad31e1

File tree

12 files changed

+111
-75
lines changed

12 files changed

+111
-75
lines changed

clang/docs/ReleaseNotes.rst

+3
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@ Resolutions to C++ Defect Reports
268268
- Clang now requires a template argument list after a template keyword.
269269
(`CWG96: Syntactic disambiguation using the template keyword <https://cplusplus.github.io/CWG/issues/96.html>`_).
270270

271+
- Clang now considers ``noexcept(typeid(expr))`` more carefully, instead of always assuming that ``std::bad_typeid`` can be thrown.
272+
(`CWG2191: Incorrect result for noexcept(typeid(v)) <https://cplusplus.github.io/CWG/issues/2191.html>`_).
273+
271274
C Language Changes
272275
------------------
273276

clang/include/clang/AST/ExprCXX.h

+4
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,10 @@ class CXXTypeidExpr : public Expr {
919919
reinterpret_cast<Stmt **>(&const_cast<CXXTypeidExpr *>(this)->Operand);
920920
return const_child_range(begin, begin + 1);
921921
}
922+
923+
/// Whether this is of a form like "typeid(*ptr)" that can throw a
924+
/// std::bad_typeid if a pointer is a null pointer ([expr.typeid]p2)
925+
bool hasNullCheck() const;
922926
};
923927

924928
/// A member reference to an MSPropertyDecl.

clang/lib/AST/Expr.cpp

+12-4
Original file line numberDiff line numberDiff line change
@@ -3769,10 +3769,18 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
37693769
break;
37703770
}
37713771

3772-
case CXXTypeidExprClass:
3773-
// typeid might throw if its subexpression is potentially-evaluated, so has
3774-
// side-effects in that case whether or not its subexpression does.
3775-
return cast<CXXTypeidExpr>(this)->isPotentiallyEvaluated();
3772+
case CXXTypeidExprClass: {
3773+
const auto *TE = cast<CXXTypeidExpr>(this);
3774+
if (!TE->isPotentiallyEvaluated())
3775+
return false;
3776+
3777+
// If this type id expression can throw because of a null pointer, that is a
3778+
// side-effect independent of if the operand has a side-effect
3779+
if (IncludePossibleEffects && TE->hasNullCheck())
3780+
return true;
3781+
3782+
break;
3783+
}
37763784

37773785
case CXXConstructExprClass:
37783786
case CXXTemporaryObjectExprClass: {

clang/lib/AST/ExprCXX.cpp

+47
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,53 @@ QualType CXXTypeidExpr::getTypeOperand(ASTContext &Context) const {
166166
Operand.get<TypeSourceInfo *>()->getType().getNonReferenceType(), Quals);
167167
}
168168

169+
static bool isGLValueFromPointerDeref(const Expr *E) {
170+
E = E->IgnoreParens();
171+
172+
if (const auto *CE = dyn_cast<CastExpr>(E)) {
173+
if (!CE->getSubExpr()->isGLValue())
174+
return false;
175+
return isGLValueFromPointerDeref(CE->getSubExpr());
176+
}
177+
178+
if (const auto *OVE = dyn_cast<OpaqueValueExpr>(E))
179+
return isGLValueFromPointerDeref(OVE->getSourceExpr());
180+
181+
if (const auto *BO = dyn_cast<BinaryOperator>(E))
182+
if (BO->getOpcode() == BO_Comma)
183+
return isGLValueFromPointerDeref(BO->getRHS());
184+
185+
if (const auto *ACO = dyn_cast<AbstractConditionalOperator>(E))
186+
return isGLValueFromPointerDeref(ACO->getTrueExpr()) ||
187+
isGLValueFromPointerDeref(ACO->getFalseExpr());
188+
189+
// C++11 [expr.sub]p1:
190+
// The expression E1[E2] is identical (by definition) to *((E1)+(E2))
191+
if (isa<ArraySubscriptExpr>(E))
192+
return true;
193+
194+
if (const auto *UO = dyn_cast<UnaryOperator>(E))
195+
if (UO->getOpcode() == UO_Deref)
196+
return true;
197+
198+
return false;
199+
}
200+
201+
bool CXXTypeidExpr::hasNullCheck() const {
202+
if (!isPotentiallyEvaluated())
203+
return false;
204+
205+
// C++ [expr.typeid]p2:
206+
// If the glvalue expression is obtained by applying the unary * operator to
207+
// a pointer and the pointer is a null pointer value, the typeid expression
208+
// throws the std::bad_typeid exception.
209+
//
210+
// However, this paragraph's intent is not clear. We choose a very generous
211+
// interpretation which implores us to consider comma operators, conditional
212+
// operators, parentheses and other such constructs.
213+
return isGLValueFromPointerDeref(getExprOperand());
214+
}
215+
169216
QualType CXXUuidofExpr::getTypeOperand(ASTContext &Context) const {
170217
assert(isTypeOperand() && "Cannot call getTypeOperand for __uuidof(expr)");
171218
Qualifiers Quals;

clang/lib/CodeGen/CGCXXABI.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,7 @@ class CGCXXABI {
274274
getAddrOfCXXCatchHandlerType(QualType Ty, QualType CatchHandlerType) = 0;
275275
virtual CatchTypeInfo getCatchAllTypeInfo();
276276

277-
virtual bool shouldTypeidBeNullChecked(bool IsDeref,
278-
QualType SrcRecordTy) = 0;
277+
virtual bool shouldTypeidBeNullChecked(QualType SrcRecordTy) = 0;
279278
virtual void EmitBadTypeidCall(CodeGenFunction &CGF) = 0;
280279
virtual llvm::Value *EmitTypeid(CodeGenFunction &CGF, QualType SrcRecordTy,
281280
Address ThisPtr,

clang/lib/CodeGen/CGExprCXX.cpp

+9-44
Original file line numberDiff line numberDiff line change
@@ -2143,40 +2143,9 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) {
21432143
}
21442144
}
21452145

2146-
static bool isGLValueFromPointerDeref(const Expr *E) {
2147-
E = E->IgnoreParens();
2148-
2149-
if (const auto *CE = dyn_cast<CastExpr>(E)) {
2150-
if (!CE->getSubExpr()->isGLValue())
2151-
return false;
2152-
return isGLValueFromPointerDeref(CE->getSubExpr());
2153-
}
2154-
2155-
if (const auto *OVE = dyn_cast<OpaqueValueExpr>(E))
2156-
return isGLValueFromPointerDeref(OVE->getSourceExpr());
2157-
2158-
if (const auto *BO = dyn_cast<BinaryOperator>(E))
2159-
if (BO->getOpcode() == BO_Comma)
2160-
return isGLValueFromPointerDeref(BO->getRHS());
2161-
2162-
if (const auto *ACO = dyn_cast<AbstractConditionalOperator>(E))
2163-
return isGLValueFromPointerDeref(ACO->getTrueExpr()) ||
2164-
isGLValueFromPointerDeref(ACO->getFalseExpr());
2165-
2166-
// C++11 [expr.sub]p1:
2167-
// The expression E1[E2] is identical (by definition) to *((E1)+(E2))
2168-
if (isa<ArraySubscriptExpr>(E))
2169-
return true;
2170-
2171-
if (const auto *UO = dyn_cast<UnaryOperator>(E))
2172-
if (UO->getOpcode() == UO_Deref)
2173-
return true;
2174-
2175-
return false;
2176-
}
2177-
21782146
static llvm::Value *EmitTypeidFromVTable(CodeGenFunction &CGF, const Expr *E,
2179-
llvm::Type *StdTypeInfoPtrTy) {
2147+
llvm::Type *StdTypeInfoPtrTy,
2148+
bool HasNullCheck) {
21802149
// Get the vtable pointer.
21812150
Address ThisPtr = CGF.EmitLValue(E).getAddress();
21822151

@@ -2189,16 +2158,11 @@ static llvm::Value *EmitTypeidFromVTable(CodeGenFunction &CGF, const Expr *E,
21892158
CGF.EmitTypeCheck(CodeGenFunction::TCK_DynamicOperation, E->getExprLoc(),
21902159
ThisPtr, SrcRecordTy);
21912160

2192-
// C++ [expr.typeid]p2:
2193-
// If the glvalue expression is obtained by applying the unary * operator to
2194-
// a pointer and the pointer is a null pointer value, the typeid expression
2195-
// throws the std::bad_typeid exception.
2196-
//
2197-
// However, this paragraph's intent is not clear. We choose a very generous
2198-
// interpretation which implores us to consider comma operators, conditional
2199-
// operators, parentheses and other such constructs.
2200-
if (CGF.CGM.getCXXABI().shouldTypeidBeNullChecked(
2201-
isGLValueFromPointerDeref(E), SrcRecordTy)) {
2161+
// Whether we need an explicit null pointer check. For example, with the
2162+
// Microsoft ABI, if this is a call to __RTtypeid, the null pointer check and
2163+
// exception throw is inside the __RTtypeid(nullptr) call
2164+
if (HasNullCheck &&
2165+
CGF.CGM.getCXXABI().shouldTypeidBeNullChecked(SrcRecordTy)) {
22022166
llvm::BasicBlock *BadTypeidBlock =
22032167
CGF.createBasicBlock("typeid.bad_typeid");
22042168
llvm::BasicBlock *EndBlock = CGF.createBasicBlock("typeid.end");
@@ -2244,7 +2208,8 @@ llvm::Value *CodeGenFunction::EmitCXXTypeidExpr(const CXXTypeidExpr *E) {
22442208
// type) to which the glvalue refers.
22452209
// If the operand is already most derived object, no need to look up vtable.
22462210
if (E->isPotentiallyEvaluated() && !E->isMostDerived(getContext()))
2247-
return EmitTypeidFromVTable(*this, E->getExprOperand(), PtrTy);
2211+
return EmitTypeidFromVTable(*this, E->getExprOperand(), PtrTy,
2212+
E->hasNullCheck());
22482213

22492214
QualType OperandTy = E->getExprOperand()->getType();
22502215
return MaybeASCast(CGM.GetAddrOfRTTIDescriptor(OperandTy));

clang/lib/CodeGen/ItaniumCXXABI.cpp

+3-4
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
178178
return CatchTypeInfo{getAddrOfRTTIDescriptor(Ty), 0};
179179
}
180180

181-
bool shouldTypeidBeNullChecked(bool IsDeref, QualType SrcRecordTy) override;
181+
bool shouldTypeidBeNullChecked(QualType SrcRecordTy) override;
182182
void EmitBadTypeidCall(CodeGenFunction &CGF) override;
183183
llvm::Value *EmitTypeid(CodeGenFunction &CGF, QualType SrcRecordTy,
184184
Address ThisPtr,
@@ -1419,9 +1419,8 @@ static llvm::FunctionCallee getBadTypeidFn(CodeGenFunction &CGF) {
14191419
return CGF.CGM.CreateRuntimeFunction(FTy, "__cxa_bad_typeid");
14201420
}
14211421

1422-
bool ItaniumCXXABI::shouldTypeidBeNullChecked(bool IsDeref,
1423-
QualType SrcRecordTy) {
1424-
return IsDeref;
1422+
bool ItaniumCXXABI::shouldTypeidBeNullChecked(QualType SrcRecordTy) {
1423+
return true;
14251424
}
14261425

14271426
void ItaniumCXXABI::EmitBadTypeidCall(CodeGenFunction &CGF) {

clang/lib/CodeGen/MicrosoftCXXABI.cpp

+3-5
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ class MicrosoftCXXABI : public CGCXXABI {
144144
return CatchTypeInfo{nullptr, 0x40};
145145
}
146146

147-
bool shouldTypeidBeNullChecked(bool IsDeref, QualType SrcRecordTy) override;
147+
bool shouldTypeidBeNullChecked(QualType SrcRecordTy) override;
148148
void EmitBadTypeidCall(CodeGenFunction &CGF) override;
149149
llvm::Value *EmitTypeid(CodeGenFunction &CGF, QualType SrcRecordTy,
150150
Address ThisPtr,
@@ -977,11 +977,9 @@ MicrosoftCXXABI::performBaseAdjustment(CodeGenFunction &CGF, Address Value,
977977
PolymorphicBase);
978978
}
979979

980-
bool MicrosoftCXXABI::shouldTypeidBeNullChecked(bool IsDeref,
981-
QualType SrcRecordTy) {
980+
bool MicrosoftCXXABI::shouldTypeidBeNullChecked(QualType SrcRecordTy) {
982981
const CXXRecordDecl *SrcDecl = SrcRecordTy->getAsCXXRecordDecl();
983-
return IsDeref &&
984-
!getContext().getASTRecordLayout(SrcDecl).hasExtendableVFPtr();
982+
return !getContext().getASTRecordLayout(SrcDecl).hasExtendableVFPtr();
985983
}
986984

987985
static llvm::CallBase *emitRTtypeidCall(CodeGenFunction &CGF,

clang/lib/Sema/SemaExceptionSpec.cpp

+5-15
Original file line numberDiff line numberDiff line change
@@ -1114,21 +1114,10 @@ static CanThrowResult canTypeidThrow(Sema &S, const CXXTypeidExpr *DC) {
11141114
if (DC->isTypeOperand())
11151115
return CT_Cannot;
11161116

1117-
Expr *Op = DC->getExprOperand();
1118-
if (Op->isTypeDependent())
1117+
if (DC->isValueDependent())
11191118
return CT_Dependent;
11201119

1121-
const RecordType *RT = Op->getType()->getAs<RecordType>();
1122-
if (!RT)
1123-
return CT_Cannot;
1124-
1125-
if (!cast<CXXRecordDecl>(RT->getDecl())->isPolymorphic())
1126-
return CT_Cannot;
1127-
1128-
if (Op->Classify(S.Context).isPRValue())
1129-
return CT_Cannot;
1130-
1131-
return CT_Can;
1120+
return DC->hasNullCheck() ? CT_Can : CT_Cannot;
11321121
}
11331122

11341123
CanThrowResult Sema::canThrow(const Stmt *S) {
@@ -1157,8 +1146,9 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
11571146
}
11581147

11591148
case Expr::CXXTypeidExprClass:
1160-
// - a potentially evaluated typeid expression applied to a glvalue
1161-
// expression whose type is a polymorphic class type
1149+
// - a potentially evaluated typeid expression applied to a (possibly
1150+
// parenthesized) built-in unary * operator applied to a pointer to a
1151+
// polymorphic class type
11621152
return canTypeidThrow(*this, cast<CXXTypeidExpr>(S));
11631153

11641154
// - a potentially evaluated call to a function, member function, function

clang/test/CXX/drs/cwg21xx.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
// cxx98-error@-1 {{variadic macros are a C99 feature}}
1212
#endif
1313

14+
namespace std {
15+
struct type_info;
16+
}
17+
1418
namespace cwg2100 { // cwg2100: 12
1519
template<const int *P, bool = true> struct X {};
1620
template<typename T> struct A {
@@ -231,6 +235,15 @@ static_assert(!__is_trivially_assignable(NonConstCopy &&, NonConstCopy &&), "");
231235
#endif
232236
} // namespace cwg2171
233237

238+
namespace cwg2191 { // cwg2191: 19
239+
#if __cplusplus >= 201103L
240+
struct B { virtual void f() { } };
241+
struct D : B { } d;
242+
static_assert(noexcept(typeid(d)), "");
243+
static_assert(!noexcept(typeid(*static_cast<D*>(nullptr))), "");
244+
#endif
245+
} // namespace cwg2191
246+
234247
namespace cwg2180 { // cwg2180: yes
235248
class A {
236249
A &operator=(const A &); // #cwg2180-A-copy

clang/test/SemaCXX/warn-unused-value.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,16 @@ void f() {
102102
Bad b;
103103
(void)typeid(b.f()); // expected-warning {{expression with side effects will be evaluated despite being used as an operand to 'typeid'}}
104104

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

clang/www/cxx_dr_status.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -12954,7 +12954,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
1295412954
<td><a href="https://cplusplus.github.io/CWG/issues/2191.html">2191</a></td>
1295512955
<td>C++17</td>
1295612956
<td>Incorrect result for <TT>noexcept(typeid(v))</TT></td>
12957-
<td class="unknown" align="center">Unknown</td>
12957+
<td class="unreleased" align="center">Clang 19</td>
1295812958
</tr>
1295912959
<tr class="open" id="2192">
1296012960
<td><a href="https://cplusplus.github.io/CWG/issues/2192.html">2192</a></td>

0 commit comments

Comments
 (0)