From c60a1c7ee2e7b55a39c295e2f221d70b9dfb3968 Mon Sep 17 00:00:00 2001 From: Tim Wu Date: Mon, 12 Oct 2015 11:34:12 -0700 Subject: [PATCH] Handle nil values on indexing operations Hitting a nil value previously would cause a crash in the experiment run. This happens in both the case of the value being in a map, and in the case where the value is part of a struct. This fix causes the code to instead return a nil. Unfortunately this is not quite ideal in the case of maps to numerical values. In a normal go map, maps with numerical values apparently return a 0 when the requested key is not present. There doesn't seem to be any simple way to maintain parity with that behavior (though it does seem kind of odd, looking at it from Java colored glasses), so a concession is made to just return a nil in that case. --- index_op_test.go | 47 ++++++++++++++++++++++++++++++--- operators.go | 6 ++++- test/map_index_test.json | 12 +++++++++ test/struct_with_nil_field.json | 12 +++++++++ 4 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 test/struct_with_nil_field.json diff --git a/index_op_test.go b/index_op_test.go index 5f752da..589c2e4 100644 --- a/index_op_test.go +++ b/index_op_test.go @@ -95,7 +95,7 @@ func TestArrayInStruct(t *testing.T) { } type StructWithMap struct { - Map map[string]string + Map map[string]int64 } func TestMapField(t *testing.T) { @@ -110,8 +110,8 @@ func TestMapField(t *testing.T) { } inputs := make(map[string]interface{}) - mapField := make(map[string]string) - mapField["key"] = "value" + mapField := make(map[string]int64) + mapField["key"] = 42 inputs["s"] = &StructWithMap{ Map: mapField, } @@ -128,7 +128,46 @@ func TestMapField(t *testing.T) { t.Fatal("Experiment run failed") } - if elem := exp.Outputs["element"]; elem != "value" { + if elem := exp.Outputs["element"]; elem != int64(42) { + t.Fail() + } + + if exp.Outputs["empty"] != nil { + t.Fail() + } +} + +type StructWithNilField struct { + None interface{} +} + +func TestStructWithNilField(t *testing.T) { + data, err := ioutil.ReadFile("test/struct_with_nil_field.json") + if err != nil { + t.Fatal(err) + } + var js map[string]interface{} + err = json.Unmarshal(data, &js) + if (err != nil) { + t.Fatal(err) + } + + inputs := make(map[string]interface{}) + inputs["struct"] = &StructWithNilField{} + + exp := &Interpreter{ + Name: "struct with nil field", + Salt: "safasdf", + Inputs: inputs, + Outputs: make(map[string]interface{}), + Code: js, + } + + if _, ok := exp.Run(); !ok { + t.Fatal("Experiment run failed") + } + + if exp.Outputs["nil"] != nil { t.Fail() } } \ No newline at end of file diff --git a/operators.go b/operators.go index 1a57175..cf2865e 100644 --- a/operators.go +++ b/operators.go @@ -213,7 +213,11 @@ func unwrapValue(value reflect.Value) interface{} { case reflect.Bool: return value.Bool() default: - return value.Interface() + if value.IsValid() { + return value.Interface() + } else { + return nil + } } } diff --git a/test/map_index_test.json b/test/map_index_test.json index 7ce7cd1..9481e0c 100644 --- a/test/map_index_test.json +++ b/test/map_index_test.json @@ -32,6 +32,18 @@ }, "index": "key" } + }, + { + "op": "set", + "var": "empty", + "value": { + "op": "index", + "base": { + "op": "get", + "var": "map" + }, + "index": "not there" + } } ] } \ No newline at end of file diff --git a/test/struct_with_nil_field.json b/test/struct_with_nil_field.json new file mode 100644 index 0000000..ad8e14b --- /dev/null +++ b/test/struct_with_nil_field.json @@ -0,0 +1,12 @@ +{ + "op": "set", + "var": "nil", + "value": { + "op": "index", + "base": { + "op": "get", + "var": "struct" + }, + "index": "None" + } +} \ No newline at end of file