Skip to content

fix(nft): address review findings before 0.10.0 - #80

Merged
kalwalt merged 4 commits into
devfrom
fix/nft-review-0.10.0
Oct 6, 2026
Merged

kalwalt merged 4 commits into
devfrom
fix/nft-review-0.10.0

Conversation

@kalwalt

@kalwalt kalwalt commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Fixes three findings Qodo raised on the release PR #79, before tagging 0.10.0. All three are in code that came with #76.

Summary

  • a49cb73 – trackingInitMain null check: after threadGetArg() the guard re-tested threadHandle, so a NULL trackingInitHandle was dereferenced on the worker thread.

  • faa50a6 – cap the decompressed .zft size: inflateAll() grew its buffer without bound, so a small archive with a huge expansion ratio could exhaust memory. Archives expanding past MARKER_DECOMPRESS_MAX_SIZE (128 MB, overridable with -D) now return -1. Real markers are a few MB. markerDecompress.h now documents the return values, and that outTemp should name new files.

  • dd3e7d8 – AR2_CAPABLE_ADAPTIVE_TEMPLATE: the Mod tracking path never supported adaptive templates; with the option on, trackingMod2d.c did not compile. trackingMod.h now stops the build with an explicit #error, and trackingMod2d.c includes it. The option is 0 in config.h, so default builds are unaffected.

  • 7506d59 – limit edge cases (Qodo on this PR): the initial buffer is capped at the limit, so limits below 4 MB are enforced; at the limit zlib gets one spare byte to finish the stream, so an archive of exactly the limit is accepted. New webarkit_nft_limit_test (markerDecompress built with a 1 MB limit), run in CI.

Not changed, with reasons in the review threads on #79:

  • overwriting an existing marker set on a failed extraction: callers extract to fresh temporary paths; documented instead
  • NUL bytes in archive data: .zft payloads are hex text, so an archive with a NUL is malformed and -1 is correct

