Conversation
|
Hi Jeremy, thanks for your contribution. I don‘t like how this minor fix significantly increases complexity. Let‘s discuss the problem in an issue and see what are possible alternatives. |
|
This PR also seems to have a regression I'll have to fix if we want to take this approach over yours. import superjson from 'superjson';
const set = new Set();
const root = { back: set };
set.add(null);
set.add(root);
try {
const json = superjson.stringify(root);
const _ = superjson.parse(json);
console.log("✅ Success");
} catch (e) {
console.error("❌ Crash detected:", e.message);
} |
|
I'll also read over this once more today then
I'll have to check if this issue I found back then to see if it's actually present |
|
While you're at it, maybe you could also take the tests I added in #347 and add them. |
|
@greptile |
|
My tests still show some places where this implementation is incorrect, but these were issues that were present in the previous version of superjson too. To avoid this PR becoming even larger I think fixing all of these isn't a good idea. |
Skn0tt
left a comment
There was a problem hiding this comment.
What an involved fix! Looking good though. I left some remarks about performance.
About the failing test cases you found, could you commit them as failing tests? It's always better to know the bugs than not to :)
| if (isMap(parent)) { | ||
| const row = +path[path.length - 2]; | ||
| const keyToRow = getNthKey(parent, row); | ||
| const indexed = getIndexedKeys(parent, context); |
There was a problem hiding this comment.
i might be missing something - why can't this be getNthKey?
There was a problem hiding this comment.
We mutate indexed afterwards, we can replace this with getNthKey but then we would later have to write into context directly
Previously getNthKey might be called many times each time iterating through a full user provided collection.
By providing a large collection and a large referentialEqualities array you could cause quadratic compute.
For a 1mb payload (a common webserver limit) this can block the event loop for 20 - 40 seconds
Benchmarks (run on a Ryzen 7 5700X, Arch Linux)
Previous
After this change:
Greptile Overview
Greptile Summary
Fixes DoS vulnerability by replacing O(n²)
getNthKeyiteration with O(1) cached array lookups using a WeakMap context. Previously, deserializing large payloads with many referential equalities could block the event loop for 20-40 seconds; now completes in ~100ms. The fix introduces a caching layer that stores Map/Set keys in arrays, initialized during untransformation and maintained through mutations.Confidence Score: 4/5
src/accessDeep.tscache mutation logic (lines 135-185)Important Files Changed
getNthKeyiteration with O(1) cached array lookups, adds cache mutation logic to maintain consistency when Set/Map elements are updatedAccessDeepContextthrough value and referential equality annotation applicationSequence Diagram
sequenceDiagram participant Client participant deserialize participant applyValueAnnotations participant transformer participant applyReferentialEqualities participant accessDeep participant WeakMap as Context (WeakMap) Client->>deserialize: deserialize(json, meta) deserialize->>WeakMap: new WeakMap() alt has value annotations deserialize->>applyValueAnnotations: (result, meta.values, context) applyValueAnnotations->>transformer: untransformValue() for Set/Map transformer->>WeakMap: set(Set/Map, keys[]) Note over transformer,WeakMap: O(n) - cache keys once transformer-->>applyValueAnnotations: return untransformed Set/Map end alt has referential equalities deserialize->>applyReferentialEqualities: (result, meta.referentialEqualities, context) loop for each reference applyReferentialEqualities->>accessDeep: getDeep/setDeep accessDeep->>WeakMap: get(Set/Map) WeakMap-->>accessDeep: return cached keys[] Note over accessDeep,WeakMap: O(1) lookup (was O(n)) accessDeep->>accessDeep: access by index alt mutation needed accessDeep->>accessDeep: update Set/Map accessDeep->>WeakMap: update cached keys[] end end end deserialize-->>Client: return deserialized object