Skip to content

Commit c58f1bb

Browse files
robpikeianlancetaylor
authored andcommitted
text/template: permit eq and ne funcs to check against nil
The existing code errors out immediately if the argument is not "comparable", making it impossible to test a slice, map, and so on from being compared to nil. Fix by delaying the "comparable" error check until we encounter an actual check between two non-comparable, non-nil values. Note for the future: reflect makes it unnecessarily clumsy to deal with nil values in cases like this. For instance, it should be possible to check if a value is nil without stepping around a panic. See the new functions isNil and canCompare for my (too expensive) workaround. Fixes #51642 Change-Id: Ic4072698c4910130ea7e3d76e7a148d8a8b88162 Reviewed-on: https://go-review.googlesource.com/c/go/+/392274 Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Trust: Cherry Mui <[email protected]>
1 parent ac31352 commit c58f1bb

File tree

3 files changed

+71
-37
lines changed

3 files changed

+71
-37
lines changed

src/html/template/exec_test.go

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,15 +1191,19 @@ var cmpTests = []cmpTest{
11911191
{"eq .Iface1 .Iface1", "true", true},
11921192
{"eq .Iface1 .Iface2", "false", true},
11931193
{"eq .Iface2 .Iface2", "true", true},
1194+
{"eq .Map .Map", "true", true}, // Uncomparable types but nil is OK.
1195+
{"eq .Map nil", "true", true}, // Uncomparable types but nil is OK.
1196+
{"eq nil .Map", "true", true}, // Uncomparable types but nil is OK.
1197+
{"eq .Map .NonNilMap", "false", true}, // Uncomparable types but nil is OK.
11941198
// Errors
1195-
{"eq `xy` 1", "", false}, // Different types.
1196-
{"eq 2 2.0", "", false}, // Different types.
1197-
{"lt true true", "", false}, // Unordered types.
1198-
{"lt 1+0i 1+0i", "", false}, // Unordered types.
1199-
{"eq .Ptr 1", "", false}, // Incompatible types.
1200-
{"eq .Ptr .NegOne", "", false}, // Incompatible types.
1201-
{"eq .Map .Map", "", false}, // Uncomparable types.
1202-
{"eq .Map .V1", "", false}, // Uncomparable types.
1199+
{"eq `xy` 1", "", false}, // Different types.
1200+
{"eq 2 2.0", "", false}, // Different types.
1201+
{"lt true true", "", false}, // Unordered types.
1202+
{"lt 1+0i 1+0i", "", false}, // Unordered types.
1203+
{"eq .Ptr 1", "", false}, // Incompatible types.
1204+
{"eq .Ptr .NegOne", "", false}, // Incompatible types.
1205+
{"eq .Map .V1", "", false}, // Uncomparable types.
1206+
{"eq .NonNilMap .NonNilMap", "", false}, // Uncomparable types.
12031207
}
12041208

