Title: [Bug] assert.strictEqual OOM on objects with deeply shared references · Issue #61346 · nodejs/node · GitHub
Open Graph Title: [Bug] assert.strictEqual OOM on objects with deeply shared references · Issue #61346 · nodejs/node
X Title: [Bug] assert.strictEqual OOM on objects with deeply shared references · Issue #61346 · nodejs/node
Description: Version v25.2.1 Platform Darwin Hafezs-MacBook-Pro.local 25.2.0 Darwin Kernel Version 25.2.0: Tue Nov 18 21:09:40 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6000 arm64 Subsystem assert What steps will reproduce the bug? // Run with:...
Open Graph Description: Version v25.2.1 Platform Darwin Hafezs-MacBook-Pro.local 25.2.0 Darwin Kernel Version 25.2.0: Tue Nov 18 21:09:40 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6000 arm64 Subsystem assert What st...
X Description: Version v25.2.1 Platform Darwin Hafezs-MacBook-Pro.local 25.2.0 Darwin Kernel Version 25.2.0: Tue Nov 18 21:09:40 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6000 arm64 Subsystem assert What st...
Opengraph URL: https://github.com/nodejs/node/issues/61346
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"[Bug] assert.strictEqual OOM on objects with deeply shared references","articleBody":"### Version\n\nv25.2.1\n\n### Platform\n\n```text\nDarwin Hafezs-MacBook-Pro.local 25.2.0 Darwin Kernel Version 25.2.0: Tue Nov 18 21:09:40 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6000 arm64\n```\n\n### Subsystem\n\nassert\n\n### What steps will reproduce the bug?\n\n```javascript\n// Run with: node --max-old-space-size=512 repro.js\n'use strict';\nconst assert = require('assert');\n\n// Create an object graph where many unique paths converge on shared objects.\n// This delays circular reference detection and creates exponential growth\n// in util.inspect output at high depths.\n\nfunction createBase() {\n const base = { id: 'base', models: {}, schemas: {}, types: {} };\n for (let i = 0; i \u003c 5; i++) {\n base.types[`type_${i}`] = {\n name: `type_${i}`,\n base,\n caster: { base, name: `type_${i}_caster` },\n options: {\n base,\n validators: [\n { base, name: 'v1' },\n { base, name: 'v2' },\n { base, name: 'v3' },\n ],\n },\n };\n }\n return base;\n}\n\nfunction createSchema(base, name) {\n const schema = { name, base, paths: {}, tree: {}, virtuals: {} };\n for (let i = 0; i \u003c 10; i++) {\n schema.paths[`field_${i}`] = {\n path: `field_${i}`,\n schema,\n instance: base.types[`type_${i % 5}`],\n options: {\n type: base.types[`type_${i % 5}`],\n validators: [\n { validator: () =\u003e true, base, schema },\n { validator: () =\u003e true, base, schema },\n ],\n },\n caster: base.types[`type_${i % 5}`].caster,\n };\n }\n schema.childSchemas = [];\n for (let i = 0; i \u003c 3; i++) {\n const child = { name: `${name}_child_${i}`, base, schema, paths: {} };\n for (let j = 0; j \u003c 5; j++) {\n child.paths[`child_field_${j}`] = {\n path: `child_field_${j}`,\n schema: child,\n instance: base.types[`type_${j % 5}`],\n options: { base, schema: child },\n };\n }\n schema.childSchemas.push(child);\n }\n return schema;\n}\n\nconst base = createBase();\nconst schema1 = createSchema(base, 'Schema1');\nconst schema2 = createSchema(base, 'Schema2');\n\n// These are different objects, so comparison should fail with AssertionError\nassert.strictEqual(schema1, schema2);\n```\n\n### How often does it reproduce? Is there a required condition?\n\n100% reproducible. The condition is having objects with many unique paths that converge on shared objects (common in ORMs like Mongoose where schemas reference a shared \"base\" object).\n\n### What is the expected behavior? Why is that the expected behavior?\n\n`assert.strictEqual` should throw an `AssertionError` with a diff showing the objects are not reference-equal. The error message can be truncated if very large, but the process should not crash.\n\n### What do you see instead?\n\nThe process crashes with OOM:\n```\nFATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory\n```\n\nThe OOM happens because `util.inspect` expands each unique path through the object graph separately. For objects where many paths converge on shared objects (common in ORMs), the number of unique paths grows exponentially. With `depth: 1000`, the expansion runs long enough to produce 100+ MB strings before hitting circular reference markers.\n\n### Additional information\n\nThis was discovered while working with Mongoose documents. The issue is in `lib/internal/assert/assertion_error.js` where `inspectValue()` has no output size limit.\n\n**Potential fixes:**\n\n1. **Truncate inspect output** (e.g., at 2MB) before passing to the diff algorithm\n2. **Skip diff entirely for huge objects** - just show \"Objects are not equal (output too large to diff)\"\n3. **Smarter truncation** - find where objects differ first, truncate around that area. However, this is complex to implement and the performance impact is probably negative compared to current solution: for objects with exponential paths, even traversing them to find differences could be expensive, and the subtree around the difference may still contain references to shared objects that cause the same explosion.\n\n**Trade-off with truncation:**\n\nIf both objects produce very large inspect output and happen to match exactly in the first 2MB but differ later, the diff would appear identical even though the assertion failed. However:\n- The alternative is an OOM crash, which is worse\n- The assertion result is still correct (`===` failed)\n- A truncation marker (`... [truncated]`) indicates output was cut off\n- Users can examine `error.actual` and `error.expected` programmatically for further investigation\n\nI'm happy to submit a PR with a fix.","author":{"url":"https://github.com/AbdelrahmanHafez","@type":"Person","name":"AbdelrahmanHafez"},"datePublished":"2026-01-11T17:51:21.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/61346/node/issues/61346"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:853b0652-c33a-57b0-1625-fdc80b9422cd |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | B128:29A342:281C9A1:34DEEC8:696485C4 |
| html-safe-nonce | 3758e63fce9ecbd2cc6c1fd4c86ee87ac2a04ae140f80ac56d753ee59890475a |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCMTI4OjI5QTM0MjoyODFDOUExOjM0REVFQzg6Njk2NDg1QzQiLCJ2aXNpdG9yX2lkIjoiNjEyNjc5NzAxMDE1Nzk5NTQ2MCIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 86283c72babc65f6d41fad0edd8f84e9c7ab2a4cda7bdb49d4020a0e2eadfb7f |
| hovercard-subject-tag | issue:3801712581 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/61346/issue_layout |
| twitter:image | https://opengraph.githubassets.com/dc84bef7a9fc5d1aac48b1b1ff3397fe6a602a1f2743db37b3ad0ee7a60eef6d/nodejs/node/issues/61346 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/dc84bef7a9fc5d1aac48b1b1ff3397fe6a602a1f2743db37b3ad0ee7a60eef6d/nodejs/node/issues/61346 |
| og:image:alt | Version v25.2.1 Platform Darwin Hafezs-MacBook-Pro.local 25.2.0 Darwin Kernel Version 25.2.0: Tue Nov 18 21:09:40 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6000 arm64 Subsystem assert What st... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | AbdelrahmanHafez |
| hostname | github.com |
| expected-hostname | github.com |
| None | baa7d9900fdf7b27d604f36887af878d569cfbdcf97126832a5f4f0caf0c6ba5 |
| turbo-cache-control | no-preview |
| go-import | github.com/nodejs/node git https://github.com/nodejs/node.git |
| octolytics-dimension-user_id | 9950313 |
| octolytics-dimension-user_login | nodejs |
| octolytics-dimension-repository_id | 27193779 |
| octolytics-dimension-repository_nwo | nodejs/node |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 27193779 |
| octolytics-dimension-repository_network_root_nwo | nodejs/node |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 842eff1d11f899d02b6b3b98fa3ea4860e64b34e |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width