fix: add null guards, NaN checks, and try-catch to prevent runtime crashes#348
fix: add null guards, NaN checks, and try-catch to prevent runtime crashes#348LautaroPetaccio merged 5 commits intomainfrom
Conversation
Test this pull request
|
…ashes - Reorder entity-structure.ts pointer checks so null check runs before Set constructor - Add NaN-safe parseInt helper for all ADR timestamp env vars in timestamps.ts - Fix comment with wrong timestamp value for ADR_74_TIMESTAMP - Add optional chaining on scene.ts metadata.display and entity.content - Add null guards on profile.ts entity.content access - Wrap parseUrn calls in try-catch in profile.ts, outfits.ts, and utils.ts - Guard owners[0] access in subgraph scenes.ts for empty arrays - Add NaN validation after parseInt on scene pointer coordinates - Fix typo "state" -> "estate" in error message
- entity-structure: validate null/undefined pointers fail gracefully - scenes: validate non-numeric coordinates produce clear error messages - scenes: validate undefined content and display don't throw - profiles: validate undefined entity.content returns error not crash
a147db4 to
77789f0
Compare
decentraland-bot
left a comment
There was a problem hiding this comment.
Review: PR #348 — Null Guards, NaN Checks, and Try-Catch for Runtime Crashes
Verdict: ✅ APPROVE
Solid defensive hardening across the codebase. All changes are correct and prevent real crash scenarios.
Key Changes Reviewed
| File | Fix | Assessment |
|---|---|---|
entity-structure.ts |
Null check before new Set() |
✅ Correct — prevents TypeError on null/undefined pointers |
timestamps.ts |
parseTimestamp helper with NaN fallback |
✅ Correct — prevents silent validator disabling via misconfigured env vars |
scene.ts |
Optional chaining on display?.navmapThumbnail + content ?? [] |
✅ Correct |
profile.ts |
parseUrn try-catch + content ?? [] guards |
✅ Correct |
outfits.ts / utils.ts |
parseUrn try-catch |
✅ Correct |
subgraph/scenes.ts |
Empty owners guard + NaN coord check + "state"→"estate" typo | ✅ Correct |
on-chain/scenes.ts |
NaN coordinate check | ✅ Correct |
Findings
P2 — Important:
- [Pattern] The
parseUrntry-catch is repeated identically in 4 locations (profile.ts×2,outfits.ts,utils.ts). Consider extracting asafeParseUrn()utility inutils.tsto centralize this pattern. This would also align with the existingparseUrnNoFailfunctions found elsewhere in the codebase.
P3 — Nice-to-Have:
-
[Testing] No tests cover: (a)
parseTimestampwith NaN input, (b)parseUrnthrowing (vs returning null), (c) emptyownersarray in subgraph scenes. While these are defensive paths, adding at least one test per guard would prevent future regressions. -
[Testing] In
subgraph/scenes.ts, when NaN is detected the code callscontroller.abort()andbreak, but already-queued PQueue tasks may still execute. Consider callingqueue.clear()aftercontroller.abort()to prevent unnecessary subgraph queries. -
[Minor]
SCENE_VALIDATIONS_CONCURRENCYparsing (line 55 in subgraph/scenes.ts) could also benefit from the sameparseTimestamp-style NaN guard.
CI Status
All checks passing. Coverage increased +0.8%.
Requested by Lautaro Petaccio via Slack
… tests Address review feedback: - Extract safeParseUrn() in utils.ts to replace 4 identical inline try-catch blocks in profile.ts, outfits.ts, and utils.ts. Aligns with the existing parseUrnNoFail pattern used in stores.ts and items.ts. - Add NaN and non-positive guard to SCENE_VALIDATIONS_CONCURRENCY parsing, falling back to 10 if the env var is invalid or <= 0. - Call queue.clear() after controller.abort() when NaN coordinates are detected, so already-queued tasks don't run unnecessary subgraph queries. - Add tests for safeParseUrn (valid, unresolvable, malformed input). - Add tests for parseTimestamp (valid env var, invalid env var, empty string).
| if (parcel.owners.length > 0) { | ||
| if ( | ||
| await hasAccessThroughAuthorizations(parcel.owners[0].address, ethAddress, timestamp, tokenAddresses.land) | ||
| ) { |
There was a problem hiding this comment.
maybe this can be only one if, right?
Collapse the if/return/if/return chains for estate and parcel authorization back into single || expressions, with the owners.length guard inlined as a short-circuit condition. Also simplify the concurrency env var parsing to a single parseInt + NaN check.
Restructure all new tests to follow describe/it semantic naming: - describe blocks use "when"/"and" for context - it blocks use "should" for behavior - Variables and setup moved to beforeEach, not inside it() - afterEach added for mock cleanup - Scoped mocks to the describe context where they are used
Summary
Several code paths in the validator can crash with unhandled exceptions instead of returning proper validation errors. This PR adds defensive guards so the validator fails gracefully with clear error messages rather than throwing.
Why these changes are needed
entity-structure.ts: The null/empty check onentity.pointersruns afternew Set(entity.pointers), which throws aTypeErrorifpointersisnull. The null check is unreachable in the crash case. Reordering puts the null check first so it catches the problem before theSetconstructor runs.timestamps.ts: All 10 ADR timestamp constants are overridable via environment variables usingparseInt(), but none validate the result. If an env var is set to a non-numeric value (e.g."invalid"),parseIntreturnsNaN, and sinceNaN >= anyNumberis alwaysfalse, the correspondingvalidateAfterADRxxgate silently disables the wrapped validator. A single misconfigured env var likeADR_45_TIMESTAMP=invalidwould disable IPFS hashing, metadata schema, thumbnail, and content validation — with no log message. A sharedparseTimestamphelper now falls back to the default when the parsed value isNaN. The incorrect timestamp number in the ADR-74 comment is also fixed.scene.ts:metadata?.display.navmapThumbnailuses optional chaining onmetadatabut not ondisplay. Ifmetadataexists butdisplayisundefined, this throws. Similarly,entity.content.some(...)throws ifcontentisundefined. Added optional chaining on both.profile.ts:entity.content.map(...)at two call sites throws ifcontentisundefined. Other locations in the same file already guard withentity.content ?? []; these two were missed. Also,parseUrn()from@dcl/urn-resolveris async and can throw on malformed input, but the wearable and emote URN validators call it without try-catch. If it throws instead of returningnull, the entire validator crashes. Added try-catch that treats thrown exceptions the same as unresolvable URNs.outfits.tsandutils.ts: SameparseUrnthrow risk — wrapped in try-catch.subgraph/scenes.ts: The scene access validator accessesestate.owners[0].addressandparcel.owners[0].addresswithout checking if theownersarray has elements. The subgraph queries filter bycreatedAt_lte: $timestampwithfirst: 1, so if no ownership record exists at the queried timestamp, the array is empty and[0].addressthrows aTypeError. This crashes into theretry()loop (5 retries, 100ms each), then propagates as an internal error. The fix checksowners.lengthfirst and returnsfalse(no access) when empty. Also addsisNaNvalidation afterparseInton pointer coordinates so"abc,def"produces a clear "invalid pointer" error instead of passingNaNto GraphQL queries. The typo "state" in an error message is corrected to "estate".on-chain/scenes.ts: SameisNaNguard added for pointer coordinate parsing.Test plan
npm run buildcompiles cleanlynpm test— all 325 existing tests passnpm run lint:check— no errors🤖 Generated with Claude Code