Skip to content

Commit da8f135

Browse files
committed
fix Object.defineProperty instance isolation for first instance
Use a marker to always create per-instance metatables, not just when _descriptors already exists on the shared metatable. Add more thorough instance isolation tests.
1 parent 697a912 commit da8f135

File tree

2 files changed

+62
-4
lines changed

2 files changed

+62
-4
lines changed

src/lualib/SetDescriptor.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,9 @@ export function __TS__SetDescriptor(
2929
// When setting a descriptor on an instance (not a prototype), ensure it has
3030
// its own metatable so descriptors are not shared across instances.
3131
// See: https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1625
32-
if (!isPrototype && rawget(metatable, "_descriptors")) {
33-
// The metatable already has descriptors from a previous defineProperty
34-
// call (likely on a different instance sharing the same class metatable).
35-
// Create a per-instance metatable that chains to the original.
32+
if (!isPrototype && !rawget(metatable, "_isOwnDescriptorMetatable")) {
3633
const instanceMetatable: any = {};
34+
instanceMetatable._isOwnDescriptorMetatable = true;
3735
setmetatable(instanceMetatable, metatable);
3836
setmetatable(target, instanceMetatable);
3937
metatable = instanceMetatable;

test/unit/builtins/object.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,66 @@ describe("Object.defineProperty", () => {
211211
return t1.obj === t2.obj;
212212
`.expectToMatchJsResult();
213213
});
214+
215+
test("instance isolation with three instances", () => {
216+
util.testFunction`
217+
class Test {
218+
declare obj: object;
219+
constructor() {
220+
Object.defineProperty(this, "obj", { value: {}, writable: true, configurable: true });
221+
}
222+
}
223+
const t1 = new Test();
224+
const t2 = new Test();
225+
const t3 = new Test();
226+
return [t1.obj === t2.obj, t1.obj === t3.obj, t2.obj === t3.obj];
227+
`.expectToMatchJsResult();
228+
});
229+
230+
test("instance isolation with mutation", () => {
231+
util.testFunction`
232+
class Test {
233+
declare value: number;
234+
constructor(v: number) {
235+
Object.defineProperty(this, "value", { value: v, writable: true, configurable: true });
236+
}
237+
}
238+
const t1 = new Test(1);
239+
const t2 = new Test(2);
240+
return [t1.value, t2.value];
241+
`.expectToMatchJsResult();
242+
});
243+
244+
test("instance isolation with multiple properties", () => {
245+
util.testFunction`
246+
class Test {
247+
declare a: string;
248+
declare b: string;
249+
constructor(a: string, b: string) {
250+
Object.defineProperty(this, "a", { value: a, writable: true, configurable: true });
251+
Object.defineProperty(this, "b", { value: b, writable: true, configurable: true });
252+
}
253+
}
254+
const t1 = new Test("x", "y");
255+
const t2 = new Test("p", "q");
256+
return [t1.a, t1.b, t2.a, t2.b];
257+
`.expectToMatchJsResult();
258+
});
259+
260+
test("instance isolation preserves prototype methods", () => {
261+
util.testFunction`
262+
class Test {
263+
declare val: number;
264+
constructor(v: number) {
265+
Object.defineProperty(this, "val", { value: v, writable: true, configurable: true });
266+
}
267+
getVal() { return this.val; }
268+
}
269+
const t1 = new Test(10);
270+
const t2 = new Test(20);
271+
return [t1.getVal(), t2.getVal()];
272+
`.expectToMatchJsResult();
273+
});
214274
});
215275

216276
describe("Object.getOwnPropertyDescriptor", () => {

0 commit comments

Comments
 (0)