Skip to content

Commit 21edf44

Browse files
committed
Also check AccountData
1 parent 82d8d1b commit 21edf44

File tree

2 files changed

+185
-150
lines changed

2 files changed

+185
-150
lines changed

data/basics/fields_test.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package basics_test
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"strings"
7+
"testing"
8+
9+
"github.com/algorand/go-algorand/data/basics"
10+
"github.com/algorand/go-algorand/data/bookkeeping"
11+
"github.com/algorand/go-algorand/test/partitiontest"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
type typePath []string
16+
17+
func (p typePath) addMapKey() typePath {
18+
return append(p, "map_key")
19+
}
20+
21+
func (p typePath) addValue() typePath {
22+
return append(p, "value")
23+
}
24+
25+
func (p typePath) addField(fieldName string) typePath {
26+
return append(p, "field "+fieldName)
27+
}
28+
29+
func (p typePath) validatePathFrom(t reflect.Type) error {
30+
if len(p) == 0 {
31+
// path is empty, so it's vacuously valid
32+
return nil
33+
}
34+
35+
value := p[0]
36+
switch {
37+
case value == "map_key":
38+
return p[1:].validatePathFrom(t.Key())
39+
case value == "value":
40+
return p[1:].validatePathFrom(t.Elem())
41+
case strings.HasPrefix(value, "field "):
42+
fieldName := value[len("field "):]
43+
fieldType, ok := t.FieldByName(fieldName)
44+
if !ok {
45+
return fmt.Errorf("Type '%s' does not have the field '%s'", t.Name(), fieldName)
46+
}
47+
return p[1:].validatePathFrom(fieldType.Type)
48+
default:
49+
return fmt.Errorf("Unexpected item in path: %s", value)
50+
}
51+
}
52+
53+
func (p typePath) Equals(other typePath) bool {
54+
if len(p) != len(other) {
55+
return false
56+
}
57+
for i := range p {
58+
if p[i] != other[i] {
59+
return false
60+
}
61+
}
62+
return true
63+
}
64+
65+
func (p typePath) String() string {
66+
return strings.Join(p, "->")
67+
}
68+
69+
func checkReferencedTypes(seen map[reflect.Type]bool, path typePath, typeStack []reflect.Type, check func(path typePath, stack []reflect.Type) bool) {
70+
currentType := typeStack[len(typeStack)-1]
71+
72+
if _, seenType := seen[currentType]; seenType {
73+
return
74+
}
75+
76+
if !check(path, typeStack) {
77+
// if currentType is not ok, don't visit its children
78+
return
79+
}
80+
81+
// add currentType to seen set, to avoid infinite recursion if currentType references itself
82+
seen[currentType] = true
83+
84+
// after currentType's children are visited, "forget" the type, so we can examine it again if needed
85+
// if this didn't happen, only 1 error per invalid type would get reported
86+
defer delete(seen, currentType)
87+
88+
switch currentType.Kind() {
89+
case reflect.Map:
90+
newPath := path.addMapKey()
91+
newStack := append(typeStack, currentType.Key())
92+
checkReferencedTypes(seen, newPath, newStack, check)
93+
fallthrough
94+
case reflect.Array, reflect.Slice, reflect.Ptr:
95+
newPath := path.addValue()
96+
newStack := append(typeStack, currentType.Elem())
97+
checkReferencedTypes(seen, newPath, newStack, check)
98+
case reflect.Struct:
99+
for i := 0; i < currentType.NumField(); i++ {
100+
field := currentType.Field(i)
101+
newPath := path.addField(field.Name)
102+
newStack := append(typeStack, field.Type)
103+
checkReferencedTypes(seen, newPath, newStack, check)
104+
}
105+
}
106+
}
107+
108+
func makeTypeCheckFunction(t *testing.T, exceptions []typePath, startType reflect.Type) func(path typePath, stack []reflect.Type) bool {
109+
for _, exception := range exceptions {
110+
err := exception.validatePathFrom(startType)
111+
require.NoError(t, err)
112+
}
113+
114+
return func(path typePath, stack []reflect.Type) bool {
115+
currentType := stack[len(stack)-1]
116+
117+
for _, exception := range exceptions {
118+
if path.Equals(exception) {
119+
t.Logf("Skipping exception for path: %s", path.String())
120+
return true
121+
}
122+
}
123+
124+
switch currentType.Kind() {
125+
case reflect.String:
126+
t.Errorf("Invalid string type referenced from %s. Use []byte instead. Full path: %s", startType.Name(), path.String())
127+
return false
128+
case reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer:
129+
// raise an error if one of these strange types is referenced too
130+
t.Errorf("Invalid type %s referenced from %s. Full path: %s", currentType.Name(), startType.Name(), path.String())
131+
return false
132+
default:
133+
return true
134+
}
135+
}
136+
}
137+
138+
func TestBlockFields(t *testing.T) {
139+
partitiontest.PartitionTest(t)
140+
141+
blockType := reflect.TypeOf(bookkeeping.Block{})
142+
143+
// These exceptions are for pre-existing usages of string. Only add to this list if you really need to use string.
144+
exceptions := []typePath{
145+
typePath{}.addField("BlockHeader").addField("GenesisID"),
146+
typePath{}.addField("BlockHeader").addField("UpgradeState").addField("CurrentProtocol"),
147+
typePath{}.addField("BlockHeader").addField("UpgradeState").addField("NextProtocol"),
148+
typePath{}.addField("BlockHeader").addField("UpgradeVote").addField("UpgradePropose"),
149+
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("SignedTxn").addField("Txn").addField("Type"),
150+
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("SignedTxn").addField("Txn").addField("Header").addField("GenesisID"),
151+
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("SignedTxn").addField("Txn").addField("AssetConfigTxnFields").addField("AssetParams").addField("UnitName"),
152+
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("SignedTxn").addField("Txn").addField("AssetConfigTxnFields").addField("AssetParams").addField("AssetName"),
153+
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("SignedTxn").addField("Txn").addField("AssetConfigTxnFields").addField("AssetParams").addField("URL"),
154+
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("GlobalDelta").addMapKey(),
155+
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("GlobalDelta").addValue().addField("Bytes"),
156+
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("LocalDeltas").addValue().addMapKey(),
157+
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("LocalDeltas").addValue().addValue().addField("Bytes"),
158+
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("Logs").addValue(),
159+
}
160+
161+
seen := make(map[reflect.Type]bool)
162+
163+
checkReferencedTypes(seen, nil, []reflect.Type{blockType}, makeTypeCheckFunction(t, exceptions, blockType))
164+
}
165+
166+
func TestAccountDataFields(t *testing.T) {
167+
partitiontest.PartitionTest(t)
168+
169+
blockType := reflect.TypeOf(basics.AccountData{})
170+
171+
// These exceptions are for pre-existing usages of string. Only add to this list if you really need to use string.
172+
exceptions := []typePath{
173+
typePath{}.addField("AssetParams").addValue().addField("UnitName"),
174+
typePath{}.addField("AssetParams").addValue().addField("AssetName"),
175+
typePath{}.addField("AssetParams").addValue().addField("URL"),
176+
typePath{}.addField("AppLocalStates").addValue().addField("KeyValue").addMapKey(),
177+
typePath{}.addField("AppLocalStates").addValue().addField("KeyValue").addValue().addField("Bytes"),
178+
typePath{}.addField("AppParams").addValue().addField("GlobalState").addMapKey(),
179+
typePath{}.addField("AppParams").addValue().addField("GlobalState").addValue().addField("Bytes"),
180+
}
181+
182+
seen := make(map[reflect.Type]bool)
183+
184+
checkReferencedTypes(seen, nil, []reflect.Type{blockType}, makeTypeCheckFunction(t, exceptions, blockType))
185+
}

