Skip to content

fix: prevent panic in uniqueItems validation for unhashable types (#1042)#1045

Merged
wolveix merged 2 commits into
danielgtaylor:mainfrom
ArdyJunata:fix/uniqueitems-panic-unhashable-type
Jul 12, 2026
Merged

fix: prevent panic in uniqueItems validation for unhashable types (#1042)#1045
wolveix merged 2 commits into
danielgtaylor:mainfrom
ArdyJunata:fix/uniqueitems-panic-unhashable-type

Conversation

@ArdyJunata

Copy link
Copy Markdown
Contributor

Summary

Fixes #1042

Problem

The uniqueItems check in handleArray used map[any]struct{} as a hash set. When array items contain non-hashable types such as map[string]interface{} — produced by malformed JSON like [{}] for a []string field — inserting into the map panics:

http: panic serving [::1]:59220: hash of unhashable type: map[string]interface {}

This caused the server to return no response at all instead of a 422 Unprocessable Entity. The panic kills the goroutine serving the request.

Reproduction:

curl -X POST -H 'Content-Type: application/json' \
  -d '{"arrayOfStrings": [{}]}' \
  http://localhost:8888/demo
# Server logs: panic serving ...: hash of unhashable type: map[string]interface {}

Fix

Introduce a uniqueItemsContains helper that:

  1. Fast path: attempts an O(1) map[any] lookup (works for all hashable types — strings, ints, bools, etc.)
  2. Slow path: a deferred recover() catches the panic for unhashable types and falls back to an O(n) reflect.DeepEqual linear scan

This means:

  • ✅ Normal arrays of hashable items → same O(n) performance as before
  • ✅ Arrays with maps/slices → graceful O(n²) fallback, no panic
  • ✅ Duplicate unhashable objects → still detected correctly
  • ✅ Malformed [{}] for []string → proper 422 expected string error

Tests

Three regression tests added to validate_test.go:

Test Verifies
uniqueItems with unhashable object element must not panic [{}] for []string returns 422, not a panic
uniqueItems detects duplicate unhashable objects [{"a":1},{"a":1}] is still detected as non-unique
uniqueItems with mixed hashable and unhashable elements ["ok", {"k":"v"}] does not panic

All existing tests continue to pass.

Fixes danielgtaylor#1042

The uniqueItems check in handleArray used map[any]struct{} as a hash
set. When array items contain non-hashable types such as
map[string]interface{} (produced by malformed JSON like [{"k":"v"}]
for a []string field), inserting into the map panics with:

  http: panic serving ...: hash of unhashable type: map[string]interface {}

This caused the server to return no response at all — not even a 500.

Fix: introduce a uniqueItemsContains helper that attempts the O(1) map
lookup on the fast path. A deferred recover() catches the panic for
unhashable types and falls back to an O(n) reflect.DeepEqual linear
scan, which:
  - Never panics regardless of element type
  - Still detects duplicate unhashable values correctly
  - Has no impact on the common case (hashable types hit the fast path)

Malformed input like [{"k":"v"}] for a []string field now correctly
returns a 422 'expected string' validation error instead of crashing.

Regression tests added in validate_test.go:
  - uniqueItems with unhashable object element must not panic
  - uniqueItems detects duplicate unhashable objects
  - uniqueItems with mixed hashable and unhashable elements must not panic
@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.15%. Comparing base (2b69054) to head (c15e7c9).
⚠️ Report is 10 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1045      +/-   ##
==========================================
+ Coverage   93.14%   93.15%   +0.01%     
==========================================
  Files          23       23              
  Lines        4901     4955      +54     
==========================================
+ Hits         4565     4616      +51     
- Misses        272      275       +3     
  Partials       64       64              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The uniqueItems fix rebuilt a fresh map from all previously-seen items on
every element, making the check O(n^2) for every uniqueItems array (not just
the unhashable edge case). Since array length is client-controlled, that's a
quadratic-blowup vector.

Grow a single map incrementally for hashable items (O(1) lookups, the common
case) and only fall back to a reflect.DeepEqual scan for genuinely unhashable
items (maps/slices), preserving the recover-based guard from danielgtaylor#1042.
@wolveix

wolveix commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Thanks for tracking this down, @ArdyJunata! The panic fix is correct and the three regression tests cover the right cases.

I pushed a performance follow-up directly to your branch. uniqueItemsContains rebuilt a fresh map[any] from all previously-seen items on every element, which made the check O(n²) for every uniqueItems array, not only the unhashable case.

Benchmarks before and after the changes:

┌───────┬────────────────┬──────────────┬────────────────────┐
│ items │ before this PR │ with this PR │ with the follow-up │
├───────┼────────────────┼──────────────┼────────────────────┤
│ 1,000 │ 0.03 ms        │ 8.5 ms       │ 0.03 ms            │
├───────┼────────────────┼──────────────┼────────────────────┤
│ 4,000 │ 0.10 ms        │ 149 ms       │ 0.13 ms            │
└───────┴────────────────┴──────────────┴────────────────────┘

The follow-up keeps your recover approach; it grows a single map incrementally for hashable items (O(1) lookups, the common case) and only falls back to the reflect.DeepEqual scan for genuinely unhashable items.

@wolveix wolveix merged commit 4e53fef into danielgtaylor:main Jul 12, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validation of uniqueItems causes panic when input is not the expected type

2 participants