12051209
func TestComparison(t *testing.T) {
@@ -1208,16 +1212,18 @@ func TestComparison(t *testing.T) {
12081212
Uthree, Ufour uint
12091213
NegOne, Three int
12101214
Ptr, NilPtr *int
1215+
NonNilMap map[int]int
12111216
Map map[int]int
12121217
V1, V2 V
12131218
Iface1, Iface2 fmt.Stringer
12141219
}{
1215-
Uthree: 3,
1216-
Ufour: 4,
1217-
NegOne: -1,
1218-
Three: 3,
1219-
Ptr: new(int),
1220-
Iface1: b,
1220+
Uthree: 3,
1221+
Ufour: 4,
1222+
NegOne: -1,
1223+
Three: 3,
1224+
Ptr: new(int),
1225+
NonNilMap: make(map[int]int),
1226+
Iface1: b,
12211227
}
12221228
for _, test := range cmpTests {
12231229
text := fmt.Sprintf("{{if %s}}true{{else}}false{{end}}", test.expr)

src/text/template/exec_test.go

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,15 +1220,19 @@ var cmpTests = []cmpTest{
12201220
{"eq .NilIface .Iface1", "false", true},
12211221
{"eq .NilIface 0", "false", true},
12221222
{"eq 0 .NilIface", "false", true},
1223+
{"eq .Map .Map", "true", true}, // Uncomparable types but nil is OK.
1224+
{"eq .Map nil", "true", true}, // Uncomparable types but nil is OK.
1225+
{"eq nil .Map", "true", true}, // Uncomparable types but nil is OK.
1226+
{"eq .Map .NonNilMap", "false", true}, // Uncomparable types but nil is OK.
12231227
// Errors
1224-
{"eq `xy` 1", "", false}, // Different types.
1225-
{"eq 2 2.0", "", false}, // Different types.
1226-
{"lt true true", "", false}, // Unordered types.
1227-
{"lt 1+0i 1+0i", "", false}, // Unordered types.
1228-
{"eq .Ptr 1", "", false}, // Incompatible types.
1229-
{"eq .Ptr .NegOne", "", false}, // Incompatible types.
1230-
{"eq .Map .Map", "", false}, // Uncomparable types.
1231-
{"eq .Map .V1", "", false}, // Uncomparable types.
1228+
{"eq `xy` 1", "", false}, // Different types.
1229+
{"eq 2 2.0", "", false}, // Different types.
1230+
{"lt true true", "", false}, // Unordered types.
1231+
{"lt 1+0i 1+0i", "", false}, // Unordered types.
1232+
{"eq .Ptr 1", "", false}, // Incompatible types.
1233+
{"eq .Ptr .NegOne", "", false}, // Incompatible types.
1234+
{"eq .Map .V1", "", false}, // Uncomparable types.
1235+
{"eq .NonNilMap .NonNilMap", "", false}, // Uncomparable types.
12321236
}
12331237

12341238
func TestComparison(t *testing.T) {
@@ -1237,16 +1241,18 @@ func TestComparison(t *testing.T) {
12371241
Uthree, Ufour uint
12381242
NegOne, Three int
12391243
Ptr, NilPtr *int
1244+
NonNilMap map[int]int
12401245
Map map[int]int
12411246
V1, V2 V
12421247
Iface1, NilIface fmt.Stringer
12431248
}{
1244-
Uthree: 3,
1245-
Ufour: 4,
1246-
NegOne: -1,
1247-
Three: 3,
1248-
Ptr: new(int),
1249-
Iface1: b,
1249+
Uthree: 3,
1250+
Ufour: 4,
1251+
NegOne: -1,
1252+
Three: 3,
1253+
Ptr: new(int),
1254+
NonNilMap: make(map[int]int),
1255+
Iface1: b,
12501256
}
12511257
for _, test := range cmpTests {
12521258
text := fmt.Sprintf("{{if %s}}true{{else}}false{{end}}", test.expr)

src/text/template/funcs.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -436,14 +436,33 @@ func basicKind(v reflect.Value) (kind, error) {
436436
return invalidKind, errBadComparisonType
437437
}
438438

439+
// isNil returns true if v is the zero reflect.Value, or nil of its type.
440+
func isNil(v reflect.Value) bool {
441+
if v == zero {
442+
return true
443+
}
444+
switch v.Kind() {
445+
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
446+
return v.IsNil()
447+
}
448+
return false
449+
}
450+
451+
// canCompare reports whether v1 and v2 are both the same kind, or one is nil.
452+
// Called only when dealing with nillable types, or there's about to be an error.
453+
func canCompare(v1, v2 reflect.Value) bool {
454+
k1 := v1.Kind()
455+
k2 := v2.Kind()
456+
if k1 == k2 {
457+
return true
458+
}
459+
// We know the type can be compared to nil.
460+
return k1 == reflect.Invalid || k2 == reflect.Invalid
461+
}
462+
439463
// eq evaluates the comparison a == b || a == c || ...
440464
func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
441465
arg1 = indirectInterface(arg1)
442-
if arg1 != zero {
443-
if t1 := arg1.Type(); !t1.Comparable() {
444-
return false, fmt.Errorf("uncomparable type %s: %v", t1, arg1)
445-
}
446-
}
447466
if len(arg2) == 0 {
448467
return false, errNoComparison
449468
}
@@ -479,11 +498,14 @@ func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
479498
case uintKind:
480499
truth = arg1.Uint() == arg.Uint()
481500
default:
482-
if arg == zero || arg1 == zero {
483-
truth = arg1 == arg
501+
if !canCompare(arg1, arg) {
502+
return false, fmt.Errorf("non-comparable types %s: %v, %s: %v", arg1, arg1.Type(), arg.Type(), arg)
503+
}
504+
if isNil(arg1) || isNil(arg) {
505+
truth = isNil(arg) == isNil(arg1)
484506
} else {
485-
if t2 := arg.Type(); !t2.Comparable() {
486-
return false, fmt.Errorf("uncomparable type %s: %v", t2, arg)
507+
if !arg.Type().Comparable() {
508+
return false, fmt.Errorf("non-comparable type %s: %v", arg, arg.Type())
487509
}
488510
truth = arg1.Interface() == arg.Interface()
489511
}

0 commit comments

Comments
 (0)