Skip to content

Commit 9793e40

Browse files
committed
[defer-import-eval] Fix super property access on module namespace objects
Object::SetSuperProperty was short-circuiting to TypeError when the OWN lookup on the receiver hit MODULE_NAMESPACE, skipping the [[GetOwnProperty]] call that OrdinarySetWithOwnDescriptor[1] requires per spec (step 2.c). This meant deferred module evaluation was not triggered and TDZ bindings incorrectly threw TypeError instead of ReferenceError. The fix replaces the MODULE_NAMESPACE case with a GetOwnPropertyDescriptor call that mirrors the existing INTERCEPTOR/JSPROXY path. This properly invokes the namespace's [[GetOwnProperty]], which: - Triggers deferred module evaluation for non-symbol-like string keys - Throws ReferenceError for uninitialized (TDZ) bindings - Returns a writable descriptor for initialized exports, falling through to DefineOwnProperty which rejects the value change (TypeError) Also marks two test262 tests as FAIL (ignore-super-property-set-exported and ignore-super-property-set-not-exported) whose expectations are incorrect per tc39/test262#4999. For a detailed flow on how spec flows to thse cases, see: tc39/test262#4980 [1] - https://tc39.es/ecma262/#sec-ordinarysetwithowndescriptor
1 parent f8473e0 commit 9793e40

8 files changed

+168
-8
lines changed

src/objects/objects.cc

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2657,9 +2657,37 @@ Maybe<bool> Object::SetSuperProperty(LookupIterator* it,
26572657
NewTypeError(MessageTemplate::kWasmObjectsAreOpaque));
26582658

26592659
case LookupIterator::MODULE_NAMESPACE: {
2660-
RETURN_FAILURE(isolate, GetShouldThrow(isolate, should_throw),
2661-
NewTypeError(MessageTemplate::kStrictCannotSetProperty,
2662-
it->GetName(), it->GetReceiver()));
2660+
PropertyDescriptor desc;
2661+
Maybe<bool> owned =
2662+
JSReceiver::GetOwnPropertyDescriptor(&own_lookup, &desc);
2663+
MAYBE_RETURN(owned, Nothing<bool>());
2664+
if (!owned.FromJust()) {
2665+
// Property not found on namespace (non-exported key).
2666+
// Spec: OrdinarySetWithOwnDescriptor step 2.d.ii calls
2667+
// CreateDataProperty(Receiver, P, V), which invokes
2668+
// Receiver.[[DefineOwnProperty]] ->
2669+
// JSModuleNamespace::DefineOwnProperty
2670+
// -> throws kRedefineDisallowed TypeError.
2671+
// We shortcut to RedefineIncompatibleProperty for the same result.
2672+
return RedefineIncompatibleProperty(isolate, it->GetName(), value,
2673+
should_throw);
2674+
}
2675+
if (PropertyDescriptor::IsAccessorDescriptor(&desc) ||
2676+
!desc.writable()) {
2677+
return RedefineIncompatibleProperty(isolate, it->GetName(), value,
2678+
should_throw);
2679+
}
2680+
// Module namespace [[GetOwnProperty]] returns writable: true for
2681+
// initialized exports. So this DefineOwnProperty path is reached for
2682+
// normal exports. DefineOwnProperty dispatches to
2683+
// JSModuleNamespace::DefineOwnProperty which validates that the
2684+
// new value equals the current value (non-configurable property
2685+
// constraint) and throws TypeError (kRedefineDisallowed) when it
2686+
// doesn't match.
2687+
PropertyDescriptor value_desc;
2688+
value_desc.set_value(Cast<JSAny>(value));
2689+
return JSReceiver::DefineOwnProperty(isolate, receiver, it->GetName(),
2690+
&value_desc, should_throw);
26632691
}
26642692

26652693
case LookupIterator::TRANSITION:

test/mjsunit/harmony/modules-import-defer-no-trigger.mjs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,33 @@ assertEquals(0, globalThis.eval_list.length);
4040
assertThrows(() => obj.nonExistent = 41, TypeError);
4141
assertEquals(0, globalThis.eval_list.length);
4242

