Skip to content

[Clang] Introduce CXXTypeidExpr::hasNullCheck #95718

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------------

Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/AST/ExprCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,10 @@ class CXXTypeidExpr : public Expr {
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.
Expand Down
16 changes: 12 additions & 4 deletions clang/lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
47 changes: 47 additions & 0 deletions clang/lib/AST/ExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 1 addition & 2 deletions clang/lib/CodeGen/CGCXXABI.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,7 @@ class CGCXXABI {
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,
Expand Down
53 changes: 9 additions & 44 deletions clang/lib/CodeGen/CGExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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");
Expand Down Expand Up @@ -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));
Expand Down
7 changes: 3 additions & 4 deletions clang/lib/CodeGen/ItaniumCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
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,
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 3 additions & 5 deletions clang/lib/CodeGen/MicrosoftCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class MicrosoftCXXABI : public CGCXXABI {
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,
Expand Down Expand Up @@ -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,
Expand Down
20 changes: 5 additions & 15 deletions clang/lib/Sema/SemaExceptionSpec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions clang/test/CXX/drs/cwg21xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions clang/test/SemaCXX/warn-unused-value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion clang/www/cxx_dr_status.html
Original file line number Diff line number Diff line change
Expand Up @@ -12954,7 +12954,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<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>
Expand Down
Loading