From 55636a90613facfecd73ec2676d45bd7ee82a61a Mon Sep 17 00:00:00 2001 From: test Date: Fri, 2 Oct 2026 22:22:59 -0400 Subject: [PATCH] pointers: allow regions named like object properties --- packages/pointers/CHANGELOG.md | 5 +++ packages/pointers/src/dereference/cursor.ts | 32 +++++++------ packages/pointers/src/dereference/generate.ts | 11 +++-- .../pointers/src/dereference/index.test.ts | 45 +++++++++++++++++++ packages/pointers/src/dereference/process.ts | 7 +-- 5 files changed, 81 insertions(+), 19 deletions(-) diff --git a/packages/pointers/CHANGELOG.md b/packages/pointers/CHANGELOG.md index 6dde7204ca..ae9759832c 100644 --- a/packages/pointers/CHANGELOG.md +++ b/packages/pointers/CHANGELOG.md @@ -11,6 +11,11 @@ itself are tracked in the root [`CHANGELOG.md`](../../CHANGELOG.md). - `dereference` composes the `yields` renames of nested template references. A region now takes the rename of each enclosing reference, innermost first, where only the innermost rename used to apply. +- `dereference` accepts regions named like object properties, such as + `length` or `constructor`. A region named `length` made `cursor.view` throw + a `TypeError`; this is how solc's pointer templates name the length of a + top-level dynamic array. Such a region is reachable by `regions.lookup` and + `regions.named`, but not as a property of `regions` itself. ## 0.1.0-preview.0 — 2026-09-21 diff --git a/packages/pointers/src/dereference/cursor.ts b/packages/pointers/src/dereference/cursor.ts index d84b5f1dce..3236efea8c 100644 --- a/packages/pointers/src/dereference/cursor.ts +++ b/packages/pointers/src/dereference/cursor.ts @@ -12,8 +12,10 @@ export function createCursor( list.push(region); } - const named: { [name: string]: Cursor.Region[] } = {}; - const current: { [name: string]: Cursor.Region } = {}; + // no prototype, so that names like `constructor` or `__proto__` are + // ordinary keys + const named: { [name: string]: Cursor.Region[] } = Object.create(null); + const current: { [name: string]: Cursor.Region } = Object.create(null); const propertyFlags = { writable: false, @@ -44,26 +46,30 @@ export function createCursor( } } - for (const [name, region] of Object.entries(current)) { - Object.defineProperty(regions, name, { - value: region, - ...propertyFlags, - }); - } - Object.defineProperties(regions, { named: { - value: (name: string) => named[name] || [], + value: (name: string) => (name in named ? named[name] : []), ...propertyFlags, }, lookup: { - value: { - ...current, - }, + value: current, ...propertyFlags, }, }); + // Also expose each name as a property of the array, unless it would + // shadow a property the array already has (e.g. `length`); those + // regions remain reachable by `named` and `lookup`. + for (const [name, region] of Object.entries(current)) { + if (name in regions) { + continue; + } + Object.defineProperty(regions, name, { + value: region, + ...propertyFlags, + }); + } + return { regions, async read(region: Cursor.Region) { diff --git a/packages/pointers/src/dereference/generate.ts b/packages/pointers/src/dereference/generate.ts index 11305f82a8..09868f6c4c 100644 --- a/packages/pointers/src/dereference/generate.ts +++ b/packages/pointers/src/dereference/generate.ts @@ -64,7 +64,7 @@ export async function* generateRegions( // nested yields compose if (region.name) { const name = renameStack.reduceRight( - (name, mapping) => mapping[name] ?? name, + (name, mapping) => (hasOwn(mapping, name) ? mapping[name] : name), region.name, ); if (name !== region.name) { @@ -123,6 +123,9 @@ export async function* generateRegions( } } +const hasOwn = (object: object, key: string) => + Object.prototype.hasOwnProperty.call(object, key); + async function initializeProcessOptions({ templates, state, @@ -131,8 +134,10 @@ async function initializeProcessOptions({ const currentStackLength = await state.stack.length; const stackLengthChange = currentStackLength - initialStackLength; - const regions: Record = {}; - const variables: Record = {}; + // no prototype, so that names like `constructor` or `__proto__` are + // ordinary keys + const regions: Record = Object.create(null); + const variables: Record = Object.create(null); return { templates, diff --git a/packages/pointers/src/dereference/index.test.ts b/packages/pointers/src/dereference/index.test.ts index f6c291161d..d4da3e2ac0 100644 --- a/packages/pointers/src/dereference/index.test.ts +++ b/packages/pointers/src/dereference/index.test.ts @@ -537,6 +537,51 @@ describe("dereference", () => { expect(regions.named("outer-data")).toHaveLength(2); }); + describe.each(["length", "constructor", "__proto__", "toString"])( + "regions named like object properties (%s)", + (name) => { + it("allows referencing the region by name", async () => { + const pointer: Pointer = { + group: [ + { name, location: "memory", offset: 32, length: 4 }, + { + name: "data", + location: "memory", + offset: { ".offset": name }, + length: { ".length": name }, + }, + ], + }; + + const cursor = await dereference(pointer); + const { regions } = await cursor.view(state); + + expect(regions).toHaveLength(2); + expect(regions[1].offset).toEqual(Data.fromNumber(32)); + expect(regions[1].length).toEqual(Data.fromNumber(4)); + }); + + it("allows renaming the region with yields", async () => { + const templates: Pointer.Templates = { + leaf: { + expect: [], + for: { name, location: "memory", offset: 0, length: 1 }, + }, + }; + const pointer: Pointer = { + template: "leaf", + yields: { [name]: "renamed" }, + }; + + const cursor = await dereference(pointer, { templates }); + const { regions } = await cursor.view(state); + + expect(regions).toHaveLength(1); + expect(regions[0].name).toEqual("renamed"); + }); + }, + ); + it("composes yields renames across nested template references", async () => { const templates: Pointer.Templates = { leaf: { diff --git a/packages/pointers/src/dereference/process.ts b/packages/pointers/src/dereference/process.ts index cd1ebc33c1..52a2c1f2be 100644 --- a/packages/pointers/src/dereference/process.ts +++ b/packages/pointers/src/dereference/process.ts @@ -138,9 +138,10 @@ async function* processScope( ): Process { const { define: variableExpressions, in: in_ } = collection; - const allVariables = { - ...options.variables, - }; + const allVariables: Record = Object.assign( + Object.create(null), + options.variables, + ); const newVariables: { [identifier: string]: Value } = {}; for (const [identifier, expression] of Object.entries(variableExpressions)) { const value = await evaluate(expression, {