diff --git a/conf/env.go b/conf/env.go index 0acd44570..9994090df 100644 --- a/conf/env.go +++ b/conf/env.go @@ -39,6 +39,13 @@ func EnvWithCache(c *Cache, env any) Nature { return n case reflect.Map: + // A pointer to a map is not supported as an environment (see #825). + // Reject it with a clear message instead of dereferencing it or + // panicking deep inside reflect. + if v.Kind() == reflect.Ptr { + panic(fmt.Sprintf("environment must be a map, not a pointer to a map: %s", t)) + } + n := c.FromType(v.Type()) if n.TypeData == nil { n.TypeData = new(TypeData) diff --git a/test/issues/825/issue_test.go b/test/issues/825/issue_test.go new file mode 100644 index 000000000..2528ba167 --- /dev/null +++ b/test/issues/825/issue_test.go @@ -0,0 +1,43 @@ +package issue_test + +import ( + "testing" + + "github.com/expr-lang/expr" + "github.com/expr-lang/expr/internal/testify/assert" + "github.com/expr-lang/expr/internal/testify/require" +) + +// TestIssue825 verifies that passing a pointer to a map as the environment is +// rejected with a clear message instead of panicking deep inside reflect. +// +// Supporting *map by dereferencing it was declined by the maintainer (#825 is +// labeled wontfix); the agreed direction was to reject *map with an error +// message. Previously conf.EnvWithCache selected the map branch on the +// dereferenced kind but then read the map keys/length from the original +// (pointer) value, panicking with the opaque: +// +// reflect: call of reflect.Value.Len on ptr to non-array Value +func TestIssue825(t *testing.T) { + m := map[string]any{"foo": 42} + + assert.PanicsWithValue(t, + "environment must be a map, not a pointer to a map: *map[string]interface {}", + func() { + _, _ = expr.Compile("foo > 0", expr.Env(&m)) + }, + ) +} + +// TestIssue825_MapStillWorks guards the common case: a map passed by value is +// unaffected and continues to work exactly as before. +func TestIssue825_MapStillWorks(t *testing.T) { + m := map[string]any{"foo": 42} + + program, err := expr.Compile("foo + 1", expr.Env(m)) + require.NoError(t, err) + + out, err := expr.Run(program, m) + require.NoError(t, err) + assert.Equal(t, 43, out) +}