A class definition is always strict mode

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
This commit is contained in:
Seonghyun Kim 2026-06-17 13:49:07 +09:00
commit a2381044da
6 changed files with 93 additions and 1 deletions

View file

@ -139,6 +139,7 @@ struct GlobalVariableAccessCacheItem;
F(StoreByNameWithAddress) \
F(InitializeDisposable) \
F(FinalizeDisposable) \
F(SetExecutionStateInStrictMode) \
F(FillOpcodeTable) \
F(End)
@ -3139,6 +3140,32 @@ public:
#endif
};
// A class definition is always strict mode code, including its heritage
// expression and computed property names, even when it appears inside
// non-strict code. This opcode temporarily forces the running execution state
// into strict mode while those parts are evaluated.
// When m_enter is true, the current strict flag is saved into m_savedStrictRegisterIndex
// and strict mode is turned on. When m_enter is false, the strict flag is restored
// from m_savedStrictRegisterIndex (so that nested classes restore correctly).
class SetExecutionStateInStrictMode : public ByteCode {
public:
SetExecutionStateInStrictMode(const ByteCodeLOC& loc, bool enter, const size_t savedStrictRegisterIndex)
: ByteCode(Opcode::SetExecutionStateInStrictModeOpcode, loc)
, m_enter(enter)
, m_savedStrictRegisterIndex(savedStrictRegisterIndex)
{
}
bool m_enter;
ByteCodeRegisterIndex m_savedStrictRegisterIndex;
#ifndef NDEBUG
void dump()
{
printf("set execution state in strict mode (%s) <-> r%u", m_enter ? "enter" : "exit", m_savedStrictRegisterIndex);
}
#endif
};
class BindingCalleeIntoRegister : public ByteCode {
public:
BindingCalleeIntoRegister(const ByteCodeLOC& loc)

View file

@ -198,6 +198,8 @@ public:
static void initializeDisposable(ExecutionState& state, Value* registerFile, size_t& programCounter);
static bool finalizeDisposable(ExecutionState& state, Value* registerFile, size_t& programCounter, ByteCodeBlock* byteCodeBlock);
static void setExecutionStateInStrictModeOperation(ExecutionState& state, Value* registerFile, size_t& programCounter);
#if defined(ENABLE_TCO)
static Value tailRecursionSlowCase(ExecutionState& state, TailRecursion* code, ByteCodeBlock* byteCodeBlock, const Value& callee, Value* registerFile);
static Value prepareTailCallOptimization(ExecutionState*& state, TailCall* code, ScriptFunctionObject* callee, ByteCodeBlock*& callerBlock, size_t& programCounter, const Value* registerFile);
@ -1574,6 +1576,14 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock
NEXT_INSTRUCTION();
}
// Rarely-used; see InterpreterSlowPath::setExecutionStateInStrictModeOperation.
DEFINE_OPCODE(SetExecutionStateInStrictMode)
:
{
InterpreterSlowPath::setExecutionStateInStrictModeOperation(*state, registerFile, programCounter);
NEXT_INSTRUCTION();
}
#if defined(ENABLE_TCO)
// Tail recursion
DEFINE_OPCODE(TailRecursion)
@ -4883,6 +4893,23 @@ NEVER_INLINE void InterpreterSlowPath::initializeDisposable(ExecutionState& stat
ADD_PROGRAM_COUNTER(InitializeDisposable);
}
// A class definition is always strict mode code. This rarely-used opcode forces
// the running execution state into strict mode while a class's heritage expression
// and computed property names are evaluated, then restores the previous strict flag.
// Kept out-of-line (and away from the hot opcodes) on purpose to keep it out of the
// interpreter's hot path.
NEVER_INLINE void InterpreterSlowPath::setExecutionStateInStrictModeOperation(ExecutionState& state, Value* registerFile, size_t& programCounter)
{
SetExecutionStateInStrictMode* code = (SetExecutionStateInStrictMode*)programCounter;
if (code->m_enter) {
registerFile[code->m_savedStrictRegisterIndex] = Value(state.inStrictMode());
state.setInStrictMode(true);
} else {
state.setInStrictMode(registerFile[code->m_savedStrictRegisterIndex].asBoolean());
}
ADD_PROGRAM_COUNTER(SetExecutionStateInStrictMode);
}
static void finalizeDisposableAwaitOperation(ExecutionState& state, ByteCodeBlock* byteCodeBlock, DisposableResourceRecord* data,
const Value& awaitValue, size_t programCounter, FinalizeDisposable* code, size_t nextStage)
{

View file

@ -55,6 +55,17 @@ public:
context->m_classInfo.m_prototypeIndex = context->getRegister();
context->m_classInfo.m_superIndex = hasSuper ? context->getRegister() : SIZE_MAX;
// A class definition is always strict mode code. When the surrounding code
// is not strict, force strict mode while the heritage expression and the
// computed property names are evaluated. The save register is allocated last
// (on top of the register stack) so the completion-value register is untouched.
bool needStrictModeSwitch = !codeBlock->m_codeBlock->isStrict();
ByteCodeRegisterIndex savedStrictRegister = REGISTER_LIMIT;
if (needStrictModeSwitch) {
savedStrictRegister = context->getRegister();
codeBlock->pushCode(SetExecutionStateInStrictMode(ByteCodeLOC(m_loc.index), true, savedStrictRegister), context, this->m_loc.index);
}
// add class name property if there is no 'name' static member
if (m_class.classBody()->hasStaticMemberName(codeBlock->m_codeBlock->context()->staticStrings().name)) {
context->m_classInfo.m_name = Optional<AtomicString>();
@ -104,6 +115,11 @@ public:
classIdent->generateStoreByteCode(codeBlock, context, classIndex, true);
ASSERT(!context->m_isLexicallyDeclaredBindingInitialization);
if (needStrictModeSwitch) {
codeBlock->pushCode(SetExecutionStateInStrictMode(ByteCodeLOC(m_loc.index), false, savedStrictRegister), context, this->m_loc.index);
context->giveUpRegister(); // for drop savedStrictRegister (allocated last)
}
if (context->m_classInfo.m_superIndex != SIZE_MAX) {
context->giveUpRegister(); // for drop m_superIndex
}

View file

@ -48,6 +48,16 @@ public:
Node* classIdent = m_class.id();
bool hasSuper = m_class.superClass();
// A class definition is always strict mode code. When the surrounding code
// is not strict, force strict mode while the heritage expression and the
// computed property names are evaluated.
bool needStrictModeSwitch = !codeBlock->m_codeBlock->isStrict();
ByteCodeRegisterIndex savedStrictRegister = REGISTER_LIMIT;
if (needStrictModeSwitch) {
savedStrictRegister = context->getRegister();
codeBlock->pushCode(SetExecutionStateInStrictMode(ByteCodeLOC(m_loc.index), true, savedStrictRegister), context, this->m_loc.index);
}
const ClassContextInformation classInfoBefore = context->m_classInfo;
context->m_classInfo.m_constructorIndex = dstIndex;
context->m_classInfo.m_prototypeIndex = context->getRegister();
@ -107,6 +117,11 @@ public:
context->giveUpRegister(); // for drop m_bodyIndex
context->m_classInfo = classInfoBefore;
if (needStrictModeSwitch) {
codeBlock->pushCode(SetExecutionStateInStrictMode(ByteCodeLOC(m_loc.index), false, savedStrictRegister), context, this->m_loc.index);
context->giveUpRegister(); // for drop savedStrictRegister
}
}
virtual void iterateChildren(const std::function<void(Node* node)>& fn) override

View file

@ -226,6 +226,13 @@ public:
return m_inStrictMode;
}
// Used to evaluate class heritage and computed property names as strict mode
// code regardless of the surrounding code (a class definition is always strict).
void setInStrictMode(bool inStrictMode)
{
m_inStrictMode = inStrictMode;
}
bool inExecutionStopState() const
{
return m_inExecutionStopState;

@ -1 +1 @@
Subproject commit 9169aa40a38a25c61d043511931c31209d980f4e
Subproject commit 8cce9e0598052a8b6b3766f1100b3c1ef01fa093