43-
class A { constructor() { return ns; } };
44-
class B extends A {
43+
// super property set with symbol-like keys should not trigger evaluation.
44+
class A { constructor() { return ns; } }
45+
46+
class B1 extends A {
47+
constructor() {
48+
super();
49+
super[Symbol()] = 14;
50+
}
51+
}
52+
try { new B1(); } catch (_) {}
53+
assertEquals(0, globalThis.eval_list.length);
54+
55+
class B2 extends A {
4556
constructor() {
4657
super();
47-
super.x = 14;
58+
super[Symbol.toStringTag] = 14;
4859
}
49-
};
60+
}
61+
try { new B2(); } catch (_) {}
62+
assertEquals(0, globalThis.eval_list.length);
5063

51-
assertThrows(() => new B(), TypeError);
64+
class B3 extends A {
65+
constructor() {
66+
super();
67+
super.then = 14;
68+
}
69+
}
70+
try { new B3(); } catch (_) {}
5271
assertEquals(0, globalThis.eval_list.length);
5372

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2026 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flags: --js-defer-import-eval
6+
7+
globalThis.eval_list = [];
8+
9+
import defer * as ns from './modules-skip-import-defer-1.mjs';
10+
11+
assertEquals(0, globalThis.eval_list.length);
12+
13+
class A { constructor() { return ns; } }
14+
class B extends A {
15+
constructor() {
16+
super();
17+
super.foo = 14;
18+
}
19+
}
20+
21+
try { new B(); } catch (_) {}
22+
23+
// super.foo = 14 calls OrdinarySetWithOwnDescriptor which calls
24+
// Receiver.[[GetOwnProperty]]("foo") on the namespace, triggering evaluation.
25+
assertArrayEquals(['defer-1'], globalThis.eval_list);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2026 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Flags: --js-defer-import-eval
6+
7+
globalThis.eval_list = [];
8+
9+
import defer * as ns from './modules-skip-import-defer-1.mjs';
10+
11+
assertEquals(0, globalThis.eval_list.length);
12+
13+
class A { constructor() { return ns; } }
14+
class B extends A {
15+
constructor() {
16+
super();
17+
super.notExported = 14;
18+
}
19+
}
20+
21+
try { new B(); } catch (_) {}
22+
23+
// Even for non-exported keys, [[GetOwnProperty]] calls GetModuleExportsList
24+
// which triggers evaluation.
25+
assertArrayEquals(['defer-1'], globalThis.eval_list);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2026 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as ns from './modules-namespace-super-access-tdz.mjs';
6+
7+
class A { constructor() { return ns; } }
8+
class B extends A {
9+
constructor() {
10+
super();
11+
super.foo = 14;
12+
}
13+
}
14+
15+
assertThrows(() => new B(), ReferenceError);
16+
17+
export let foo = 42;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2026 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
export let foo = 42;
6+
7+
import * as ns from './modules-namespace-super-property-set.mjs';
8+
9+
class A { constructor() { return ns; } }
10+
class B extends A {
11+
constructor() {
12+
super();
13+
super.foo = 14;
14+
}
15+
}
16+
17+
assertThrows(() => new B(), TypeError);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2026 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as ns from './modules-namespace-super-set-tdz-with-accessor.mjs';
6+
7+
let setterValue;
8+
9+
class A {
10+
set foo(v) { setterValue = v; }
11+
constructor() { return ns; }
12+
}
13+
class B extends A {
14+
constructor() {
15+
super();
16+
super.foo = 14;
17+
}
18+
}
19+
20+
new B();
21+
assertEquals(14, setterValue);
22+
23+
export let foo = 42;

test/test262/test262.status

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@
2828
[
2929

3030
[ALWAYS, {
31+
# tc39/test262 PR #4999: these tests have incorrect expectations.
32+
# They assume super.prop = val on a deferred namespace doesn't trigger
33+
# [[GetOwnProperty]], but OrdinarySetWithOwnDescriptor requires it.
34+
'language/import/import-defer/evaluation-triggers/ignore-super-property-set-exported': [FAIL],
35+
'language/import/import-defer/evaluation-triggers/ignore-super-property-set-not-exported': [FAIL],
36+
3137
###################### MISSING ES6 FEATURES #######################
3238

3339
# https://code.google.com/p/v8/issues/detail?id=4248

0 commit comments

Comments
 (0)