Repository navigation
A name Object.prototype carries is a name like any other: own-key lookups for classes, fields, parts and property bags - #78
Merged
Conversation
…c object it is
The node class map, the three Kind tables, the class-schema tables and the opaque-object
icon table are object literals, and each was asked about a class name the file supplies by
plain indexing. An object literal answers every name Object.prototype carries. toString,
valueOf, constructor, hasOwnProperty, isPrototypeOf, propertyIsEnumerable and toLocaleString
are valid MATLAB class names, and a file can also spell __proto__ and __defineGetter__.
Measured on rt_text.sldd and rt_bin.sldd with the entry's class renamed from MyGadget, and on
test/fixtures/mcos/object_props.mat with its Vehicle object renamed valueOf:
- NodeClassMap.parseValue took CLASS_MAP[class] for the node class and called its parse,
so a dictionary holding one such entry failed to open at all, text and binary alike:
"TypeError: NodeClass.parse is not a function", for every one of the nine names.
- DataNode.kind and kindForClass returned the inherited member as the Kind: a function
for toString, the Object function for constructor, Object.prototype for __proto__. The
Kind column, toRow().Kind and toDTO().kind all carried it, and so would the drop tooltip
of mathworks/data-explorer-vscode, which labels a drag with kindForClass. A derived entry
reached the same answer through DERIVED_KIND_BY_CLASS.
- getSchema mapped over the `props` of CLASS_DEFS[class], an inherited function, and threw
"Cannot read properties of undefined (reading 'map')". Every setProperty asks for the
schema first (trySetSchemaProperty), so once such an entry could open, it could not be
renamed or edited.
- In a .mat, the MCOS bridge asked getClass whether it knows the class, was told yes, and
the typed dispatch threw. The object degraded to an opaque variable without its five
property rows, with a part-unreadable warning naming the TypeError. With the subsystem
left out, so that the object stays opaque, its icon id was the inherited function.
Each lookup is now of an own key. getClass checks hasOwnProperty, and parseValue goes through
getClass. kindMap gains kindIn, which kindForClass and DataNode.kind both use; it is a module
export the barrel does not carry. schema/index.ts has a local helper for classDef, because
schema/ imports nothing. MatlabVariableNode.icon checks MCOS_ICON_MAP the same way. Such a
class is now what any class the tables do not list is: an ObjectNode whose Kind is the raw
class name, with the object icon and the same rows as an Unknown.Klass read from the same file.
89 new cases, in test/nodeClassMap.test.ts (new), test/kindMap.test.ts,
test/schema/schemaResolve.test.ts, test/mcosTypedNode.test.ts and test/objectIcon.test.ts.
test/tools/matRename.ts derives a .mat from a MATLAB-written one by renaming a token inside
its compressed elements, at the same length, and moves the header's subsystem offset to
match. Against origin/main's source (15831c2), 85 of the 89 fail and the 4 controls pass. Here
all 89 pass. With each fix put back alone, its own cases fail: the class map 56, kindForClass
18, DataNode.kind 55, classDef 27, the icon 2. Full suite: 6726 passed, 26 skipped.
… other
MatParser gathered a struct's fields into a plain `{}` keyed by the names the file spells,
and for a struct array it asked `if (!fields[fn]) fields[fn] = []` before pushing each
element's value. constructor, toString, valueOf, hasOwnProperty, isPrototypeOf,
propertyIsEnumerable and toLocaleString are legal MATLAB field names, and on a plain `{}`
each is already "there" as an inherited member, so the `[]` was skipped and the push went to
a function. A 1x2 struct array with any of them, written by core's own encodeMatVariable, made
parseMat and DataModel.addMatSource throw "fields[fn].push is not a function", and the whole
.mat failed to open. __proto__ is not a MATLAB name, but a file can spell it, and a plain `{}`
hands that write to the prototype setter: a struct array threw the same way, and a 1x1
struct's field silently disappeared. Nothing reached Object.prototype itself: `.push` on it
throws.
Presence is now an own-key question, and every write of a file-spelled key goes through
setOwn (parser/ownKeys.ts, new, not in the barrel). setOwn assigns as before, except that it
defines `__proto__` as the own data property every other key becomes. `fields` stays an
ordinary object with Object.prototype, as every bag parseMat hands out is, so a consumer's
`fields.hasOwnProperty(…)`, `String(fields)` and `toStrictEqual` work as they did. With that, a
__proto__ field is read, and three more places wrote it into a plain `{}` on its way out of
the reader:
- MatlabVariableNode._buildVarObject rebuilt `fields` for the writer. A 1x1x2 struct, which
a text dictionary holds as a MAT stream, lost the field once edited and copied there.
- MatlabVariableNode._serializeStructValue built each element's bag. A 1x2 struct copied
into a text dictionary after an edit lost the value while `_fields` still named it, so
the reopened struct read the name off Object.prototype and showed `[object Object]`.
- McosParser.buildStructValue did the same for a struct inside an MCOS object, and
buildProperties for an object's property bag. Derived from object_props.mat, a v.Specs
struct whose one field is __proto__ showed no field at origin/main, and
`[object Object]` with only MatParser fixed. With f.FleetName renamed __proto__, f's
property bag took the string's envelope as its prototype and inherited `_array_type`,
`_elements` and the rest.
All three now write through setOwn. An object's __proto__ property still gets no row, because
ObjectNode reserves every `_`-named key.
23 new cases, in test/matStructArray.test.ts and test/mcosParser.test.ts. test/tools/matRename.ts
now also takes raw bytes, for a same-length patch of a struct's field-name stride. Against the
previous commit's source 14 fail. The 9 that pass are the 1x1 struct with each name but
__proto__, which a plain `{}` already held as an own key, and the ordinary-name control. Here
all 23 pass. With each fix put back alone: MatParser fails 14, _buildVarObject 1,
_serializeStructValue 1, buildStructValue 1 and buildProperties 1. A prototype-less `fields`
(Object.create(null)) fails 18, on the ordinary-object assertion. Full suite: 6749 passed,
26 skipped.
…ng one opens
A zip's member names, and a text-OPC .mdl's part paths, are the file's words, and every
reader gathered the members into a plain `{}` keyed by them: Inflate's own central-directory
walk, fflate's unzipSync (the fallback, and the only engine in a browser), and MdlParser's
part splitter. For `__proto__` the assignment is Object.prototype's setter, so the member's
bytes became the bag's prototype. parseSlx walks the bag with for…in, which then enumerated
every byte index of the member. A 20 KB .slx holding a 20,000,000-byte member of that name
(zeros, so it deflates to almost nothing) took 2.7 s and then failed to open with
"RangeError: Too many properties to enumerate", under either engine. A text-OPC .mdl with a
part at `/__proto__` of the same size failed the same way, after 2.6 s. A binary .sldd opened,
because its reader does not walk the bag, but the member was gone from it.
The member is now left out, by both engines and by the .mdl splitter. Keeping it is not an
option: no reader asks for a part of that name, and fflate's zipSync, which serializeBinarySldd
calls to write a dictionary back, throws on an own `__proto__` key. So the result is what every
caller already saw of the member (nothing), and the bag stays a plain bag. As before, no
warning says a member was left out: the zip readers have no warnings channel, and the .mdl
splitter, which has one, stays silent to match them.
SlxParser.extractConfigSets also asked the bag for each part that configSetInfo names, by
plain indexing. A set listed as part `/toString` (or constructor, hasOwnProperty, __proto__)
found an inherited member, so the set was reported as a part that "could not be read (The
"list" argument must be an instance of SharedArrayBuffer, ArrayBuffer or ArrayBufferView.)".
The lookup is now of an own key, and the warning says what is true: the package does not
contain that part. The set is dropped either way, and the other set is still read.
9 new cases, in test/inflateSeam.test.ts (each engine), test/mdlParser.test.ts and
test/parseWarnings.test.ts. Against the previous commit's source all 9 fail; here all pass.
With each fix put back alone: the native walk fails 2, the fflate filter 2, the .mdl splitter
1, and the configSetInfo lookup 4. Full suite: 6758 passed, 26 skipped.
…ves as a field
A dictionary's struct field names are the file's words, in both formats, and three places used
them as keys of a plain object. Measured on rt_text.sldd and rt_bin.sldd, whose gadget.Specs
is a 1x1 struct of mass (1500) and color ('red'), with color renamed:
- BinarySlddParser.parseStructElement gathered an element's <P Name="…"> children into `{}`.
A field called __proto__ was the prototype setter's argument, and vanished on open.
- StructNode.serializeElement rebuilt each element into `{}` on save. A text dictionary's
__proto__ field opened as 'red', but after an edit of mass and a save its value was lost
while `_fields` still named it. The reopened row read the name off Object.prototype and
showed `[object Object]`.
- StructNode.parse read each declared field off the element by plain indexing. A field that
`_fields` names and the element does not hold (a hand-edited file) read an inherited
member: the row showed `function Object() { [native code] }` for constructor and
`function toString() { [native code] }` for toString, where any other such name reads as
absent.
The reader and the writer now write each name with setOwn, which assigns as before and
defines `__proto__` as an own data property. The bags keep Object.prototype like every other
parsed bag, and a later <P> of the same name still overrides an earlier one in place. The read
is of an own key (fieldOf). A .mat struct with a __proto__ field now also survives a copy into
a binary dictionary, which dropped it until now.
Opening a binary dictionary costs what it did, because the reader still assigns into a plain
`{}`. Medians of five alternating processes, each the median of three opens after one warm-up,
against origin/main (15831c2): binsldd/dd140000.sldd 2286 ms there and 2281 ms here,
customer/Controller_cummins.sldd 966 and 972 ms, customer/PTSA_SysIf_bosch.sldd 526 and 524 ms.
An earlier form of this branch (8016698, on fcf4b46) built this bag and EncodedValue's from
[name, value] pairs with Object.fromEntries. Measured the same way, in the same runs, it took
2393, 1003 and 547 ms, 3.8 to 4.7 percent more than origin/main.
5 new cases, in test/structNode.test.ts and test/matStructArray.test.ts, each over nine
inherited names and an ordinary control. Two open, edit, save and reopen a struct field in
each format; one reads a declared field the element does not hold, in a text dictionary; and
the .mat copy test gains a binary-dictionary venue, through a helper now shared with its text
one. Against the previous commit's source all 5 fail; here all pass. With each fix put
back alone: the binary reader fails 3, serializeElement 2 (its text case, and the binary one,
whose save goes through it too), and the own-key read 1. Full suite: 6763 passed, 26 skipped.
… class refusal says why Two more object-literal tables were asked about a file's word by plain indexing. buildProjectPage labels each working folder with LOCATION_LABEL[key] ?? key. The key is a Reference's Location in the project store. Read through parseProject from a monolithic store, a location keyed toString, constructor, valueOf or hasOwnProperty got an inherited function as its label, and `__proto__` got Object.prototype, where a key the table does not list (NewInSomeFutureRelease) shows itself. The page is what a host renders for a .prj, so a label there should be text. encodeMatVariable takes the MAT class code from CLASS_CODE[className]. The class can come from a file: a text dictionary's `_type`, or the Class of a binary dictionary's dense empty (textDictionaryForm). For those names the inherited member got past the refusal of a class with no MAT code, and the encode failed later as "no payload width for MAT element type undefined" instead of "no MAT class for "toString"". Both are MatWriteErrors, and every caller treats one like another, so nothing a user sees changed. Only the reason did. Both lookups are now of an own key. 12 new cases, in test/projectPage.test.ts and test/matWriter.test.ts, five inherited names and an ordinary control each. Against the previous commit's source the 10 inherited cases fail and the 2 controls pass. Here all pass. With either lookup put back alone, its own 5 fail. Full suite: 6775 passed, 26 skipped.
A dictionary's property bags, elements and value envelopes are keyed by the file's words, and
the readers now hold a `__proto__` key as an own key (JSON.parse, and BinarySlddParser since
the struct-field commit). The save paths then copied each bag with
`Object.assign({}, bag, …)`, which writes every key with [[Set]], so `__proto__` was handed to
the prototype setter. A primitive value was dropped. An object value became the copy's
prototype, and the copy then inherited whatever keys it held. MATLAB cannot write the key
(an identifier starts with a letter), but a file can carry it, and what was read should go
back out. Measured through a text dictionary saved with no edits:
- A Simulink.Parameter and a Simulink.Signal each lost a `__proto__` property from the
saved entry.
- A CustomObject whose `__proto__` property held a `_saveobj` key read as a custom-saving
class, because DataNode._mergeProps asks `SAVEOBJ_KEY in stored` of the copy. The saved
entry gained a `_custom_save` envelope the file never had, carrying the object's
Description.
Every such copy is now an object spread, which defines each key as an own data property, in
the same order and with the same later-wins overrides. That covers DataNode
(_serializeSimulinkObject, _mergeProps, _getSerializedProperties, writeIntoSaveobj),
SimulinkObjectNode._propsOf, ParameterNode, SignalNode, BaseBusNode and its element,
EnumTypeNode and its enumeral, and ObjectNode. EnumTypeNode's Enumerals wrapper was also
rebuilt key by key and is now one spread. Two more bags were filled key by key from the file
and now write through setOwn (parser/ownKeys.ts):
- EncodedValue keeps an encoded element's attributes to write them back. An attribute
called `__proto__` was dropped.
- DataNode.textDictionaryMetadata respells a binary reader's metadata keys for a text
dictionary and carries the rest across. It asked `key in out` of a plain `{}`, so a
metadata key called toString or constructor counted as already written, and
`__proto__` went to the setter. All three were dropped. Presence is now an own-key
question.
The copies whose keys are the node's own (`_gatedProps`, the `_serializedOverrides` literals,
`Object.assign(stored, propOverrides)`) are unchanged. So is ModelNode's copy of a model's zip
members, which can no longer hold a member of that name.
9 new cases, in test/absentPropertyWriteBack.test.ts and test/encodedValueWriteBack.test.ts.
The property-bag cases are differential. The same value goes in under `__proto__` and under
`_zzproto_`, another `_`-named key of the same length: an object holding `_saveobj` in every
object of every entry's value (text), a double in every object element (binary). That is done
over the parity corpus's cases.sldd and the bus_types and custom_object fixtures, and once
more beside a `_custom_save` envelope. Each file is saved untouched and again after every
entry is renamed, and the two saves must be the same file but for the name. A user-class
object rebuilds its bag from its rows and has none for a `_`-named key, so there both keys go
the same way, as they do for every `_`-named key (ObjectNode keeps none of them on a save;
that is outside this change). Against the previous commit's source all 9 fail; here all 9
pass. With each file put back alone: DataNode fails 6, BaseBusNode 4, ParameterNode 3,
EnumTypeNode 2, ObjectNode 1, SignalNode 1, SimulinkObjectNode 1 and EncodedValue 1. Full
suite: 6784 passed, 26 skipped.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Core looked up names a file spells (class names, struct field names, property names, zip member and OPC part names, attribute names, project location keys) in plain object literals and plain
{}accumulators. An object literal answers every nameObject.prototypecarries with an inherited member:toString,valueOf,constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerableandtoLocaleStringare all valid MATLAB class and field names. A write of__proto__into a plain{}calls the prototype setter instead of storing a key. MATLAB cannot write that name, but a file can carry it. #73 fixed this for the System Composer catalog (ScCatalog.ts). This PR applies the same approach everywhere else a census ofsrc/found it reachable from file content:Object.prototype.hasOwnProperty.callas Classify System Composer names by own key, and draw entry uuids from crypto.getRandomValues #73 does.setOwn(new,parser/ownKeys.ts). It assigns as before, except that it defines__proto__as the own data property every other key becomes.Object.assign.Every bag a reader hands out stays an ordinary object with
Object.prototype.Nothing in this PR writes to
Object.prototypeitself, and no site found before it did. The census looked specifically for a nesteda[k1][k2] = vwith a file-derivedk1, and the only "look up, then mutate" (MatParser'sfields[fn].push) threw rather than writing. The barrel (src/index.ts) and every exported type are unchanged. Two module exports are new, and the barrel carries neither:kindIn(kindMap.ts) andsetOwn(parser/ownKeys.ts).Six commits on origin/main 15831c2. Each includes its rebuilt
dist/. At each one, a freshrm -rf dist && npm run buildin a clean worktree leavesgit status --porcelainempty, and the full suite passes.1. Node class and Kind lookups
NodeClassMap(CLASS_MAP, throughgetClassandparseValue),kindMap(KIND_BY_CLASS,DERIVED_KIND_BY_CLASS,KIND_BY_CLASSIFICATION, inkindForClass),DataNode.kind,schema/index.ts(CLASS_DEFS,CLASS_ALIASES), andMatlabVariableNode'sMCOS_ICON_MAP.Before, measured on
test/fixtures/rt_text.slddandrt_bin.slddwith the entry's class renamed fromMyGadget:TypeError: NodeClass.parse is not a function. That held for each of toString, valueOf, constructor, hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString,__proto__and__defineGetter__. Controls:Simulink.Parameteropened as a ParameterNode, andUnknown.Klass/Foo.Baropened as ObjectNodes whose Kind is the class name.DataNode.kindwas the inherited member: a function fortoString, theObjectfunction forconstructor, andObject.prototypefor__proto__. That reachedtoRow().KindandtoDTO().kind. A derived entry got the same answer throughDERIVED_KIND_BY_CLASS.kindForClass('toString')returnedObject.prototype.toString,kindForClass('constructor')returnedObject, andkindForClass('__proto__')returnedObject.prototype. A classification token spelled that way did the same. mathworks/data-explorer-vscode labels a drag withkindForClass(src/webview/dropDecision.ts,kindInTarget).getSchemamapped over thepropsof an inherited function and threwCannot read properties of undefined (reading 'map'). EverysetPropertyasks for the schema first (trySetSchemaProperty), so such an entry could not be renamed or edited once it opened.getClasswhether it knows the class and was told yes, and then the typed dispatch threw.test/fixtures/mcos/object_props.matwith itsVehicleobject renamedvalueOfshowed that object as an opaque variable without its five property rows, plus apart-unreadablewarning naming the TypeError. With the subsystem left out, the opaque object's icon id was the inherited function.Now such a class is what any unlisted class is: an ObjectNode with the raw class name as its Kind, the object icon, and the same rows as
Unknown.Klassread from the same file. It can be renamed, edited, saved and reopened.mcosTypedNode.ts:83(isKnown) is fixed throughgetClass.2. MAT struct fields
MatParser's structfields,MatlabVariableNode._buildVarObjectand_serializeStructValue, andMcosParser.buildStructValueandbuildProperties.Before: a 1x2 struct array with a field named constructor, toString, valueOf, hasOwnProperty (or any of the nine names), written by core's own
encodeMatVariable, madeparseMatandDataModel.addMatSourcethrowfields[fn].push is not a function, so the .mat did not open. A 1x1 struct's__proto__field silently vanished.Once the reader kept it, three more writes into
{}lost it on the way out:_fields: ["a","__proto__"]but lost the values, and reopened showing[object Object](read offObject.prototype).v.Specsstruct patched to one field named__proto__showed no field at origin/main. With f.FleetName renamed__proto__, f's property bag took the string's envelope as its prototype.Now a field's presence is an own-key question, and every write goes through
setOwn.parseMatis public, and thefieldsit hands out stays an ordinary object withObject.prototype, sofields.hasOwnProperty(…),String(fields)andtoStrictEqualbehave as before._buildVarObject'sfieldsis the same. Every such field round-trips through the tree, a text-dictionary paste and a binary-dictionary paste. An MCOS object's__proto__property still gets no row, because ObjectNode reserves every_-named key (_addPropertyChildren), but it no longer re-parents the bag.3. Zip members and text-OPC parts named
__proto__Inflate(the native central-directory walk and the fflate fallback),MdlParser's part splitter, andSlxParser.extractConfigSets.Before: every member bag was a plain
{}, so a member named__proto__became the bag's prototype. parseSlx walks the bag withfor…in, so a 20 KB .slx holding a 20,000,000-byte member of that name (zeros) took 2.7 s and then failed to open withRangeError: Too many properties to enumerate, under either engine. A text-OPC .mdl with a 20 MB part at/__proto__failed the same way after 2.6 s. Separately, a configSetInfo entry naming part/toString(or constructor, hasOwnProperty,__proto__) was reported as a part that "could not be read (The "list" argument must be an instance of SharedArrayBuffer, ArrayBuffer or ArrayBufferView.)" rather than one the package does not contain.Now both engines and the .mdl splitter leave such a member out, and the part lookup is by own key. Leaving it out is what every caller already saw of that member. Keeping it isn't possible: fflate's
zipSync, whichserializeBinarySlddcalls, throws on an own__proto__key (measured:Cannot read properties of undefined (reading 'level')). The .slx and .mdl open as the same model as their controls (JSON.stringify/toEqualagainst the clean parse).As before, no ParseWarning says a member was dropped. The zip readers have no warnings channel, and the .mdl splitter has one (it warns for a part header with no path) but stays silent here to match them. That is a follow-up, below.
4. Dictionary struct fields
BinarySlddParser.parseStructElement,StructNode.serializeElement, andStructNode.parse's field reads.Before, on rt_text.sldd and rt_bin.sldd's
gadget.Specswithcolorrenamed:<P Name="__proto__">field vanished on open.__proto__field opened as'red', but after an edit and a save it reopened as[object Object]._fieldsnames and the element doesn't hold (a hand-edited file) showedfunction Object() { [native code] }forconstructorandfunction toString() { [native code] }fortoString. Any other such name reads as absent.Now the reader and the writer write each name with
setOwn, and the read is by own key (fieldOf). All nine names open, edit, save and reopen as fields in both formats. The reader still assigns into a plain{}, so opening a binary dictionary costs what it did (see Performance).5. Two more static tables
ProjectPage'sLOCATION_LABEL[key] ?? key, where key is a project store's Reference Location. Read throughparseProjectfrom a monolithic store, a location keyed toString, constructor, valueOf or hasOwnProperty got an inherited function as its label, and__proto__gotObject.prototype. Now each is labelled with its raw key, asNewInSomeFutureReleaseis.MatWriter'sCLASS_CODE[className], where className can be a text dictionary's_type. Such a class got past the "no MAT class" refusal and failed later asno payload width for MAT element type undefined. Both are MatWriteErrors, and every caller (textDictionaryForm,_matStream) handles them identically, so only the reason changed. It now refuses withno MAT class for "toString".6.
__proto__keys in a dictionary's property bagsBefore: the save paths copied each bag with
Object.assign({}, bag, …). The copies were in DataNode (_serializeSimulinkObject,_mergeProps,_getSerializedProperties,writeIntoSaveobj),SimulinkObjectNode._propsOf, ParameterNode, SignalNode, BaseBusNode and its element, EnumTypeNode and its enumeral, and ObjectNode. In a text dictionary saved with no edits:__proto__property.__proto__property held a_saveobjkey read as a custom-saving class (SAVEOBJ_KEY in storedon the re-parented copy). The untouched save gained a_custom_saveenvelope the file never had.Two bags filled key by key from the file also lost keys:
EncodedValuedropped an encoded element's__proto__attribute on write-back (0 of 17 kept on sparse_binary.sldd).DataNode.textDictionaryMetadatadropped metadata keys named toString or constructor (key in out) and__proto__.Now every such copy is an object spread. That keeps key order and later-wins overrides, and defines each key as an own property. The two key-by-key bags write through
setOwn, and the metadata's presence check is own-key. Copies whose keys are the node's own (_gatedProps, the_serializedOverridesliterals,Object.assign(stored, propOverrides)) are unchanged, and so is ModelNode's copy of zip members, which can no longer hold that name.Performance
Opening is as fast as at origin/main. These are medians of five processes, alternating between the dist trees, each process timing three
ingestopens after one warm-up.The earlier form built the binary reader's struct and object bags, and EncodedValue's, with
Object.fromEntriesover [name, value] pairs. An earlier run with commit 4 alone against both gave 3.8–4.7% for that form, and −0.4% to +0.6% for this one.Two more spot checks, three alternating processes each:
parseMatalone takes 1636–1640 ms at base and 1595–1651 ms here, andMatNode.fromParsedalone 2092–2127 ms at base and 2091–2119 ms here.Tests
147 new cases. Every one goes through the real parse path (
ingest/parseMat/parseSlx/parseMdl/parseProjecton fixture-derived files), exceptkindForClass,getSchema/getLayout,getClass,encodeMatVariable,unzipEntries, and one icon case onparseMatVariable. No existing test is modified. Two import lists gain a name (schemaResolve, projectPage). Two pieces of this PR's own test code change in later commits:test/tools/matRename.ts(added in commit 1) takes raw bytes from commit 2 on, and commit 4 folds commit 2's .mat copy test into a helper it shares with a new binary-dictionary venue.__proto__cases and the control pass, as a plain{}held them)_buildVarObject1,_serializeStructValue1, buildStructValue 1, buildProperties 1; a prototype-lessfieldsfails 18How the fixtures were built:
test/tools/matRename.ts(new) derives a .mat from a MATLAB-written one by a same-length replacement inside its compressed elements (text or raw bytes), and moves the header's subsystem offset to match.encodeMatVariable, with the 8-byte empty name element of the member it writes replaced by a small-elements.__proto__member is written under a same-length placeholder and renamed in the local header and the central directory, because fflate'szipSynccannot be handed that key.__proto__and under_zzproto_in every object of every entry's value (text) or every object element (binary). That covers the parity corpus's cases.sldd and the bus_types and custom_object fixtures, plus one bag beside a_custom_saveenvelope. Each file is saved untouched and again after every entry is renamed, and the two saves must be the same file but for the name.Census: checked and left alone
fast-xml-parser 5.10.1 treats three kinds of element name differently:
__proto__,constructororprototypeare refused with[SECURITY] Invalid name, for the whole part. Measured: one<constructor/>anywhere in rt_bin.sldd's chunk makes the dictionary open with no entries and asource-unreadablewarning naming the reason.hasOwnProperty,toString,valueOfand the four__define…/__lookup…names are renamed with a__prefix (<toString>becomes__toString).isPrototypeOf, is kept as an own key.Attribute names are
@_-prefixed first, so none of this applies to them. No reader asks for an element of any of those names, so a renamed or kept one is ignored exactly as an unknown element is. The refusal is unconditional (sanitizeNameruns aftertransformTagName), no MATLAB writer produces such an element, nothing is polluted, and the loss is reported. Avoiding it would mean rewriting the XML before parsing it. The same applies to model and project XML parts.Keys from a fixed vocabulary:
SECTION_NAMESPACE/ALLOWED_TYPES: design, arch, config, other.FORMAT_LABEL: the reader's own layout names.ATOM_BY_KEYand the schemaREGISTRY: schema JSON keys.CLASS_NAMES,ELEMENT_WIDTH,PAYLOAD_TYPE,WIDTHand McosParser's tables: numeric keys.'Min' in sp/key in storedwrite-back gates in Bus, ConnectionBus, Signal, ValueType and SimulinkObjectNode: literal keys.Looked up through Maps or Sets:
NON_DATA_BLOCK_PARAMSandENUM_BLOCK_PARAMS(through theNON_DATA_PARAMSMap), ProjectParser, UsageIndex, DataModel, typeLinkIndex, deletionPlan, maskScope and the scans. smol-toml 1.9's tables are prototype-less and keep a__proto__key as an own key (measured).Already guarded: DictionaryXmlFast (five interned tags behind
hasOwnProperty, attributes@_-prefixed), and ScCatalog (Classify System Composer names by own key, and draw entry uuids from crypto.getRandomValues #73).Written with file keys but only read by literal keys: SlxParser
directProps, MdlParserflatProps(a__proto__parameter is dropped there, and no read asks for it), and SlxParserrawContents(keys end.xml/.json).Zip-member bags downstream of Inflate (
ingest's .prj files, BinarySlddParserzipMetadata, BinarySlddSerializer, ModelNode): they can no longer hold a__proto__member.Host input, not file content:
DataNode.setProperty'sself[resolved]when no prop matches. A host passingconstructorwould shadownode.constructor. The PI "Other" rows built from file keys are read-only.getSectionMetadata('toString').Pre-existing, not caused by this branch
complex/base.sldd,complex/plant.slddandcomplex/shared.sldd(local corpus) fail to save at origin/main 15831c2 and here alike. The error isTypeError: Cannot read properties of undefined (reading '0')atParameterNode.serializeValue,(result._elements)[0].ObjectNode._reservedPropsreadsserial._properties, whichObjectNode.parsenever sets, so it always sees{}. A user-class object's save rebuilds its bag from its rows, so a_-named key in that bag is not written back,__proto__and_zzalike. Measured on custom_object_text.sldd: with_zzin each of its 12 property bags, an untouched save kept 5 and lost the 7 under ObjectNodes.Follow-ups
__proto__zip member or text-OPC part gets no ParseWarning.unzipEntrieswould need a warnings channel to report it for .slx, binary .sldd and .prj. The .mdl splitter could warn today, but would then be the only format that does.Verification
npm run verifyat the head passes: typecheck, build, smoke, test, check:pack, check:leak and check:browser. Tests: 206 files passed and 1 skipped, 6784 passed and 26 skipped. origin/main 15831c2 has 6637 passed and 26 skipped, so the difference is the 147 new cases.