Skip to content

Commit 4a1a177

Browse files
author
Antoine Pelisse
committed
Refactor Error handling to prevent PE escapes
By refactoring the code this way, we only deal with errors when we have errors rather than by default, and also that prevents path element content to escape to the heap, which significantly decreases the amount of allocations and improves performance.
1 parent 49a5de7 commit 4a1a177

File tree

6 files changed

+72
-163
lines changed

6 files changed

+72
-163
lines changed

typed/helpers.go

Lines changed: 16 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828

2929
// ValidationError reports an error about a particular field
3030
type ValidationError struct {
31-
Path fieldpath.Path
31+
Path string
3232
ErrorMessage string
3333
}
3434

@@ -56,58 +56,39 @@ func (errs ValidationErrors) Error() string {
5656
return strings.Join(messages, "\n")
5757
}
5858

59-
// errorFormatter makes it easy to keep a list of validation errors. They
60-
// should all be packed into a single error object before leaving the package
61-
// boundary, since it's weird to have functions not return a plain error type.
62-
type errorFormatter struct {
63-
path fieldpath.Path
64-
}
65-
66-
func (ef *errorFormatter) descend(pe fieldpath.PathElement) {
67-
ef.path = append(ef.path, pe)
59+
// Set the given path to all the validation errors.
60+
func (errs ValidationErrors) WithPath(p string) ValidationErrors {
61+
for i := range errs {
62+
errs[i].Path = p
63+
}
64+
return errs
6865
}
6966

70-
// parent returns the parent, for the purpose of buffer reuse. It's an error to
71-
// call this if there is no parent.
72-
func (ef *errorFormatter) parent() errorFormatter {
73-
return errorFormatter{
74-
path: ef.path[:len(ef.path)-1],
67+
// Prefix all errors path with the given pathelement. This is useful
68+
// when unwinding the stack on errors.
69+
func (errs ValidationErrors) WithPrefix(prefix string) ValidationErrors {
70+
for i := range errs {
71+
errs[i].Path = prefix + errs[i].Path
7572
}
73+
return errs
7674
}
7775

78-
func (ef errorFormatter) errorf(format string, args ...interface{}) ValidationErrors {
76+
func errorf(format string, args ...interface{}) ValidationErrors {
7977
return ValidationErrors{{
80-
Path: append(fieldpath.Path{}, ef.path...),
8178
ErrorMessage: fmt.Sprintf(format, args...),
8279
}}
8380
}
8481

85-
func (ef errorFormatter) error(err error) ValidationErrors {
86-
return ValidationErrors{{
87-
Path: append(fieldpath.Path{}, ef.path...),
88-
ErrorMessage: err.Error(),
89-
}}
90-
}
91-
92-
func (ef errorFormatter) prefixError(prefix string, err error) ValidationErrors {
93-
return ValidationErrors{{
94-
Path: append(fieldpath.Path{}, ef.path...),
95-
ErrorMessage: prefix + err.Error(),
96-
}}
97-
}
98-
9982
type atomHandler interface {
10083
doScalar(*schema.Scalar) ValidationErrors
10184
doList(*schema.List) ValidationErrors
10285
doMap(*schema.Map) ValidationErrors
103-
104-
errorf(msg string, args ...interface{}) ValidationErrors
10586
}
10687

10788
func resolveSchema(s *schema.Schema, tr schema.TypeRef, v *value.Value, ah atomHandler) ValidationErrors {
10889
a, ok := s.Resolve(tr)
10990
if !ok {
110-
return ah.errorf("schema error: no type found matching: %v", *tr.NamedType)
91+
return errorf("schema error: no type found matching: %v", *tr.NamedType)
11192
}
11293

11394
a = deduceAtom(a, v)
@@ -152,32 +133,7 @@ func handleAtom(a schema.Atom, tr schema.TypeRef, ah atomHandler) ValidationErro
152133
name = "named type: " + *tr.NamedType
153134
}
154135

155-
return ah.errorf("schema error: invalid atom: %v", name)
156-
}
157-
158-
func (ef errorFormatter) validateScalar(t *schema.Scalar, v *value.Value, prefix string) (errs ValidationErrors) {
159-
if v == nil {
160-
return nil
161-
}
162-
if *v == nil {
163-
return nil
164-
}
165-
switch *t {
166-
case schema.Numeric:
167-
if !value.IsFloat(*v) && !value.IsInt(*v) {
168-
// TODO: should the schema separate int and float?
169-
return ef.errorf("%vexpected numeric (int or float), got %T", prefix, *v)
170-
}
171-
case schema.String:
172-
if !value.IsString(*v) {
173-
return ef.errorf("%vexpected string, got %#v", prefix, *v)
174-
}
175-
case schema.Boolean:
176-
if !value.IsBool(*v) {
177-
return ef.errorf("%vexpected boolean, got %v", prefix, *v)
178-
}
179-
}
180-
return nil
136+
return errorf("schema error: invalid atom: %v", name)
181137
}
182138

183139
// Returns the list, or an error. Reminder: nil is a valid list and might be returned.

typed/merge.go

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ import (
2525
)
2626

2727
type mergingWalker struct {
28-
errorFormatter
2928
lhs *value.Value
3029
rhs *value.Value
3130
schema *schema.Schema
3231
typeRef schema.TypeRef
3332

33+
// Current path that we are merging
34+
path fieldpath.Path
35+
3436
// How to merge. Called after schema validation for all leaf fields.
3537
rule mergeRule
3638

@@ -69,11 +71,11 @@ var (
6971
func (w *mergingWalker) merge() (errs ValidationErrors) {
7072
if w.lhs == nil && w.rhs == nil {
7173
// check this condidition here instead of everywhere below.
72-
return w.errorf("at least one of lhs and rhs must be provided")
74+
return errorf("at least one of lhs and rhs must be provided")
7375
}
7476
a, ok := w.schema.Resolve(w.typeRef)
7577
if !ok {
76-
return w.errorf("schema error: no type found matching: %v", *w.typeRef.NamedType)
78+
return errorf("schema error: no type found matching: %v", *w.typeRef.NamedType)
7779
}
7880

7981
alhs := deduceAtom(a, w.lhs)
@@ -107,8 +109,8 @@ func (w *mergingWalker) doLeaf() {
107109
}
108110

109111
func (w *mergingWalker) doScalar(t *schema.Scalar) (errs ValidationErrors) {
110-
errs = append(errs, w.validateScalar(t, w.lhs, "lhs: ")...)
111-
errs = append(errs, w.validateScalar(t, w.rhs, "rhs: ")...)
112+
errs = append(errs, validateScalar(t, w.lhs, "lhs: ")...)
113+
errs = append(errs, validateScalar(t, w.rhs, "rhs: ")...)
112114
if len(errs) > 0 {
113115
return errs
114116
}
@@ -132,7 +134,7 @@ func (w *mergingWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeR
132134
}
133135
*w2 = *w
134136
w2.typeRef = tr
135-
w2.errorFormatter.descend(pe)
137+
w2.path = append(w2.path, pe)
136138
w2.lhs = nil
137139
w2.rhs = nil
138140
w2.out = nil
@@ -142,7 +144,7 @@ func (w *mergingWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeR
142144
func (w *mergingWalker) finishDescent(w2 *mergingWalker) {
143145
// if the descent caused a realloc, ensure that we reuse the buffer
144146
// for the next sibling.
145-
w.errorFormatter = w2.errorFormatter.parent()
147+
w.path = w2.path[:len(w2.path)-1]
146148
*w.spareWalkers = append(*w.spareWalkers, w2)
147149
}
148150

@@ -154,7 +156,7 @@ func (w *mergingWalker) derefMap(prefix string, v *value.Value, dest *map[string
154156
}
155157
m, err := mapValue(*v)
156158
if err != nil {
157-
return w.prefixError(prefix, err)
159+
return errorf("%v: %v", prefix, err)
158160
}
159161
*dest = m
160162
return nil
@@ -174,14 +176,14 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs []interface{}) (
174176
for i, child := range rhs {
175177
pe, err := listItemToPathElement(t, i, child)
176178
if err != nil {
177-
errs = append(errs, w.errorf("rhs: element %v: %v", i, err.Error())...)
179+
errs = append(errs, errorf("rhs: element %v: %v", i, err.Error())...)
178180
// If we can't construct the path element, we can't
179181
// even report errors deeper in the schema, so bail on
180182
// this element.
181183
continue
182184
}
183185
if _, ok := observedRHS.Get(pe); ok {
184-
errs = append(errs, w.errorf("rhs: duplicate entries for key %v", pe.String())...)
186+
errs = append(errs, errorf("rhs: duplicate entries for key %v", pe.String())...)
185187
}
186188
observedRHS.Insert(pe, child)
187189
rhsOrder = append(rhsOrder, pe)
@@ -192,14 +194,14 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs []interface{}) (
192194
for i, child := range lhs {
193195
pe, err := listItemToPathElement(t, i, child)
194196
if err != nil {
195-
errs = append(errs, w.errorf("lhs: element %v: %v", i, err.Error())...)
197+
errs = append(errs, errorf("lhs: element %v: %v", i, err.Error())...)
196198
// If we can't construct the path element, we can't
197199
// even report errors deeper in the schema, so bail on
198200
// this element.
199201
continue
200202
}
201203
if observedLHS.Has(pe) {
202-
errs = append(errs, w.errorf("lhs: duplicate entries for key %v", pe.String())...)
204+
errs = append(errs, errorf("lhs: duplicate entries for key %v", pe.String())...)
203205
continue
204206
}
205207
observedLHS.Insert(pe)
@@ -210,7 +212,7 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs []interface{}) (
210212
w2.rhs = &rchild
211213
}
212214
if newErrs := w2.merge(); len(newErrs) > 0 {
213-
errs = append(errs, newErrs...)
215+
errs = append(errs, newErrs.WithPrefix(pe.String())...)
214216
} else if w2.out != nil {
215217
out = append(out, *w2.out)
216218
}
@@ -225,7 +227,7 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs []interface{}) (
225227
w2 := w.prepareDescent(pe, t.ElementType)
226228
w2.rhs = &value
227229
if newErrs := w2.merge(); len(newErrs) > 0 {
228-
errs = append(errs, newErrs...)
230+
errs = append(errs, newErrs.WithPrefix(pe.String())...)
229231
} else if w2.out != nil {
230232
out = append(out, *w2.out)
231233
}
@@ -248,7 +250,7 @@ func (w *mergingWalker) derefList(prefix string, v *value.Value, dest *[]interfa
248250
}
249251
l, err := listValue(*v)
250252
if err != nil {
251-
return w.prefixError(prefix, err)
253+
return errorf("%v: %v", prefix, err)
252254
}
253255
*dest = l
254256
return nil
@@ -283,11 +285,12 @@ func (w *mergingWalker) visitMapItem(t *schema.Map, out map[string]interface{},
283285
if sf, ok := t.FindField(key); ok {
284286
fieldType = sf.Type
285287
}
286-
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &key}, fieldType)
288+
pe := fieldpath.PathElement{FieldName: &key}
289+
w2 := w.prepareDescent(pe, fieldType)
287290
w2.lhs = lhs
288291
w2.rhs = rhs
289292
if newErrs := w2.merge(); len(newErrs) > 0 {
290-
errs = append(errs, newErrs...)
293+
errs = append(errs, newErrs.WithPrefix(pe.String())...)
291294
} else if w2.out != nil {
292295
out[key] = *w2.out
293296
}

typed/remove.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,5 +112,3 @@ func (w *removingWalker) doMap(t *schema.Map) ValidationErrors {
112112
}
113113
return nil
114114
}
115-
116-
func (*removingWalker) errorf(_ string, _ ...interface{}) ValidationErrors { return nil }

typed/tofieldset.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,3 @@ func (v *toFieldSetWalker) doMap(t *schema.Map) (errs ValidationErrors) {
158158

159159
return errs
160160
}
161-
162-
func (v *toFieldSetWalker) errorf(string, ...interface{}) ValidationErrors {
163-
return nil
164-
}

typed/typed.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ func (tv TypedValue) NormalizeUnions(new *TypedValue) (*TypedValue, error) {
163163
w.out = &v
164164
}
165165
if err := normalizeUnions(w); err != nil {
166-
errs = append(errs, w.error(err)...)
166+
errs = append(errs, errorf(err.Error())...)
167167
}
168168
}
169169
out, mergeErrs := merge(&tv, new, func(w *mergingWalker) {}, normalizeFn)
@@ -189,7 +189,7 @@ func (tv TypedValue) NormalizeUnionsApply(new *TypedValue) (*TypedValue, error)
189189
w.out = &v
190190
}
191191
if err := normalizeUnionsApply(w); err != nil {
192-
errs = append(errs, w.error(err)...)
192+
errs = append(errs, errorf(err.Error())...)
193193
}
194194
}
195195
out, mergeErrs := merge(&tv, new, func(w *mergingWalker) {}, normalizeFn)
@@ -213,12 +213,10 @@ var mwPool = sync.Pool{
213213

214214
func merge(lhs, rhs *TypedValue, rule, postRule mergeRule) (*TypedValue, error) {
215215
if lhs.schema != rhs.schema {
216-
return nil, errorFormatter{}.
217-
errorf("expected objects with types from the same schema")
216+
return nil, errorf("expected objects with types from the same schema")
218217
}
219218
if !lhs.typeRef.Equals(rhs.typeRef) {
220-
return nil, errorFormatter{}.
221-
errorf("expected objects of the same type, but got %v and %v", lhs.typeRef, rhs.typeRef)
219+
return nil, errorf("expected objects of the same type, but got %v and %v", lhs.typeRef, rhs.typeRef)
222220
}
223221

224222
mw := mwPool.Get().(*mergingWalker)

0 commit comments

Comments
 (0)