[clang][bytecode] Add a scope to function calls (#140441)

We need a place to destroy the temporaries created for call arguments.
This commit is contained in:
Timm Baeder 2025-05-19 11:49:27 +02:00 committed by GitHub
parent 1b711b27d2
commit bca39f4e8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 121 additions and 73 deletions

View File

@ -45,10 +45,6 @@ public:
Ctx->InitStack.push_back(InitLink::Decl(VD)); Ctx->InitStack.push_back(InitLink::Decl(VD));
} }
void addExtended(const Scope::Local &Local) override {
return this->addLocal(Local);
}
~DeclScope() { ~DeclScope() {
this->Ctx->InitializingDecl = OldInitializingDecl; this->Ctx->InitializingDecl = OldInitializingDecl;
this->Ctx->InitStack.pop_back(); this->Ctx->InitStack.pop_back();
@ -2021,6 +2017,45 @@ bool Compiler<Emitter>::visitArrayElemInit(unsigned ElemIndex, const Expr *Init,
return this->emitFinishInitPop(Init); return this->emitFinishInitPop(Init);
} }
template <class Emitter>
bool Compiler<Emitter>::visitCallArgs(ArrayRef<const Expr *> Args,
const FunctionDecl *FuncDecl) {
assert(VarScope->getKind() == ScopeKind::Call);
llvm::BitVector NonNullArgs = collectNonNullArgs(FuncDecl, Args);
unsigned ArgIndex = 0;
for (const Expr *Arg : Args) {
if (std::optional<PrimType> T = classify(Arg)) {
if (!this->visit(Arg))
return false;
} else {
std::optional<unsigned> LocalIndex = allocateLocal(
Arg, Arg->getType(), /*ExtendingDecl=*/nullptr, ScopeKind::Call);
if (!LocalIndex)
return false;
if (!this->emitGetPtrLocal(*LocalIndex, Arg))
return false;
InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex));
if (!this->visitInitializer(Arg))
return false;
}
if (FuncDecl && NonNullArgs[ArgIndex]) {
PrimType ArgT = classify(Arg).value_or(PT_Ptr);
if (ArgT == PT_Ptr) {
if (!this->emitCheckNonNullArg(ArgT, Arg))
return false;
}
}
++ArgIndex;
}
return true;
}
template <class Emitter> template <class Emitter>
bool Compiler<Emitter>::VisitInitListExpr(const InitListExpr *E) { bool Compiler<Emitter>::VisitInitListExpr(const InitListExpr *E) {
return this->visitInitList(E->inits(), E->getArrayFiller(), E); return this->visitInitList(E->inits(), E->getArrayFiller(), E);
@ -4343,7 +4378,7 @@ bool Compiler<Emitter>::emitConst(const APSInt &Value, const Expr *E) {
template <class Emitter> template <class Emitter>
unsigned Compiler<Emitter>::allocateLocalPrimitive( unsigned Compiler<Emitter>::allocateLocalPrimitive(
DeclTy &&Src, PrimType Ty, bool IsConst, const ValueDecl *ExtendingDecl, DeclTy &&Src, PrimType Ty, bool IsConst, const ValueDecl *ExtendingDecl,
bool IsConstexprUnknown) { ScopeKind SC, bool IsConstexprUnknown) {
// Make sure we don't accidentally register the same decl twice. // Make sure we don't accidentally register the same decl twice.
if (const auto *VD = if (const auto *VD =
dyn_cast_if_present<ValueDecl>(Src.dyn_cast<const Decl *>())) { dyn_cast_if_present<ValueDecl>(Src.dyn_cast<const Decl *>())) {
@ -4364,14 +4399,14 @@ unsigned Compiler<Emitter>::allocateLocalPrimitive(
if (ExtendingDecl) if (ExtendingDecl)
VarScope->addExtended(Local, ExtendingDecl); VarScope->addExtended(Local, ExtendingDecl);
else else
VarScope->add(Local, false); VarScope->addForScopeKind(Local, SC);
return Local.Offset; return Local.Offset;
} }
template <class Emitter> template <class Emitter>
std::optional<unsigned> std::optional<unsigned>
Compiler<Emitter>::allocateLocal(DeclTy &&Src, QualType Ty, Compiler<Emitter>::allocateLocal(DeclTy &&Src, QualType Ty,
const ValueDecl *ExtendingDecl, const ValueDecl *ExtendingDecl, ScopeKind SC,
bool IsConstexprUnknown) { bool IsConstexprUnknown) {
// Make sure we don't accidentally register the same decl twice. // Make sure we don't accidentally register the same decl twice.
if ([[maybe_unused]] const auto *VD = if ([[maybe_unused]] const auto *VD =
@ -4409,7 +4444,7 @@ Compiler<Emitter>::allocateLocal(DeclTy &&Src, QualType Ty,
if (ExtendingDecl) if (ExtendingDecl)
VarScope->addExtended(Local, ExtendingDecl); VarScope->addExtended(Local, ExtendingDecl);
else else
VarScope->add(Local, false); VarScope->addForScopeKind(Local, SC);
return Local.Offset; return Local.Offset;
} }
@ -4676,7 +4711,7 @@ VarCreationState Compiler<Emitter>::visitVarDecl(const VarDecl *VD,
if (VarT) { if (VarT) {
unsigned Offset = this->allocateLocalPrimitive( unsigned Offset = this->allocateLocalPrimitive(
VD, *VarT, VD->getType().isConstQualified(), nullptr, VD, *VarT, VD->getType().isConstQualified(), nullptr,
IsConstexprUnknown); ScopeKind::Block, IsConstexprUnknown);
if (Init) { if (Init) {
// If this is a toplevel declaration, create a scope for the // If this is a toplevel declaration, create a scope for the
// initializer. // initializer.
@ -4692,8 +4727,9 @@ VarCreationState Compiler<Emitter>::visitVarDecl(const VarDecl *VD,
} }
} }
} else { } else {
if (std::optional<unsigned> Offset = this->allocateLocal( if (std::optional<unsigned> Offset =
VD, VD->getType(), nullptr, IsConstexprUnknown)) { this->allocateLocal(VD, VD->getType(), nullptr, ScopeKind::Block,
IsConstexprUnknown)) {
if (!Init) if (!Init)
return true; return true;
@ -4881,26 +4917,28 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
if (FuncDecl) { if (FuncDecl) {
if (unsigned BuiltinID = FuncDecl->getBuiltinID()) if (unsigned BuiltinID = FuncDecl->getBuiltinID())
return VisitBuiltinCallExpr(E, BuiltinID); return VisitBuiltinCallExpr(E, BuiltinID);
}
// Calls to replaceable operator new/operator delete. // Calls to replaceable operator new/operator delete.
if (FuncDecl && if (FuncDecl->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) {
FuncDecl->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) { if (FuncDecl->getDeclName().isAnyOperatorNew()) {
if (FuncDecl->getDeclName().isAnyOperatorNew()) { return VisitBuiltinCallExpr(E, Builtin::BI__builtin_operator_new);
return VisitBuiltinCallExpr(E, Builtin::BI__builtin_operator_new); } else {
} else { assert(FuncDecl->getDeclName().getCXXOverloadedOperator() == OO_Delete);
assert(FuncDecl->getDeclName().getCXXOverloadedOperator() == OO_Delete); return VisitBuiltinCallExpr(E, Builtin::BI__builtin_operator_delete);
return VisitBuiltinCallExpr(E, Builtin::BI__builtin_operator_delete); }
}
// Explicit calls to trivial destructors
if (const auto *DD = dyn_cast<CXXDestructorDecl>(FuncDecl);
DD && DD->isTrivial()) {
const auto *MemberCall = cast<CXXMemberCallExpr>(E);
if (!this->visit(MemberCall->getImplicitObjectArgument()))
return false;
return this->emitCheckDestruction(E) && this->emitPopPtr(E);
} }
} }
// Explicit calls to trivial destructors
if (const auto *DD = dyn_cast_if_present<CXXDestructorDecl>(FuncDecl); BlockScope<Emitter> CallScope(this, ScopeKind::Call);
DD && DD->isTrivial()) {
const auto *MemberCall = cast<CXXMemberCallExpr>(E);
if (!this->visit(MemberCall->getImplicitObjectArgument()))
return false;
return this->emitCheckDestruction(E) && this->emitPopPtr(E);
}
QualType ReturnType = E->getCallReturnType(Ctx.getASTContext()); QualType ReturnType = E->getCallReturnType(Ctx.getASTContext());
std::optional<PrimType> T = classify(ReturnType); std::optional<PrimType> T = classify(ReturnType);
@ -4996,23 +5034,8 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
return false; return false;
} }
llvm::BitVector NonNullArgs = collectNonNullArgs(FuncDecl, Args); if (!this->visitCallArgs(Args, FuncDecl))
// Put arguments on the stack. return false;
unsigned ArgIndex = 0;
for (const auto *Arg : Args) {
if (!this->visit(Arg))
return false;
// If we know the callee already, check the known parametrs for nullability.
if (FuncDecl && NonNullArgs[ArgIndex]) {
PrimType ArgT = classify(Arg).value_or(PT_Ptr);
if (ArgT == PT_Ptr) {
if (!this->emitCheckNonNullArg(ArgT, Arg))
return false;
}
}
++ArgIndex;
}
// Undo the argument reversal we did earlier. // Undo the argument reversal we did earlier.
if (IsAssignmentOperatorCall) { if (IsAssignmentOperatorCall) {
@ -5088,9 +5111,9 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
// Cleanup for discarded return values. // Cleanup for discarded return values.
if (DiscardResult && !ReturnType->isVoidType() && T) if (DiscardResult && !ReturnType->isVoidType() && T)
return this->emitPop(*T, E); return this->emitPop(*T, E) && CallScope.destroyLocals();
return true; return CallScope.destroyLocals();
} }
template <class Emitter> template <class Emitter>

View File

@ -102,6 +102,8 @@ struct VarCreationState {
bool notCreated() const { return !S; } bool notCreated() const { return !S; }
}; };
enum class ScopeKind { Call, Block };
/// Compilation context for expressions. /// Compilation context for expressions.
template <class Emitter> template <class Emitter>
class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
@ -305,17 +307,19 @@ protected:
const Expr *E); const Expr *E);
bool visitArrayElemInit(unsigned ElemIndex, const Expr *Init, bool visitArrayElemInit(unsigned ElemIndex, const Expr *Init,
std::optional<PrimType> InitT); std::optional<PrimType> InitT);
bool visitCallArgs(ArrayRef<const Expr *> Args, const FunctionDecl *FuncDecl);
/// Creates a local primitive value. /// Creates a local primitive value.
unsigned allocateLocalPrimitive(DeclTy &&Decl, PrimType Ty, bool IsConst, unsigned allocateLocalPrimitive(DeclTy &&Decl, PrimType Ty, bool IsConst,
const ValueDecl *ExtendingDecl = nullptr, const ValueDecl *ExtendingDecl = nullptr,
ScopeKind SC = ScopeKind::Block,
bool IsConstexprUnknown = false); bool IsConstexprUnknown = false);
/// Allocates a space storing a local given its type. /// Allocates a space storing a local given its type.
std::optional<unsigned> std::optional<unsigned>
allocateLocal(DeclTy &&Decl, QualType Ty = QualType(), allocateLocal(DeclTy &&Decl, QualType Ty = QualType(),
const ValueDecl *ExtendingDecl = nullptr, const ValueDecl *ExtendingDecl = nullptr,
bool IsConstexprUnknown = false); ScopeKind = ScopeKind::Block, bool IsConstexprUnknown = false);
std::optional<unsigned> allocateTemporary(const Expr *E); std::optional<unsigned> allocateTemporary(const Expr *E);
private: private:
@ -451,28 +455,16 @@ extern template class Compiler<EvalEmitter>;
/// Scope chain managing the variable lifetimes. /// Scope chain managing the variable lifetimes.
template <class Emitter> class VariableScope { template <class Emitter> class VariableScope {
public: public:
VariableScope(Compiler<Emitter> *Ctx, const ValueDecl *VD) VariableScope(Compiler<Emitter> *Ctx, const ValueDecl *VD,
: Ctx(Ctx), Parent(Ctx->VarScope), ValDecl(VD) { ScopeKind Kind = ScopeKind::Block)
: Ctx(Ctx), Parent(Ctx->VarScope), ValDecl(VD), Kind(Kind) {
Ctx->VarScope = this; Ctx->VarScope = this;
} }
virtual ~VariableScope() { Ctx->VarScope = this->Parent; } virtual ~VariableScope() { Ctx->VarScope = this->Parent; }
void add(const Scope::Local &Local, bool IsExtended) {
if (IsExtended)
this->addExtended(Local);
else
this->addLocal(Local);
}
virtual void addLocal(const Scope::Local &Local) { virtual void addLocal(const Scope::Local &Local) {
if (this->Parent) llvm_unreachable("Shouldn't be called");
this->Parent->addLocal(Local);
}
virtual void addExtended(const Scope::Local &Local) {
if (this->Parent)
this->Parent->addExtended(Local);
} }
void addExtended(const Scope::Local &Local, const ValueDecl *ExtendingDecl) { void addExtended(const Scope::Local &Local, const ValueDecl *ExtendingDecl) {
@ -496,10 +488,28 @@ public:
this->addLocal(Local); this->addLocal(Local);
} }
/// Like addExtended, but adds to the nearest scope of the given kind.
void addForScopeKind(const Scope::Local &Local, ScopeKind Kind) {
VariableScope *P = this;
while (P) {
if (P->Kind == Kind) {
P->addLocal(Local);
return;
}
P = P->Parent;
if (!P)
break;
}
// Add to this scope.
this->addLocal(Local);
}
virtual void emitDestruction() {} virtual void emitDestruction() {}
virtual bool emitDestructors(const Expr *E = nullptr) { return true; } virtual bool emitDestructors(const Expr *E = nullptr) { return true; }
virtual bool destroyLocals(const Expr *E = nullptr) { return true; } virtual bool destroyLocals(const Expr *E = nullptr) { return true; }
VariableScope *getParent() const { return Parent; } VariableScope *getParent() const { return Parent; }
ScopeKind getKind() const { return Kind; }
protected: protected:
/// Compiler instance. /// Compiler instance.
@ -507,12 +517,14 @@ protected:
/// Link to the parent scope. /// Link to the parent scope.
VariableScope *Parent; VariableScope *Parent;
const ValueDecl *ValDecl = nullptr; const ValueDecl *ValDecl = nullptr;
ScopeKind Kind;
}; };
/// Generic scope for local variables. /// Generic scope for local variables.
template <class Emitter> class LocalScope : public VariableScope<Emitter> { template <class Emitter> class LocalScope : public VariableScope<Emitter> {
public: public:
LocalScope(Compiler<Emitter> *Ctx) : VariableScope<Emitter>(Ctx, nullptr) {} LocalScope(Compiler<Emitter> *Ctx, ScopeKind Kind = ScopeKind::Block)
: VariableScope<Emitter>(Ctx, nullptr, Kind) {}
LocalScope(Compiler<Emitter> *Ctx, const ValueDecl *VD) LocalScope(Compiler<Emitter> *Ctx, const ValueDecl *VD)
: VariableScope<Emitter>(Ctx, VD) {} : VariableScope<Emitter>(Ctx, VD) {}
@ -599,16 +611,11 @@ public:
}; };
/// Scope for storage declared in a compound statement. /// Scope for storage declared in a compound statement.
// FIXME: Remove?
template <class Emitter> class BlockScope final : public LocalScope<Emitter> { template <class Emitter> class BlockScope final : public LocalScope<Emitter> {
public: public:
BlockScope(Compiler<Emitter> *Ctx) : LocalScope<Emitter>(Ctx) {} BlockScope(Compiler<Emitter> *Ctx, ScopeKind Kind = ScopeKind::Block)
: LocalScope<Emitter>(Ctx, Kind) {}
void addExtended(const Scope::Local &Local) override {
// If we to this point, just add the variable as a normal local
// variable. It will be destroyed at the end of the block just
// like all others.
this->addLocal(Local);
}
}; };
template <class Emitter> class ArrayIndexScope final { template <class Emitter> class ArrayIndexScope final {

View File

@ -88,3 +88,21 @@ namespace PseudoDtor {
// both-note {{visible outside that expression}} // both-note {{visible outside that expression}}
}; };
} }
/// Diagnostic differences
namespace CallScope {
struct Q {
int n = 0;
constexpr int f() const { return 0; }
};
constexpr Q *out_of_lifetime(Q q) { return &q; } // both-warning {{address of stack}}
constexpr int k3 = out_of_lifetime({})->n; // both-error {{must be initialized by a constant expression}} \
// expected-note {{read of temporary whose lifetime has ended}} \
// expected-note {{temporary created here}} \
// ref-note {{read of object outside its lifetime}}
constexpr int k4 = out_of_lifetime({})->f(); // both-error {{must be initialized by a constant expression}} \
// expected-note {{member call on temporary whose lifetime has ended}} \
// expected-note {{temporary created here}} \
// ref-note {{member call on object outside its lifetime}}
}