data/bookkeeping/encoding_test.go

Lines changed: 0 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
package bookkeeping
1818

1919
import (
20-
"fmt"
21-
"reflect"
22-
"strings"
2320
"testing"
2421

2522
"github.com/stretchr/testify/require"
@@ -69,150 +66,3 @@ func TestBlockWithTxnEncoding(t *testing.T) {
6966
require.NoError(t, err)
7067
require.Equal(t, b, b2)
7168
}
72-
73-
type typePath []string
74-
75-
func (p typePath) addMapKey() typePath {
76-
return append(p, "map_key")
77-
}
78-
79-
func (p typePath) addValue() typePath {
80-
return append(p, "value")
81-
}
82-
83-
func (p typePath) addField(fieldName string) typePath {
84-
return append(p, "field "+fieldName)
85-
}
86-
87-
func (p typePath) validatePathFrom(t reflect.Type) error {
88-
if len(p) == 0 {
89-
// path is empty, so it's vacuously valid
90-
return nil
91-
}
92-
93-
value := p[0]
94-
switch {
95-
case value == "map_key":
96-
return p[1:].validatePathFrom(t.Key())
97-
case value == "value":
98-
return p[1:].validatePathFrom(t.Elem())
99-
case strings.HasPrefix(value, "field "):
100-
fieldName := value[len("field "):]
101-
fieldType, ok := t.FieldByName(fieldName)
102-
if !ok {
103-
return fmt.Errorf("Type '%s' does not have the field '%s'", t.Name(), fieldName)
104-
}
105-
return p[1:].validatePathFrom(fieldType.Type)
106-
default:
107-
return fmt.Errorf("Unexpected item in path: %s", value)
108-
}
109-
}
110-
111-
func (p typePath) Equals(other typePath) bool {
112-
if len(p) != len(other) {
113-
return false
114-
}
115-
for i := range p {
116-
if p[i] != other[i] {
117-
return false
118-
}
119-
}
120-
return true
121-
}
122-
123-
func (p typePath) String() string {
124-
return strings.Join(p, "->")
125-
}
126-
127-
func checkReferencedTypes(seen map[reflect.Type]bool, path typePath, typeStack []reflect.Type, check func(path typePath, stack []reflect.Type) bool) {
128-
currentType := typeStack[len(typeStack)-1]
129-
130-
if _, seenType := seen[currentType]; seenType {
131-
return
132-
}
133-
134-
if !check(path, typeStack) {
135-
// if currentType is not ok, don't visit its children
136-
return
137-
}
138-
139-
// add currentType to seen set, to avoid infinite recursion if currentType references itself
140-
seen[currentType] = true
141-
142-
// after currentType's children are visited, "forget" the type, so we can examine it again if needed
143-
// if this didn't happen, only 1 error per invalid type would get reported
144-
defer delete(seen, currentType)
145-
146-
switch currentType.Kind() {
147-
case reflect.Map:
148-
newPath := path.addMapKey()
149-
newStack := append(typeStack, currentType.Key())
150-
checkReferencedTypes(seen, newPath, newStack, check)
151-
fallthrough
152-
case reflect.Array, reflect.Slice, reflect.Ptr:
153-
newPath := path.addValue()
154-
newStack := append(typeStack, currentType.Elem())
155-
checkReferencedTypes(seen, newPath, newStack, check)
156-
case reflect.Struct:
157-
for i := 0; i < currentType.NumField(); i++ {
158-
field := currentType.Field(i)
159-
newPath := path.addField(field.Name)
160-
newStack := append(typeStack, field.Type)
161-
checkReferencedTypes(seen, newPath, newStack, check)
162-
}
163-
}
164-
}
165-
166-
func TestBlockFields(t *testing.T) {
167-
partitiontest.PartitionTest(t)
168-
169-
blockType := reflect.TypeOf(Block{})
170-
171-
// These exceptions are for pre-existing usages of string. Only add to this list if you really need to use string.
172-
exceptions := []typePath{
173-
typePath{}.addField("BlockHeader").addField("GenesisID"),
174-
typePath{}.addField("BlockHeader").addField("UpgradeState").addField("CurrentProtocol"),
175-
typePath{}.addField("BlockHeader").addField("UpgradeState").addField("NextProtocol"),
176-
typePath{}.addField("BlockHeader").addField("UpgradeVote").addField("UpgradePropose"),
177-
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("SignedTxn").addField("Txn").addField("Type"),
178-
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("SignedTxn").addField("Txn").addField("Header").addField("GenesisID"),
179-
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("SignedTxn").addField("Txn").addField("AssetConfigTxnFields").addField("AssetParams").addField("UnitName"),
180-
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("SignedTxn").addField("Txn").addField("AssetConfigTxnFields").addField("AssetParams").addField("AssetName"),
181-
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("SignedTxn").addField("Txn").addField("AssetConfigTxnFields").addField("AssetParams").addField("URL"),
182-
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("GlobalDelta").addMapKey(),
183-
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("GlobalDelta").addValue().addField("Bytes"),
184-
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("LocalDeltas").addValue().addMapKey(),
185-
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("LocalDeltas").addValue().addValue().addField("Bytes"),
186-
typePath{}.addField("Payset").addValue().addField("SignedTxnWithAD").addField("ApplyData").addField("EvalDelta").addField("Logs").addValue(),
187-
}
188-
189-
for _, exception := range exceptions {
190-
err := exception.validatePathFrom(blockType)
191-
require.NoError(t, err)
192-
}
193-
194-
seen := make(map[reflect.Type]bool)
195-
196-
checkReferencedTypes(seen, nil, []reflect.Type{blockType}, func(path typePath, stack []reflect.Type) bool {
197-
currentType := stack[len(stack)-1]
198-
199-
for _, exception := range exceptions {
200-
if path.Equals(exception) {
201-
t.Logf("Skipping exception for path: %s", path.String())
202-
return true
203-
}
204-
}
205-
206-
switch currentType.Kind() {
207-
case reflect.String:
208-
t.Errorf("Invalid string type referenced from Block. Use []byte instead. Full path: %s", path.String())
209-
return false
210-
case reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer:
211-
// raise an error if one of these strange types is referenced too
212-
t.Errorf("Invalid type %s referenced from Block. Full path: %s", currentType.Name(), path.String())
213-
return false
214-
default:
215-
return true
216-
}
217-
})
218-
}

0 commit comments

Comments
 (0)