1
0
mirror of https://github.com/gogf/gf.git synced 2025-04-05 03:05:05 +08:00

fix(util/gvalid): lots of memory consumed when required validation on big binary field (#4097)

This commit is contained in:
wln32 2025-03-07 13:52:12 +08:00 committed by GitHub
parent dfe088f5cd
commit a8a055f122
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 68 additions and 19 deletions

View File

@ -287,26 +287,40 @@ func (v *Validator) doCheckValueRecursively(ctx context.Context, in doCheckValue
}
case reflect.Slice, reflect.Array:
var array []interface{}
if gjson.Valid(in.Value) {
array = gconv.Interfaces(gconv.Bytes(in.Value))
} else {
array = gconv.Interfaces(in.Value)
loop := false
switch in.Type.Elem().Kind() {
// []struct []map
case reflect.Struct, reflect.Map:
loop = true
case reflect.Ptr:
loop = true
}
if len(array) == 0 {
return
}
for _, item := range array {
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
Value: item,
Type: in.Type.Elem(),
Kind: in.Type.Elem().Kind(),
ErrorMaps: in.ErrorMaps,
ResultSequenceRules: in.ResultSequenceRules,
})
// Bail feature.
if v.bail && len(in.ErrorMaps) > 0 {
break
// When it is a base type array,
// there is no need for recursive loop validation,
// otherwise it will cause memory leakage
// https://github.com/gogf/gf/issues/4092
if loop {
var array []interface{}
if gjson.Valid(in.Value) {
array = gconv.Interfaces(gconv.Bytes(in.Value))
} else {
array = gconv.Interfaces(in.Value)
}
if len(array) == 0 {
return
}
for _, item := range array {
v.doCheckValueRecursively(ctx, doCheckValueRecursivelyInput{
Value: item,
Type: in.Type.Elem(),
Kind: in.Type.Elem().Kind(),
ErrorMaps: in.ErrorMaps,
ResultSequenceRules: in.ResultSequenceRules,
})
// Bail feature.
if v.bail && len(in.ErrorMaps) > 0 {
break
}
}
}
}

View File

@ -9,6 +9,7 @@ package gvalid_test
import (
"context"
"fmt"
"runtime"
"testing"
"time"
@ -114,3 +115,37 @@ func Test_Issue3636(t *testing.T) {
)
})
}
// https://github.com/gogf/gf/issues/4092
func Test_Issue4092(t *testing.T) {
type Model struct {
Raw []byte `v:"required"`
Test []byte `v:"foreach|in:1,2,3"`
}
gtest.C(t, func(t *gtest.T) {
const kb = 1024
const mb = 1024 * kb
raw := make([]byte, 50*mb)
in := &Model{
Raw: raw,
Test: []byte{40, 5, 6},
}
err := g.Validator().
Data(in).
Run(context.Background())
t.Assert(err, "The Test value `6` is not in acceptable range: 1,2,3")
allocMb := getMemAlloc()
t.AssertLE(allocMb, 110)
})
}
func getMemAlloc() uint64 {
byteToMb := func(b uint64) uint64 {
return b / 1024 / 1024
}
var m runtime.MemStats
runtime.ReadMemStats(&m)
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
alloc := byteToMb(m.Alloc)
return alloc
}