[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:
parent
1b711b27d2
commit
bca39f4e8f
@ -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>
|
||||||
|
@ -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 {
|
||||||
|
@ -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}}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user