🔍 Duplicate Code Pattern: tryCallWasmFunction Dual-Path Result Handling
Part of duplicate code analysis: #6383
Summary
tryCallWasmFunction in internal/guard/wasm_parse.go contains two execution paths (allocator path and direct-memory fallback path) that share ~30 lines of identical result-handling logic. Any bug fix or protocol change applied to one path must be manually mirrored in the other.
Duplication Details
Pattern: Duplicated WASM call + result-decode block
- Severity: High
- Occurrences: 2 (allocator path ~lines 220–251, direct path ~lines 292–333)
- Locations:
internal/guard/wasm_parse.go (allocator path, lines ~220–251)
internal/guard/wasm_parse.go (direct memory path, lines ~292–333)
Identical block in both paths:
results, err := fn.Call(ctx,
uint64(inputPtr),
uint64(inputSize),
uint64(outputPtr),
uint64(outputSize))
if err != nil {
return nil, 0, fmt.Errorf("WASM function call failed: %w", err)
}
resultLen := int32(results[0])
if resultLen == -2 {
if requiredSize, ok := mem.ReadUint32Le(outputPtr); ok && requiredSize > 0 {
return nil, requiredSize, nil
}
return nil, 0, nil
}
if resultLen < 0 {
return nil, 0, fmt.Errorf("WASM function returned error code: %d", resultLen)
}
if resultLen == 0 {
return []byte{}, 0, nil
}
outputJSON, ok := mem.Read(outputPtr, uint32(resultLen))
if !ok {
return nil, 0, fmt.Errorf("failed to read output from WASM memory (len=%d)", resultLen)
}
resultCopy := append([]byte(nil), outputJSON...)
return resultCopy, 0, nil
The two paths differ only in how inputPtr and outputPtr are set up (via wasmAlloc vs. direct linear memory layout). The actual call + result handling is character-for-character identical.
Impact Analysis
- Maintainability: Any change to the buffer-too-small protocol (
-2 return code), error code interpretation, or output-copy strategy requires the same edit in two places. The recent callWasmFunction test coverage work explicitly highlighted this function's complexity.
- Bug Risk: High — a fix applied only to one path silently leaves the other broken. Already seen: the allocator path has a comment
// Copy out of WASM linear memory before deferred dealloc runs. while the direct path says // to avoid aliasing with future calls. — slight comment drift indicating the paths are evolving independently.
- Code Bloat: ~30 duplicated lines, making the 148-line function harder to audit.
Refactoring Recommendations
-
Extract executeWasmCall helper
- Create a private function:
func executeWasmCall(ctx context.Context, fn api.Function, mem api.Memory, inputPtr, inputSize, outputPtr, outputSize uint32) ([]byte, uint32, error)
- Move the shared call + result-decode block into this helper
- Both paths call this helper after setting up their respective pointers
- Estimated effort: ~1 hour
- Benefits: single place to maintain buffer protocol logic; easier to test in isolation
-
Add a dedicated unit test for executeWasmCall
- The new helper encapsulates all result-code handling; it can be tested directly without full WASM module setup
Implementation Checklist
Parent Issue
See parent analysis report: #6383
Related to #6383
Generated by Duplicate Code Detector · ● 1.2M · ◷
🔍 Duplicate Code Pattern:
tryCallWasmFunctionDual-Path Result HandlingPart of duplicate code analysis: #6383
Summary
tryCallWasmFunctionininternal/guard/wasm_parse.gocontains two execution paths (allocator path and direct-memory fallback path) that share ~30 lines of identical result-handling logic. Any bug fix or protocol change applied to one path must be manually mirrored in the other.Duplication Details
Pattern: Duplicated WASM call + result-decode block
internal/guard/wasm_parse.go(allocator path, lines ~220–251)internal/guard/wasm_parse.go(direct memory path, lines ~292–333)Identical block in both paths:
The two paths differ only in how
inputPtrandoutputPtrare set up (viawasmAllocvs. direct linear memory layout). The actual call + result handling is character-for-character identical.Impact Analysis
-2return code), error code interpretation, or output-copy strategy requires the same edit in two places. The recentcallWasmFunctiontest coverage work explicitly highlighted this function's complexity.// Copy out of WASM linear memory before deferred dealloc runs.while the direct path says// to avoid aliasing with future calls.— slight comment drift indicating the paths are evolving independently.Refactoring Recommendations
Extract
executeWasmCallhelperfunc executeWasmCall(ctx context.Context, fn api.Function, mem api.Memory, inputPtr, inputSize, outputPtr, outputSize uint32) ([]byte, uint32, error)Add a dedicated unit test for
executeWasmCallImplementation Checklist
fn.Call+ result-decode logic intoexecuteWasmCallhelperwasmAllocwasmDealloccalls still run correctlymake test-all)Parent Issue
See parent analysis report: #6383
Related to #6383