Timm Baeder 95f0fab7fa
[clang][bytecode] Fix conditional operator scoping wrt. local variables (#169030)
We used to create a scope for the true- and false expression of a
conditional operator. This was done so e.g. in this example:

```c++
  struct A { constexpr A(){}; ~A(); constexpr int get() { return 10; } }; // all-note 2{{declared here}}
  static_assert( (false ? A().get() : 1) == 1);
```

we did _not_ evaluate the true branch at all, meaning we did not
register the local variable for the temporary of type `A`, which means
we also didn't call it destructor.

However, this breaks the case where the temporary needs to outlive the
conditional operator and instead be destroyed via the surrounding
`ExprWithCleanups`:
```
constexpr bool test2(bool b) {
  unsigned long __ms = b ? (const unsigned long &)0 : __ms;
  return true;
}
static_assert(test2(true));
```
Before this patch, we diagnosed this example:
```console
./array.cpp:180:15: error: static assertion expression is not an integral constant expression
  180 | static_assert(test2(true));
      |               ^~~~~~~~~~~
./array.cpp:177:24: note: read of temporary whose lifetime has ended
  177 |   unsigned long __ms = b ? (const unsigned long &)0 : __ms;
      |                        ^
./array.cpp:180:15: note: in call to 'test2(true)'
  180 | static_assert(test2(true));
      |               ^~~~~~~~~~~
./array.cpp:177:51: note: temporary created here
  177 |   unsigned long __ms = b ? (const unsigned long &)0 : __ms;
      |                                                   ^
1 error generated.
```
because the temporary created for the true branch got immediately
destroyed.

The problem in essence is that since the conditional operator doesn't
create a scope at all, we register the local variables for both its
branches, but we later only execute one of them, which means we should
also only destroy the locals of one of the branches.

We fix this similar to clang codgen's `is_active` flag: In the case of a
conditional operator (which is so far the only case where this is
problematic, and this also helps minimize the performance impact of this
change), we make local variables as disabled-by-default and then emit a
`EnableLocal` opcode later, which marks them as enabled. The code
calling their destructors checks whether the local was enabled at all.
2025-11-24 07:34:48 +01:00

201 lines
6.2 KiB
C++

//===--- InterpFrame.h - Call Frame implementation for the VM ---*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Defines the class storing information about stack frames in the interpreter.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_AST_INTERP_INTERPFRAME_H
#define LLVM_CLANG_AST_INTERP_INTERPFRAME_H
#include "Frame.h"
#include "InterpBlock.h"
#include "Pointer.h"
namespace clang {
namespace interp {
class Function;
class InterpState;
class Pointer;
/// Frame storing local variables.
class InterpFrame final : public Frame {
public:
/// The frame of the previous function.
InterpFrame *Caller;
/// Bottom Frame.
InterpFrame(InterpState &S);
/// Creates a new frame for a method call.
InterpFrame(InterpState &S, const Function *Func, InterpFrame *Caller,
CodePtr RetPC, unsigned ArgSize);
/// Creates a new frame with the values that make sense.
/// I.e., the caller is the current frame of S,
/// the This() pointer is the current Pointer on the top of S's stack,
/// and the RVO pointer is before that.
InterpFrame(InterpState &S, const Function *Func, CodePtr RetPC,
unsigned VarArgSize = 0);
/// Destroys the frame, killing all live pointers to stack slots.
~InterpFrame();
static void free(InterpFrame *F) {
if (!F->isBottomFrame())
delete F;
}
/// Invokes the destructors for a scope.
void destroy(unsigned Idx);
void initScope(unsigned Idx);
void destroyScopes();
void enableLocal(unsigned Idx);
bool isLocalEnabled(unsigned Idx) const {
return localInlineDesc(Idx)->IsActive;
}
/// Describes the frame with arguments for diagnostic purposes.
void describe(llvm::raw_ostream &OS) const override;
/// Returns the parent frame object.
Frame *getCaller() const override { return Caller; }
/// Returns the location of the call to the frame.
SourceRange getCallRange() const override;
/// Returns the caller.
const FunctionDecl *getCallee() const override;
/// Returns the current function.
const Function *getFunction() const { return Func; }
/// Returns the offset on the stack at which the frame starts.
size_t getFrameOffset() const { return FrameOffset; }
/// Returns the value of a local variable.
template <typename T> const T &getLocal(unsigned Offset) const {
return localRef<T>(Offset);
}
/// Mutates a local variable.
template <typename T> void setLocal(unsigned Offset, const T &Value) {
localRef<T>(Offset) = Value;
localInlineDesc(Offset)->IsInitialized = true;
}
/// Returns a pointer to a local variables.
Pointer getLocalPointer(unsigned Offset) const;
Block *getLocalBlock(unsigned Offset) const;
/// Returns the value of an argument.
template <typename T> const T &getParam(unsigned Offset) const {
auto Pt = Params.find(Offset);
if (Pt == Params.end())
return stackRef<T>(Offset);
return reinterpret_cast<const Block *>(Pt->second.get())->deref<T>();
}
/// Mutates a local copy of a parameter.
template <typename T> void setParam(unsigned Offset, const T &Value) {
getParamPointer(Offset).deref<T>() = Value;
}
/// Returns a pointer to an argument - lazily creates a block.
Pointer getParamPointer(unsigned Offset);
bool hasThisPointer() const { return Func && Func->hasThisPointer(); }
/// Returns the 'this' pointer.
const Pointer &getThis() const {
assert(hasThisPointer());
assert(!isBottomFrame());
return stackRef<Pointer>(ThisPointerOffset);
}
/// Returns the RVO pointer, if the Function has one.
const Pointer &getRVOPtr() const {
assert(Func);
assert(Func->hasRVO());
assert(!isBottomFrame());
return stackRef<Pointer>(0);
}
/// Checks if the frame is a root frame - return should quit the interpreter.
bool isRoot() const { return !Func; }
/// Returns the PC of the frame's code start.
CodePtr getPC() const { return Func->getCodeBegin(); }
/// Returns the return address of the frame.
CodePtr getRetPC() const { return RetPC; }
/// Map a location to a source.
SourceInfo getSource(CodePtr PC) const;
const Expr *getExpr(CodePtr PC) const;
SourceLocation getLocation(CodePtr PC) const;
SourceRange getRange(CodePtr PC) const;
unsigned getDepth() const { return Depth; }
bool isStdFunction() const;
bool isBottomFrame() const { return !Caller; }
void dump() const { dump(llvm::errs(), 0); }
void dump(llvm::raw_ostream &OS, unsigned Indent = 0) const;
private:
/// Returns an original argument from the stack.
template <typename T> const T &stackRef(unsigned Offset) const {
assert(Args);
return *reinterpret_cast<const T *>(Args - ArgSize + Offset);
}
/// Returns an offset to a local.
template <typename T> T &localRef(unsigned Offset) const {
return localBlock(Offset)->deref<T>();
}
/// Returns a pointer to a local's block.
Block *localBlock(unsigned Offset) const {
return reinterpret_cast<Block *>(Locals.get() + Offset - sizeof(Block));
}
/// Returns the inline descriptor of the local.
InlineDescriptor *localInlineDesc(unsigned Offset) const {
return reinterpret_cast<InlineDescriptor *>(Locals.get() + Offset);
}
private:
/// Reference to the interpreter state.
InterpState &S;
/// Depth of this frame.
unsigned Depth;
/// Reference to the function being executed.
const Function *Func;
/// Offset of the instance pointer. Use with stackRef<>().
unsigned ThisPointerOffset;
/// Return address.
CodePtr RetPC;
/// The size of all the arguments.
const unsigned ArgSize;
/// Pointer to the arguments in the callee's frame.
char *Args = nullptr;
/// Fixed, initial storage for known local variables.
std::unique_ptr<char[]> Locals;
/// Offset on the stack at entry.
const size_t FrameOffset;
/// Mapping from arg offsets to their argument blocks.
llvm::DenseMap<unsigned, std::unique_ptr<char[]>> Params;
};
} // namespace interp
} // namespace clang
#endif