mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 18:57:42 +00:00
LibJS: Fix switch skipping case evaluation when hitting the default case
When no case match we should not just execute the statements of the default case but also of any cases below the default case.
This commit is contained in:
parent
830ea0414c
commit
e5d48ee238
2 changed files with 65 additions and 28 deletions
|
@ -2825,6 +2825,7 @@ Value ThrowStatement::execute(Interpreter& interpreter, GlobalObject& global_obj
|
|||
return {};
|
||||
}
|
||||
|
||||
// 14.12.2 Runtime Semantics: CaseBlockEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-caseblockevaluation
|
||||
Value SwitchStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||
{
|
||||
// FIXME: This needs a massive refactoring, ideally once we start using continue, break, and return completions.
|
||||
|
@ -2845,16 +2846,46 @@ Value SwitchStatement::execute(Interpreter& interpreter, GlobalObject& global_ob
|
|||
block_declaration_instantiation(global_object, block_environment);
|
||||
|
||||
interpreter.vm().running_execution_context().lexical_environment = block_environment;
|
||||
Optional<size_t> first_passing_case;
|
||||
for (size_t i = 0; i < m_cases.size(); ++i) {
|
||||
auto& switch_case = m_cases[i];
|
||||
if (switch_case.test()) {
|
||||
auto test_result = switch_case.test()->execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
if (is_strictly_equal(discriminant_result, test_result)) {
|
||||
first_passing_case = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: we could optimize and store the location of the default case in a member variable.
|
||||
if (!first_passing_case.has_value()) {
|
||||
for (size_t i = 0; i < m_cases.size(); ++i) {
|
||||
auto& switch_case = m_cases[i];
|
||||
if (!switch_case.test()) {
|
||||
first_passing_case = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto last_value = js_undefined();
|
||||
|
||||
auto execute_switch_case = [&](auto const& switch_case) -> Optional<Value> {
|
||||
if (!first_passing_case.has_value()) {
|
||||
return last_value;
|
||||
}
|
||||
VERIFY(first_passing_case.value() < m_cases.size());
|
||||
|
||||
for (size_t i = first_passing_case.value(); i < m_cases.size(); ++i) {
|
||||
auto& switch_case = m_cases[i];
|
||||
for (auto& statement : switch_case.children()) {
|
||||
auto value = statement.execute(interpreter, global_object);
|
||||
if (!value.is_empty())
|
||||
last_value = value;
|
||||
if (interpreter.exception())
|
||||
return Value {};
|
||||
return {};
|
||||
if (interpreter.vm().should_unwind()) {
|
||||
if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_labels)) {
|
||||
// No stop_unwind(), the outer loop will handle that - we just need to break out of the switch/case.
|
||||
|
@ -2867,33 +2898,7 @@ Value SwitchStatement::execute(Interpreter& interpreter, GlobalObject& global_ob
|
|||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
bool falling_through = false;
|
||||
SwitchCase const* default_switch_case = nullptr;
|
||||
for (auto& switch_case : m_cases) {
|
||||
if (!switch_case.test()) {
|
||||
default_switch_case = &switch_case;
|
||||
falling_through = true;
|
||||
continue;
|
||||
}
|
||||
if (!falling_through) {
|
||||
auto test_result = switch_case.test()->execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
if (!is_strictly_equal(discriminant_result, test_result))
|
||||
continue;
|
||||
}
|
||||
falling_through = true;
|
||||
if (auto result = execute_switch_case(switch_case); result.has_value())
|
||||
return *result;
|
||||
}
|
||||
if (default_switch_case) {
|
||||
if (auto result = execute_switch_case(*default_switch_case); result.has_value())
|
||||
return *result;
|
||||
}
|
||||
|
||||
return last_value;
|
||||
}
|
||||
|
||||
|
|
|
@ -67,11 +67,43 @@ describe("basic switch tests", () => {
|
|||
}
|
||||
expect(i).toBe(5);
|
||||
});
|
||||
|
||||
test("default branch is not taken if more exact branch exists", () => {
|
||||
function switchTest(i) {
|
||||
let result = 0;
|
||||
switch (i) {
|
||||
case 1:
|
||||
result += 1;
|
||||
break;
|
||||
case 1:
|
||||
expect().fail();
|
||||
case 2:
|
||||
result += 2;
|
||||
default:
|
||||
result += 4;
|
||||
case 3:
|
||||
result += 8;
|
||||
break;
|
||||
case 2:
|
||||
expect().fail();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
expect(switchTest(1)).toBe(1);
|
||||
expect(switchTest(2)).toBe(14);
|
||||
expect(switchTest(3)).toBe(8);
|
||||
expect(switchTest(4)).toBe(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("syntax errors", () => {
|
||||
expect("switch () {}").not.toEval();
|
||||
expect("switch () { case 1: continue; }").not.toEval();
|
||||
expect("switch () { case 1: break doesnotexist; }").not.toEval();
|
||||
expect("label: switch () { case 1: break not_the_right_label; }").not.toEval();
|
||||
expect("label: switch () { case 1: continue label; }").not.toEval();
|
||||
expect("switch (foo) { bar }").not.toEval();
|
||||
expect("switch (foo) { default: default: }").not.toEval();
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue