Skip to content

interpreter: reject duplicate keys in map literals#1363

Open
LeSingh1 wants to merge 1 commit into
cel-expr:masterfrom
LeSingh1:fix-map-literal-duplicate-keys
Open

interpreter: reject duplicate keys in map literals#1363
LeSingh1 wants to merge 1 commit into
cel-expr:masterfrom
LeSingh1:fix-map-literal-duplicate-keys

Conversation

@LeSingh1

Copy link
Copy Markdown

Fixes #1348

Problem

A map literal that contains duplicate keys was evaluated without error; the last value silently won instead of failing as the spec requires:

{1: "a", 1: "b"}         // -> {1: "b"}, no error
{"k": "x", "k": "y"}     // -> {"k": "y"}, no error
{true: 1, false: 2, true: 3}[true]   // -> 3, no error

The CEL language spec ("Aggregate Values") states: "It is an error to have duplicate keys or field names." Both cel-cpp and cel-java reject such expressions, so cel-go was inconsistent with the spec and the other implementations.

The inconsistency also exists within cel-go: the comprehension map-building path already reports the collision via mutableMap.Insert ("insert failed: key ... already exists"), but the map-literal evaluation path (evalMap.Exec) wrote into a plain Go map[ref.Val]ref.Val and never checked for duplicates.

Keys are not necessarily constant — they can be computed at runtime (e.g. {("rol" + "e"): "admin", "role": "guest"}), so the duplicate is not always visible to the type checker and must be detected during evaluation.

Fix

evalMap.Exec now rejects duplicate keys, mirroring the check already performed by mutableMap.Insert. Keys are compared using CEL equality, so equal values of different runtime types (e.g. the int 1 and the uint 1u) collide even though they are distinct Go map keys. The error is labeled with the map's AST node id, consistent with the other errors produced by evalMap.Exec.

The optional-entry path is unaffected: an entry whose optional value is absent removes the key and is not treated as a duplicate.

Verification

  • Added regression tests in interpreter/interpreter_test.go covering same-type, string, heterogeneous ({0: 1, 0u: 2}), and runtime-computed duplicate keys, plus a negative case ensuring distinct heterogeneous keys still work. These run under the default, optimize (constant-folding), exhaustive, and track planner modes, and assert the error carries an AST node id.
  • Confirmed the tests fail before the change (returning the last-wins value) and pass after it.
  • This makes cel-go pass the previously-skipped conformance tests fields/qualified_identifier_resolution/map_value_repeat_key and map_value_repeat_key_heterogeneous, which are removed from the skip list in conformance/BUILD.bazel. Both expressions were verified to type-check (they have no disable_check) and to error at evaluation through the full cel compile+eval pipeline.
  • go test ./... (excluding the bazel-only conformance package) passes with no regressions; gofmt and go vet are clean.

Map literals with duplicate keys were evaluated without error, silently
letting the last value win (e.g. {1: "a", 1: "b"} produced {1: "b"}).
The CEL specification defines a map literal with repeated keys as an
evaluation error, and both cel-cpp and cel-java reject such expressions,
so cel-go was inconsistent with the spec and the other implementations.

The comprehension map-building path already reported the collision via
mutableMap.Insert, but evalMap.Exec wrote into a plain Go map and never
checked for duplicates. Keys are compared using CEL equality so that
equal values of different runtime types (e.g. 1 and 1u) collide even
though they are distinct Go map keys.

This makes cel-go pass the previously skipped conformance tests
fields/qualified_identifier_resolution/map_value_repeat_key and
map_value_repeat_key_heterogeneous, which are removed from the skip list.

Fixes cel-expr#1348
@TristonianJones

Copy link
Copy Markdown
Collaborator

/gcbrun

@TristonianJones TristonianJones left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very happy to have this fix, though I also have a request ...

Can you introduce an option which enables or disables this feature? Previously, the last key would win, so the shift here may switch cases which would be functional into errors. This might be surprising to some users.

You might also consider adding a feature to reject map literals with repeated keys at the checker level as well since this would give a quicker rejection and reduce the number of runtime failures.

Thanks in advance!

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.

Map literals do not reject duplicate keys

2 participants