Skip to content

Commit 435b79d

Browse files
committed
Make sure get() returns nil for map[string]string on missing keys
Fixes #723
1 parent 4511e92 commit 435b79d

File tree

5 files changed

+92
-70
lines changed

5 files changed

+92
-70
lines changed

builtin/builtin.go

+1-8
Original file line numberDiff line numberDiff line change
@@ -627,14 +627,7 @@ var Builtins = []*Function{
627627
},
628628
{
629629
Name: "get",
630-
Func: func(args ...any) (out any, err error) {
631-
defer func() {
632-
if r := recover(); r != nil {
633-
return
634-
}
635-
}()
636-
return runtime.Fetch(args[0], args[1]), nil
637-
},
630+
Func: get,
638631
},
639632
{
640633
Name: "take",

builtin/lib.go

+67
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"unicode/utf8"
99

1010
"github.com/expr-lang/expr/internal/deref"
11+
"github.com/expr-lang/expr/vm/runtime"
1112
)
1213

1314
func Len(x any) any {
@@ -367,3 +368,69 @@ func flatten(arg reflect.Value) []any {
367368
}
368369
return ret
369370
}
371+
372+
func get(params ...any) (out any, err error) {
373+
from := params[0]
374+
i := params[1]
375+
v := reflect.ValueOf(from)
376+
377+
if v.Kind() == reflect.Invalid {
378+
panic(fmt.Sprintf("cannot fetch %v from %T", i, from))
379+
}
380+
381+
// Methods can be defined on any type.
382+
if v.NumMethod() > 0 {
383+
if methodName, ok := i.(string); ok {
384+
method := v.MethodByName(methodName)
385+
if method.IsValid() {
386+
return method.Interface(), nil
387+
}
388+
}
389+
}
390+
391+
v = deref.Value(v)
392+
i = deref.Deref(i)
393+
394+
switch v.Kind() {
395+
case reflect.Array, reflect.Slice, reflect.String:
396+
index := runtime.ToInt(i)
397+
l := v.Len()
398+
if index < 0 {
399+
index = l + index
400+
}
401+
if 0 <= index && index < l {
402+
value := v.Index(index)
403+
if value.IsValid() {
404+
return value.Interface(), nil
405+
}
406+
}
407+
408+
case reflect.Map:
409+
var value reflect.Value
410+
if i == nil {
411+
value = v.MapIndex(reflect.Zero(v.Type().Key()))
412+
} else {
413+
value = v.MapIndex(reflect.ValueOf(i))
414+
}
415+
if value.IsValid() {
416+
return value.Interface(), nil
417+
}
418+
419+
case reflect.Struct:
420+
fieldName := i.(string)
421+
value := v.FieldByNameFunc(func(name string) bool {
422+
field, _ := v.Type().FieldByName(name)
423+
if field.Tag.Get("expr") == fieldName {
424+
return true
425+
}
426+
return name == fieldName
427+
})
428+
if value.IsValid() {
429+
return value.Interface(), nil
430+
}
431+
}
432+
433+
// Main difference from runtime.Fetch
434+
// is that we return `nil` instead of panic.
435+
return nil, nil
436+
}

test/fuzz/fuzz_corpus.txt

