Skip to content

Commit 57459c6

Browse files
authored
fix Object.defineProperty sharing values across instances (#1697)
* fix Object.defineProperty sharing values across instances When Object.defineProperty is called on an instance (not a prototype), create a per-instance metatable if the shared metatable already has descriptors, so that each instance gets its own descriptor storage. Fixes #1625 * 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 8c856f1 commit 57459c6

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed

src/lualib/SetDescriptor.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ export function __TS__SetDescriptor(
2626
setmetatable(target, metatable);
2727
}
2828

29+
// When setting a descriptor on an instance (not a prototype), ensure it has
30+
// its own metatable so descriptors are not shared across instances.
31+
// See: https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1625
32+
if (!isPrototype && !rawget(metatable, "_isOwnDescriptorMetatable")) {
33+
const instanceMetatable: any = {};
34+
instanceMetatable._isOwnDescriptorMetatable = true;
35+
setmetatable(instanceMetatable, metatable);
36+
setmetatable(target, instanceMetatable);
37+
metatable = instanceMetatable;
38+
}
39+
2940
const value = rawget(target, key);
3041
if (value !== undefined) rawset(target, key, undefined);
3142

test/unit/builtins/object.spec.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,81 @@ describe("Object.defineProperty", () => {
196196
return { prop: foo.bar, err };
197197
`.expectToMatchJsResult();
198198
});
199+
200+
// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1625
201+
test("instance isolation", () => {
202+
util.testFunction`
203+
class Test {
204+
declare obj: object;
205+
constructor() {
206+
Object.defineProperty(this, "obj", { value: {}, writable: true, configurable: true });
207+
}
208+
}
209+
const t1 = new Test();
210+
const t2 = new Test();
211+
return t1.obj === t2.obj;
212+
`.expectToMatchJsResult();
213+
});
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+
});
199274
});
200275

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

0 commit comments

Comments
 (0)