Test plan

  • Native (Ubuntu 24.04, GCC 13): webarkit_nft_test 13/13 (new: ArchiveExpandingPastTheLimitReturnsError), webarkit_test 28/28
  • webarkit_nft_limit_test 3/3 (2 of them fail on dd3e7d8)
  • With AR2_CAPABLE_ADAPTIVE_TEMPLATE 1: trackingMod.c and trackingMod2d.c stop at the #error
  • Emscripten 4.0.17: WebARKitNFT builds with and without threads
  • examples/DataNFT/zft/pinball.zft output still byte-identical to the original implementation
  • jsartoolkitNFT examples against this branch (refactor: use the NFT helpers from WebARKitLib (#453) jsartoolkitNFT#687)

🤖 Generated with Claude Code

kalwalt and others added 3 commits October 6, 2026 18:58
After threadGetArg() the guard re-tested threadHandle, so a NULL trackingInitHandle was dereferenced on the worker thread instead of logged. Reported by Qodo on #79.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
inflateAll() doubled its buffer with no upper bound, so a small archive with a huge expansion ratio could exhaust memory. Archives expanding past MARKER_DECOMPRESS_MAX_SIZE (128 MB, overridable at build time) are now rejected with -1. Also documents that decompressMarkers() should write to new paths: an existing marker set there is not preserved on failure. Reported by Qodo on #79.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Mod tracking path never supported adaptive templates: with the option on, trackingMod2d.c did not compile (mismatched signature, redeclared templ2). trackingMod.h now stops the build with an explicit #error, and trackingMod2d.c includes it so it uses the public declaration of ar2Tracking2dSubMod(). Reported by Qodo on #79.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix NFT tracking guards and bound .zft decompression

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Prevent a null worker-thread argument from being dereferenced.
• Reject .zft archives that expand beyond a configurable 128 MB limit.
• Clearly reject unsupported adaptive-template builds and document marker extraction failure
 behavior.
Diagram

graph TD
  Z["ZFT archive"] --> D["decompressMarkers"] --> I["Bounded inflate"] --> L{"Within size limit?"} --> P["Parse markers"] --> F["Marker files"]
  L -- "No" --> E["Return -1"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Stream decompression and extraction
  • ➕ Could reduce peak memory use by avoiding a complete inflated buffer.
  • ➖ Requires incremental parsing and more complex partial-file cleanup.
  • ➖ Is a substantially broader change than these release fixes.

Recommendation: Keep the bounded-buffer approach for this release: it addresses excessive expansion while preserving the existing extraction flow. Streaming extraction is worth considering separately if ordinary marker sizes or peak memory use warrant it.

Files changed (6) +67 / -8

Bug fix (5) +30 / -3
markerDecompress.hExpose the decompression limit and document extraction failures +11/-0

Expose the decompression limit and document extraction failures

• Defines an overridable 128 MB maximum for inflated .zft data. Documents return values and warns that failed extraction does not preserve marker files already at the output path.

WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h

trackingMod.hReject unsupported adaptive-template builds explicitly +4/-0

Reject unsupported adaptive-template builds explicitly

• Adds a compile-time error when adaptive templates are enabled for the Mod tracking path, replacing less clear compilation failures.

WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/trackingMod.h

markerDecompress.cCap inflated .zft data before extraction +13/-2

Cap inflated .zft data before extraction

• Stops buffer growth at the configured limit and returns an error if inflation needs more space. This prevents highly compressible archives from driving unbounded allocation.

WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c

trackingMod2d.cApply the Mod tracking declaration and compile-time guard +1/-0

Apply the Mod tracking declaration and compile-time guard

• Includes the public Mod tracking header so this translation unit receives the unsupported-option error and the function declaration.

WebARKit/WebARKitTrackers/WebARKitNFT/trackingMod2d.c

trackingSub.cCheck the worker-thread argument before dereferencing it +1/-1

Check the worker-thread argument before dereferencing it

• Corrects the post-threadGetArg guard to test trackingInitHandle rather than rechecking threadHandle.

WebARKit/WebARKitTrackers/WebARKitNFT/trackingSub.c

Tests (1) +37 / -5
webarkit_nft_test.ccCover rejection of oversized .zft archives +37/-5

Cover rejection of oversized .zft archives

• Generates a highly compressible archive whose expanded payload exceeds the limit, then checks that extraction fails without creating an .iset file. Moves the file-existence helper alongside the other test helpers.

tests/webarkit_nft_test.cc

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Smaller archive limits can be bypassed ✓ Resolved
Description
inflateAll() starts with a fixed 4 MiB output buffer and checks inflate_max only when that
buffer fills. If MARKER_DECOMPRESS_MAX_SIZE is configured below 4 MiB, an archive that expands
past the configured limit but stays within the initial buffer can be extracted successfully.
Code

WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c[45]

+            if (cap >= inflate_max) {
Evidence
The new header allows a build-time limit override. The implementation allocates 4 MiB before
consulting that limit, offers the entire buffer to zlib, and passes successfully inflated data to
extraction.

WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h[6-9]
WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c[29-31]
WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c[43-60]
WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c[116-121]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A configured decompression limit below 4 MiB is not enforced because inflation starts with a 4 MiB buffer.
## Fix Focus Areas
- WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c[17-54]
## Recommended Fix
Initialize output capacity no larger than the configured limit, validate the limit, and add a test using an override below 4 MiB.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Archives at the size limit can fail ✓ Resolved
Description
inflateAll() rejects whenever its output buffer is full at inflate_max, before allowing zlib to
finish reading the stream. A valid stream that produces exactly the permitted number of bytes but
still has an empty final block or trailer to process returns an error rather than extracting its
markers.
Code

WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c[R45-48]

+            if (cap >= inflate_max) {
+                ARLOGe("Error: .zft data expands past %zu bytes\n", inflate_max);
+                ret = Z_MEM_ERROR;
+                break;
Evidence
The header specifies that archives larger than the limit are rejected. The new check instead rejects
a full buffer before another inflate call can process a stream ending that emits no further output.

WebARKit/WebARKitTrackers/WebARKitNFT/include/WebARKitTrackers/WebARKitNFT/markerDecompress.h[6-8]
WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c[41-64]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A stream producing exactly the configured maximum can be rejected if zlib has not yet processed its end marker when the output buffer fills.
## Fix Focus Areas
- WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c[43-64]
## Recommended Fix
When output reaches the limit, permit zlib to finish using at most one additional output byte; reject only if it produces output beyond the maximum. Test a valid stream that produces exactly the limit before its final block.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c
Comment thread WebARKit/WebARKitTrackers/WebARKitNFT/markerDecompress.c Outdated
@kalwalt kalwalt self-assigned this Oct 6, 2026
@kalwalt kalwalt added bug Something isn't working enhancement New feature or request C/C++ code concerning the C/C++ code design and improvements Emscripten labels Oct 6, 2026
…g at the limit

- the initial inflate buffer is now capped at MARKER_DECOMPRESS_MAX_SIZE, so limits below 4 MB are enforced
- at the limit, zlib gets one spare output byte to finish the stream (final block, trailer); only output written there means the archive is too large
- MARKER_DECOMPRESS_MAX_SIZE below 1 is a build error
- new webarkit_nft_limit_test builds markerDecompress with a 1 MB limit: exactly at the limit, one byte over, and a limit below the initial buffer; run in CI
Reported by Qodo on #80.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kalwalt
kalwalt merged commit b5e4770 into dev Oct 6, 2026
1 check passed
@kalwalt kalwalt mentioned this pull request Oct 6, 2026
kalwalt added a commit to webarkit/jsartoolkitNFT that referenced this pull request Oct 7, 2026
- emscripten/WebARKitLib: b5e4770 on WebARKitLib dev, which adds
  webarkit/WebARKitLib#80 (review fixes before 0.10.0: capped .zft
  decompression, trackingInitMain null check, explicit #error for
  AR2_CAPABLE_ADAPTIVE_TEMPLATE)
- build/ and dist/ rebuilt with emsdk 4.0.17 (npm run build-docker,
  npm run build-ts); vitest 182 passed / 6 skipped, node 7/7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kalwalt added a commit to webarkit/jsartoolkitNFT that referenced this pull request Oct 7, 2026
* refactor: use the NFT helpers from WebARKitLib (#453)

trackingMod, trackingMod2d, trackingSub, markerDecompress and
NFTMarkerState moved to WebARKitLib (webarkit/WebARKitLib#75, #76) under
WebARKit/WebARKitTrackers/WebARKitNFT.

- tools/makem.js: take those sources from WebARKitLib and add its include
  directory
- bindings (JS and Python): include <WebARKitTrackers/WebARKitNFT/...>
- python-bindings/setup.py: new source and include paths
- remove the copies from emscripten/
- bump the WebARKitLib submodule to feat/issue-75-webarkit-nft

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: report a failed .zft decompression through onError

decompressMarkers() now returns -1 for a missing or malformed archive instead of exiting. The decompressZFT bindings returned 1 regardless, so loadZFT read temporary files that were never written and threw ENOENT before any callback ran. The bindings now return -1 on failure and loadZFT calls onError(prefix + ".zft"). Reported by Qodo on #687.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf: free the .zft archive from MEMFS once decompressed

loadZFT kept /markerNFT_N.zft in the Emscripten filesystem for the whole session; the old C-side remove() built the name without the dot, so it never deleted it either. Suggested by Qodo on #687.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: bump WebARKitLib to dev and rebuild build/ and dist/ (#453)

- emscripten/WebARKitLib: dfe8570 on WebARKitLib dev, the squash merge of
  webarkit/WebARKitLib#76 (same tree as the 480fdb9 used so far)
- build/ and dist/ rebuilt with emsdk 4.0.17 (npm run build-docker,
  npm run build-ts)

The earlier commits of this PR (0cff312, 3543c45, 00cf36e) changed
C/C++, JS and TS sources without committing the rebuilt build/ and dist/;
this commit brings them up to date with those changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: bump WebARKitLib to b2d6166 and rebuild build/ and dist/

- emscripten/WebARKitLib: b2d6166 on WebARKitLib dev, which adds
  webarkit/WebARKitLib#77 (#40: random template index computed in double
  precision in AR2/selectTemplate.c, fixing the
  -Wimplicit-const-int-float-conversion warning)
- build/ and dist/ rebuilt with emsdk 4.0.17 (npm run build-docker,
  npm run build-ts); vitest 182 passed / 6 skipped, node 7/7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: bump WebARKitLib to b5e4770 and rebuild build/ and dist/

- emscripten/WebARKitLib: b5e4770 on WebARKitLib dev, which adds
  webarkit/WebARKitLib#80 (review fixes before 0.10.0: capped .zft
  decompression, trackingInitMain null check, explicit #error for
  AR2_CAPABLE_ADAPTIVE_TEMPLATE)
- build/ and dist/ rebuilt with emsdk 4.0.17 (npm run build-docker,
  npm run build-ts); vitest 182 passed / 6 skipped, node 7/7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: pin WebARKitLib to the 0.10.0 release tag

Move emscripten/WebARKitLib from b5e4770 (dev) to 4fd3034, the
merge commit on master tagged 0.10.0, as the 0.9.0 pin pointed at
master. Both commits have the same tree, so build/ and dist/ are
unchanged and need no rebuild.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working C/C++ code concerning the C/C++ code design and improvements Emscripten enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant