-
-
Notifications
You must be signed in to change notification settings - Fork 96
fix(codegen,parser): isKnownEntity must not accept Object.prototype members #3069
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
02a6a8d
bf45d7f
2f71847
d2366f5
4b49c9f
595aa9a
dd36c9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| --- | ||
| '@ifc-lite/parser': patch | ||
| '@ifc-lite/codegen': patch | ||
| --- | ||
|
|
||
| Stop the generated schema registry answering for `Object.prototype` members. | ||
|
|
||
| `SCHEMA_REGISTRY.entities` is a plain object literal, so `in` and `obj[key]` | ||
| both reach the prototype chain. `getEntityMetadata('constructor')` returned | ||
| the `Object` constructor. Two exported guards were wrong as a result: | ||
|
|
||
| - `isInstantiable('constructor')` was `true`. Its own docblock says it exists | ||
| to stop authoring code writing an abstract class into an exported file. | ||
| - `normalizeIfcTypeName` returned the string `"Object"` for `constructor`, and | ||
| `undefined` for `__proto__` from a signature declaring `string`. | ||
|
|
||
| `isKnownType('constructor')` was already `false` and is unchanged. It is worth | ||
| naming, because the guard that reads as looser was the one answering correctly, | ||
| and the guard documented as the strict authoring boundary was the one letting | ||
| it through. | ||
|
|
||
| `isKnownEntity` had the same defect and now delegates to `getEntityMetadata` | ||
| rather than repeating the lookup. | ||
|
|
||
| The same generator emits a second registry with the same defect, also fixed: | ||
| `getTypeId('constructor')` returned the `Object` constructor from a signature | ||
| declaring `number | undefined`. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ | ||
|
|
||
| import { describe, it, expect } from 'vitest'; | ||
| import { isKnownType, isInstantiable, normalizeIfcTypeName } from './ifc-schema.js'; | ||
| import { getTypeId } from './generated/type-ids.js'; | ||
|
|
||
| /** | ||
| * #3063. The generated SCHEMA_REGISTRY is a plain object literal, so `in` and | ||
| * `obj[key]` both reach Object.prototype. | ||
| * | ||
| * This file pins the DAMAGE, not the mechanism. The mechanism is pinned in | ||
| * packages/codegen/test/typescript-generator-mapping.test.ts, against the text | ||
| * the generator emits, which is the only copy that cannot drift. What that | ||
| * cannot see is which of this package's exported guards the defect reached, | ||
| * and those are the ones callers actually hold: | ||
| * | ||
| * isKnownType('constructor') false (was already false) | ||
| * isInstantiable('constructor') TRUE <- the authoring guard | ||
| * normalizeIfcTypeName('constructor') "Object" | ||
| * normalizeIfcTypeName('__proto__') undefined, from a `: string` signature | ||
| * | ||
| * isInstantiable is the one that matters. Its own docblock says it exists so | ||
| * authoring code cannot write an abstract class into an exported file, and it | ||
| * was the weaker of the two: it said yes to `constructor` while isKnownType, | ||
| * the guard that reads as looser, correctly said no. | ||
| */ | ||
| describe('schema guards reject inherited Object.prototype names', () => { | ||
| // `__proto__` is deliberately in this list and is not like the others: it | ||
| // resolves to an object rather than a function, so its `.name` is undefined | ||
| // rather than a string. It is the case that turned a wrong answer into a | ||
| // type lie. | ||
| const inherited = ['constructor', 'toString', 'hasOwnProperty', '__proto__']; | ||
|
|
||
| it.each(inherited)('isInstantiable(%s) is false', (name) => { | ||
| expect(isInstantiable(name)).toBe(false); | ||
| }); | ||
|
|
||
| it.each(inherited)('isKnownType(%s) is false', (name) => { | ||
| expect(isKnownType(name)).toBe(false); | ||
| }); | ||
|
|
||
| it.each(inherited)('normalizeIfcTypeName(%s) returns the name unchanged', (name) => { | ||
| // Unknown names are preserved as-is, because a vendor extension is not an | ||
| // error. The failure being pinned is returning something ELSE: "Object" | ||
| // for `constructor`, or undefined for `__proto__`. | ||
| const result = normalizeIfcTypeName(name); | ||
| expect(typeof result).toBe('string'); | ||
| expect(result).toBe(name); | ||
| }); | ||
|
|
||
| // The same generator emits a second registry, and it had the same defect. | ||
| // The comment added to typescript-generator.ts says every lookup below it | ||
| // must be an own-property check, which was true of that file and read as | ||
| // covering the generator, so this is the sibling that claim would have hidden. | ||
| it.each(inherited)('getTypeId(%s) is undefined, not a function', (name) => { | ||
| const id = getTypeId(name); | ||
| // `number | undefined` is the declared return. Before the fix this handed | ||
| // back the Object constructor for `constructor` and Object.prototype for | ||
| // `__proto__`, so asserting `undefined` alone would pass for the wrong | ||
| // reason if the signature were ever loosened. Assert the type too. | ||
| expect(id).toBeUndefined(); | ||
| expect(typeof id).not.toBe('function'); | ||
| }); | ||
|
|
||
| it('still resolves a real type id', () => { | ||
| expect(typeof getTypeId('IfcWall')).toBe('number'); | ||
| }); | ||
|
|
||
| // Without these the suite is satisfied by making every guard return false, | ||
| // which would be a worse bug than the one being fixed. | ||
| it('still answers for real entities', () => { | ||
| expect(isKnownType('IfcWall')).toBe(true); | ||
| expect(isInstantiable('IfcWall')).toBe(true); | ||
| expect(normalizeIfcTypeName('IFCWALL')).toBe('IfcWall'); | ||
| }); | ||
|
|
||
| it('still reports an abstract class as known but not instantiable', () => { | ||
| // The distinction isInstantiable exists to draw. If the fix had broken it, | ||
| // the inherited-name cases above would still pass. | ||
| expect(isKnownType('IfcRoot')).toBe(true); | ||
| expect(isInstantiable('IfcRoot')).toBe(false); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Align the pre-fix impact narrative in both changed files.
The two files describe the affected public guards inconsistently.
packages/parser/src/ifc-schema.prototype-guard.test.ts#L19-L27: document the actual pre-fix result forisKnownType('constructor')and update the explanation..changeset/known-entity-prototype-chain.md#L6-L23: replace “three exported guards” with the exact affected exports and their pre-fix results.📍 Affects 2 files
packages/parser/src/ifc-schema.prototype-guard.test.ts#L19-L27(this comment).changeset/known-entity-prototype-chain.md#L6-L23🤖 Prompt for AI Agents