subagent transcript
session 18e25685 · workflow wf_23df5b1c
delegated task
You are an adversarial verifier for a FreeCAD-wasm code review. A reviewer claims: SEVERITY: low CATEGORY: stale-state FILE: /home/magik6k/lcad-wasm/f
▸ Magik
You are an adversarial verifier for a FreeCAD-wasm code review. A reviewer claims:
SEVERITY: low
CATEGORY: stale-state
FILE: /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/WasmGLFixedFunc.cpp
SUMMARY: ff_setup_and_draw reuses a stale F._maxIndex when a client normal/color array is drawn with an indexed draw but the vertex array is a VBO.
DETAIL/EVIDENCE: WasmGLFixedFunc.cpp: _maxIndex is only recomputed when (isElements && vertClient) (lines 172-183, where vertClient requires the VERTEX array to be client memory). The client-array gather at line 149 uses maxV = F._maxIndex+1 for ANY client array (normal/color). If vertex is a VBO but normal or color is a client array in an indexed draw, _maxIndex is not refreshed this call and a stale value from a prior draw sizes the gather — under-sizing drops vertices, over-sizing reads past the client buffer. Coin typically keeps all arrays in the same residency so this is an edge case, but it is a real correctness gap.
RECOMMENDATION: Compute _maxIndex whenever isElements is true and ANY enabled array is client-side (not just the vertex array), or derive per-array bounds.
Independently verify by reading the actual file(s) and running git/grep as needed (repo at /home/magik6k/lcad-wasm/freecad-port/FreeCAD, toolchain at /opt/toolchains, deploy at /home/magik6k/lcad-wasm/freecad-port/deploy-gui). Default to REFUTED if you cannot confirm the evidence. Adjust severity to what the evidence actually supports.
Fable
thinking
I'll check the file to confirm that.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/WasmGLFixedFunc.cpp", "limit": 220}
Fable
thinking
Looking at the issue more carefully, `_maxIndex` only gets recomputed under specific conditions at line 172 when both `isElements` and `vertClient` are true, meaning the vertex array must be in client memory. But the gather operation at line 149 uses `_maxIndex` for any client-side array through the `bind` function, which gets called whenever an array isn't GPU-backed—so it applies to vertex, normal, and color arrays regardless of where the vertex data lives.
This creates the stale value problem: if the vertex array is a VBO (GPU-backed), the recomputation block never runs and `_maxIndex` keeps whatever value it had from a previous draw call. Yet if normal or color arrays are client-side, t …
Fable
⚙ Bash {"command": "grep -n \"_maxIndex\\|_count\\|glbuf\" /home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/WasmGLFixedFunc.cpp"}
Fable
thinking
So `_maxIndex` only gets set when both vertex data is in a VBO and the normal/color data is client-side, which means in indexed draws with mixed storage types, that value stays stale or undefined and causes the gather to size incorrectly. Now I'm looking at the per-array residency logic around line 357 to confirm that arrays can independently be stored as either client data or VBOs.
Fable
⚙ Read {"file_path": "/home/magik6k/lcad-wasm/freecad-port/FreeCAD/src/Gui/WasmGLFixedFunc.cpp", "limit": 15, "offset": 345}
Fable
thinking
Each array's `glbuf` gets snapshotted independently when the pointer is set, based on whatever ARRAY_BUFFER was bound at that moment, so residency is tracked per-array. This means vertex data could be in a VBO while normal or color data remains client-side, or any other combination. I'm tracing through the code to confirm this scenario: when doing an element draw with the vertex buffer set to a VBO, `vertClient` becomes false, which skips the recomputation block at line 172, leaving `_maxIndex` stale.
Then when normal or color data is client-side, the bind operation takes the client path and uses that stale `_maxIndex` value. If `_maxIndex` was never set initially, it's undefined, which bec …
Fable
The claim is accurate. Verified:
- Line 149 (`maxV = isElements ? F._maxIndex+1 : nVerts`) sizes the client-array gather for ANY array taking the client path (`!spec.glbuf`), inside the shared `bind` helper called for vertex, normal, and color.
- Line 169/172: `vertClient` requires the VERTEX array specifically to be client (`!F.arrays.vertex.glbuf`), and `_maxIndex` is recomputed only under `if (isElements && vertClient)`.
- `grep` confirms `_maxIndex` is assigned nowhere else (only lines 174/179/181, all inside that guarded block).
- Lines 351-357 (`ffPointer`) snapshot `glbuf` per-array from the currently bound `ARRAY_BUFFER`, so residency is independent per array — vertex can be a VBO while normal/color are client memory.
So in an indexed draw where vertex is a VBO but normal or color is a client array, the guard is skipped and the gather at line 149 uses a stale (or `undefined` → NaN → zero-length) `F._maxIndex` from a prior call. Under-sizing drops vertices; over-sizing reads past the client buffer. Real gap, correctly bounded as an edge case since Coin usually keeps all arrays in the same residency. Low severity is appropriate.
Fable
⚙ StructuredOutput {"verdict": "CONFIRMED", "reason": "Code matches the claim exactly. The shared bind() helper (line 149) sizes the client-array gather with F._maxIndex+1 for ANY enabled client array (normal or color), but _maxIndex is recomputed only under …