-31
Original file line numberDiff line numberDiff line change
@@ -7088,58 +7088,33 @@ get(false ? i32 : list, i64)
70887088
get(false ? i64 : true, f64)
70897089
get(false ? score : ok, trimSuffix("bar", "bar"))
70907090
get(filter(list, true), i)
7091-
get(groupBy(array, "bar"), add)
70927091
get(groupBy(array, "bar"), i)
70937092
get(groupBy(array, "bar"), ok)
7094-
get(groupBy(array, "foo"), half)
7095-
get(groupBy(array, #), add)
7096-
get(groupBy(array, #), array)
70977093
get(groupBy(array, #), f32)
70987094
get(groupBy(array, #), f64)
70997095
get(groupBy(array, #), foo)
7100-
get(groupBy(array, #), greet)
7101-
get(groupBy(array, #), half)
71027096
get(groupBy(array, #), i)
71037097
get(groupBy(array, #), i64 * 0.5)
71047098
get(groupBy(array, #), i64)
7105-
get(groupBy(array, #)?.String, i64)
7106-
get(groupBy(array, 0.5), add)
7107-
get(groupBy(array, 0.5), div)
71087099
get(groupBy(array, 0.5), ok)
7109-
get(groupBy(array, 1), add)
71107100
get(groupBy(array, 1), f32)
71117101
get(groupBy(array, f64), i64)
71127102
get(groupBy(array, false), findIndex(array, ok))
71137103
get(groupBy(array, i), ok)
7114-
get(groupBy(array, i), score)
7115-
get(groupBy(array, i64), add)
7116-
get(groupBy(array, ok), add)
71177104
get(groupBy(array, true), i32)
71187105
get(groupBy(list, "bar"), i32)
7119-
get(groupBy(list, "foo"), greet)
7120-
get(groupBy(list, #), div)
71217106
get(groupBy(list, #), f32 <= f64)
71227107
get(groupBy(list, #), f32)
71237108
get(groupBy(list, #), foo)
7124-
get(groupBy(list, #), greet)
7125-
get(groupBy(list, #), half)
71267109
get(groupBy(list, #), i)
71277110
get(groupBy(list, #), i32)
71287111
get(groupBy(list, #), i64)
71297112
get(groupBy(list, #), int(f32))
7130-
get(groupBy(list, #), list)
71317113
get(groupBy(list, #), reduce(array, foo))
71327114
get(groupBy(list, #), score(1))
7133-
get(groupBy(list, #), score)
7134-
get(groupBy(list, 0.5), array)
7135-
get(groupBy(list, 0.5), div)
71367115
get(groupBy(list, 0.5), ok)
71377116
get(groupBy(list, 1), i32)
7138-
get(groupBy(list, f32), greet)
7139-
get(groupBy(list, foo), add)
71407117
get(groupBy(list, foo), i32)
7141-
get(groupBy(list, ok), greet)
7142-
get(groupBy(list, ok), list)
71437118
get(i .. 1, 1 * 1)
71447119
get(i .. i32, i)
71457120
get(i32 .. i, i64)
@@ -7200,12 +7175,10 @@ get(list, max(i32))
72007175
get(list, min(i32))
72017176
get(list, min(i64))
72027177
get(list, ok ? 0.5 : "foo")
7203-
get(list, ok ? array : "foo")
72047178
get(list, reduce(list, i64))
72057179
get(list, score(1))
72067180
get(list, score(i))
72077181
get(list, true ? i : i32)
7208-
get(list, {"foo": i32}?.i)
72097182
get(list[i64:1], i)
72107183
get(map(array, #), i)
72117184
get(map(array, #), i32)
@@ -7221,18 +7194,15 @@ get(map(list, 1), i32)
72217194
get(map(list, array), i)
72227195
get(map(list, i32), i64)
72237196
get(map(list, i64), i64)
7224-
get(ok ? "bar" : false, array)
72257197
get(ok ? 1 : "bar", f32)
72267198
get(ok ? add : f32, array)
72277199
get(ok ? f64 : div, i64)
72287200
get(ok ? false : list, f32 > i)
7229-
get(ok ? foo : 0.5, div)
72307201
get(ok ? half : i32, i)
72317202
get(ok ? half : ok, f64)
72327203
get(ok ? i : 0.5, half)
72337204
get(ok ? i32 : half, f64)
72347205
get(ok ? i64 : foo, f32)
7235-
get(ok ? list : "foo", add)
72367206
get(ok ? list : i32, f32)
72377207
get(ok ? ok : div, greet)
72387208
get(ok ? score : f64, i)
@@ -7241,7 +7211,6 @@ get(reduce(list, array), i32)
72417211
get(sort(array), i32)
72427212
get(take(list, i), i64)
72437213
get(true ? "bar" : ok, score(i))
7244-
get(true ? "foo" : half, list)
72457214
get(true ? 0.5 : i32, array)
72467215
get(true ? f32 : 0.5, ok)
72477216
get(true ? false : foo, i64 > 0.5)

test/issues/723/issue_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package issue_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/expr-lang/expr"
7+
"github.com/expr-lang/expr/internal/testify/require"
8+
)
9+
10+
func TestIssue723(t *testing.T) {
11+
emptyMap := make(map[string]string)
12+
env := map[string]interface{}{
13+
"empty_map": emptyMap,
14+
}
15+
16+
code := `get(empty_map, "non_existing_key")`
17+
18+
program, err := expr.Compile(code, expr.Env(env))
19+
require.NoError(t, err)
20+
21+
output, err := expr.Run(program, env)
22+
require.NoError(t, err)
23+
require.Equal(t, nil, output)
24+
}

testdata/examples.txt

-31
Original file line numberDiff line numberDiff line change
@@ -7088,58 +7088,33 @@ get(false ? i32 : list, i64)
70887088
get(false ? i64 : true, f64)
70897089
get(false ? score : ok, trimSuffix("bar", "bar"))
70907090
get(filter(list, true), i)
7091-
get(groupBy(array, "bar"), add)
70927091
get(groupBy(array, "bar"), i)
70937092
get(groupBy(array, "bar"), ok)
7094-
get(groupBy(array, "foo"), half)
7095-
get(groupBy(array, #), add)
7096-
get(groupBy(array, #), array)
70977093
get(groupBy(array, #), f32)
70987094
get(groupBy(array, #), f64)
70997095
get(groupBy(array, #), foo)
7100-
get(groupBy(array, #), greet)
7101-
get(groupBy(array, #), half)
71027096
get(groupBy(array, #), i)
71037097
get(groupBy(array, #), i64 * 0.5)
71047098
get(groupBy(array, #), i64)
7105-
get(groupBy(array, #)?.String, i64)
7106-
get(groupBy(array, 0.5), add)
7107-
get(groupBy(array, 0.5), div)
71087099
get(groupBy(array, 0.5), ok)
7109-
get(groupBy(array, 1), add)
71107100
get(groupBy(array, 1), f32)
71117101
get(groupBy(array, f64), i64)
71127102
get(groupBy(array, false), findIndex(array, ok))
71137103
get(groupBy(array, i), ok)
7114-
get(groupBy(array, i), score)
7115-
get(groupBy(array, i64), add)
7116-
get(groupBy(array, ok), add)
71177104
get(groupBy(array, true), i32)
71187105
get(groupBy(list, "bar"), i32)
7119-
get(groupBy(list, "foo"), greet)
7120-
get(groupBy(list, #), div)
71217106
get(groupBy(list, #), f32 <= f64)
71227107
get(groupBy(list, #), f32)
71237108
get(groupBy(list, #), foo)
7124-
get(groupBy(list, #), greet)
7125-
get(groupBy(list, #), half)
71267109
get(groupBy(list, #), i)
71277110
get(groupBy(list, #), i32)
71287111
get(groupBy(list, #), i64)
71297112
get(groupBy(list, #), int(f32))
7130-
get(groupBy(list, #), list)
71317113
get(groupBy(list, #), reduce(array, foo))
71327114
get(groupBy(list, #), score(1))
7133-
get(groupBy(list, #), score)
7134-
get(groupBy(list, 0.5), array)
7135-
get(groupBy(list, 0.5), div)
71367115
get(groupBy(list, 0.5), ok)
71377116
get(groupBy(list, 1), i32)
7138-
get(groupBy(list, f32), greet)
7139-
get(groupBy(list, foo), add)
71407117
get(groupBy(list, foo), i32)
7141-
get(groupBy(list, ok), greet)
7142-
get(groupBy(list, ok), list)
71437118
get(i .. 1, 1 * 1)
71447119
get(i .. i32, i)
71457120
get(i32 .. i, i64)
@@ -7200,12 +7175,10 @@ get(list, max(i32))
72007175
get(list, min(i32))
72017176
get(list, min(i64))
72027177
get(list, ok ? 0.5 : "foo")
7203-
get(list, ok ? array : "foo")
72047178
get(list, reduce(list, i64))
72057179
get(list, score(1))
72067180
get(list, score(i))
72077181
get(list, true ? i : i32)
7208-
get(list, {"foo": i32}?.i)
72097182
get(list[i64:1], i)
72107183
get(map(array, #), i)
72117184
get(map(array, #), i32)
@@ -7221,18 +7194,15 @@ get(map(list, 1), i32)
72217194
get(map(list, array), i)
72227195
get(map(list, i32), i64)
72237196
get(map(list, i64), i64)
7224-
get(ok ? "bar" : false, array)
72257197
get(ok ? 1 : "bar", f32)
72267198
get(ok ? add : f32, array)
72277199
get(ok ? f64 : div, i64)
72287200
get(ok ? false : list, f32 > i)
7229-
get(ok ? foo : 0.5, div)
72307201
get(ok ? half : i32, i)
72317202
get(ok ? half : ok, f64)
72327203
get(ok ? i : 0.5, half)
72337204
get(ok ? i32 : half, f64)
72347205
get(ok ? i64 : foo, f32)
7235-
get(ok ? list : "foo", add)
72367206
get(ok ? list : i32, f32)
72377207
get(ok ? ok : div, greet)
72387208
get(ok ? score : f64, i)
@@ -7241,7 +7211,6 @@ get(reduce(list, array), i32)
72417211
get(sort(array), i32)
72427212
get(take(list, i), i64)
72437213
get(true ? "bar" : ok, score(i))
7244-
get(true ? "foo" : half, list)
72457214
get(true ? 0.5 : i32, array)
72467215
get(true ? f32 : 0.5, ok)
72477216
get(true ? false : foo, i64 > 0.5)

0 commit comments

Comments
 (0)