Skip to content

[Bug]: 使用v10.26.0版本导致了应用程序的崩溃 #1408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ccpwcn opened this issue Apr 3, 2025 · 13 comments
Open

[Bug]: 使用v10.26.0版本导致了应用程序的崩溃 #1408

ccpwcn opened this issue Apr 3, 2025 · 13 comments
Labels

Comments

@ccpwcn
Copy link

ccpwcn commented Apr 3, 2025

What happened?

当我使用 v10.26.0 的时候,验证参数,导致了下面的崩溃:

2025/04/03 08:28:01 [Recovery] 2025/04/03 - 08:28:01 panic recovered:
POST /canteen/orderItem/multiple HTTP/1.1
Host: localhost:8586
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Authorization: *
Connection: close
Content-Length: 54
Content-Type: application/json
Referer: http://localhost:9281/h5/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1 HBuilderX
X-Forwarded-For: 127.0.0.1
X-Forwarded-Host: localhost:9281
X-Forwarded-Port: 9281
X-Forwarded-Proto: http


Bad field type param.OrderItem
E:/dependencies/go/pkg/mod/github.com/go-playground/validator/v10@v10.26.0/baked_in.go:2192 (0x14629d2)
	isGte: panic(fmt.Sprintf("Bad field type %T", field.Interface()))
E:/dependencies/go/pkg/mod/github.com/go-playground/validator/v10@v10.26.0/baked_in.go:2285 (0x14635cb)
	hasMinOf: return isGte(fl)
E:/dependencies/go/pkg/mod/github.com/go-playground/validator/v10@v10.26.0/baked_in.go:44 (0x1450ce3)
	wrapFunc.func1: return fn(fl)
E:/dependencies/go/pkg/mod/github.com/go-playground/validator/v10@v10.26.0/validator.go:473 (0x14765e1)
	(*validate).traverseField: if !ct.fn(ctx, v) {
E:/dependencies/go/pkg/mod/github.com/go-playground/validator/v10@v10.26.0/validator.go:315 (0x147882f)
	(*validate).traverseField: v.traverseField(ctx, parent, current.Index(i), ns, structNs, reusableCF, ct)
E:/dependencies/go/pkg/mod/github.com/go-playground/validator/v10@v10.26.0/validator.go:78 (0x1473f0e)
	(*validate).validateStruct: v.traverseField(ctx, current, current.Field(f.idx), ns, structNs, f, f.cTags)
E:/dependencies/go/pkg/mod/github.com/go-playground/validator/v10@v10.26.0/validator_instance.go:396 (0x147dcc8)
	(*Validate).StructCtx: vd.validateStruct(ctx, top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil)
E:/dependencies/go/pkg/mod/github.com/go-playground/validator/v10@v10.26.0/validator_instance.go:369 (0x147d7d4)
	(*Validate).Struct: return v.StructCtx(context.Background(), s)
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/binding/default_validator.go:83 (0x158a74b)
	(*defaultValidator).validateStruct: return v.validate.Struct(obj)
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/binding/default_validator.go:60 (0x158a42a)
	(*defaultValidator).ValidateStruct: return v.validateStruct(obj)
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/binding/binding.go:121 (0x1589da9)
	validate: return Validator.ValidateStruct(obj)
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/binding/json.go:55 (0x15902b7)
	decodeJSON: return validate(obj)
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/binding/json.go:37 (0x15900d5)
	jsonBinding.Bind: return decodeJSON(req.Body, obj)
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:752 (0x15a4b98)
	(*Context).ShouldBindWith: return b.Bind(c.Request, obj)
E:/code/GoglandProjects/realMsgService/ctrl/order_item.go:173 (0x18598f3)
	(*OrderItemController).CreateOrderItems: if err := c.ShouldBindWith(&in, binding.JSON); err != nil {
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x159fdb9)
	(*Context).Next: c.handlers[c.index](c)
E:/code/GoglandProjects/realMsgService/web/middleware/online.go:18 (0x18936d2)
	Online: c.Next()
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x159fdb9)
	(*Context).Next: c.handlers[c.index](c)
E:/code/GoglandProjects/realMsgService/web/middleware/permission.go:16 (0x1895c97)
	PermissionMiddleware.func1: c.Next()
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x159fdb9)
	(*Context).Next: c.handlers[c.index](c)
E:/code/GoglandProjects/realMsgService/web/middleware/auth.go:73 (0x1892804)
	Auth: c.Next()
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x159fdb9)
	(*Context).Next: c.handlers[c.index](c)
E:/code/GoglandProjects/realMsgService/web/middleware/elapsed.go:14 (0x18929c5)
	Elapsed: c.Next()
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x159fdb9)
	(*Context).Next: c.handlers[c.index](c)
E:/code/GoglandProjects/realMsgService/web/middleware/safety_trace.go:71 (0x189486b)
	SafetyTracer: c.Next()
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x159fdb9)
	(*Context).Next: c.handlers[c.index](c)
E:/code/GoglandProjects/realMsgService/web/middleware/repeat_read.go:25 (0x1893a26)
	RepeatRead: c.Next()
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x159fdb9)
	(*Context).Next: c.handlers[c.index](c)
E:/dependencies/go/pkg/mod/github.com/gin-contrib/zap@v1.1.5/zap.go:76 (0x189057a)
	GinzapWithConfig.func1: c.Next()
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x159fdb9)
	(*Context).Next: c.handlers[c.index](c)
E:/code/GoglandProjects/realMsgService/web/middleware/request_id.go:19 (0x1893cf8)
	RequestIDMiddleware: c.Next()
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x159fdb9)
	(*Context).Next: c.handlers[c.index](c)
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/recovery.go:102 (0x15b26bc)
	CustomRecoveryWithWriter.func1: c.Next()
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x159fdb9)
	(*Context).Next: c.handlers[c.index](c)
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:633 (0x15b00e9)
	(*Engine).handleHTTPRequest: c.Next()
E:/dependencies/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:589 (0x15afbbb)
	(*Engine).ServeHTTP: engine.handleHTTPRequest(c)
C:/Program Files/Go/src/net/http/server.go:3210 (0xbb3f16)
	serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
C:/Program Files/Go/src/net/http/server.go:2092 (0xb9cc74)
	(*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
C:/Program Files/Go/src/runtime/asm_amd64.s:1700 (0x6a59c0)
	goexit: BYTE	$0x90	// NOP

当我退回 v10.14.0 的时候就没有问题了。

realMsgService/ctrl/order_item.go:173 是这样的:

Image

这段代码我已经很久没有改过它,这一次崩溃,就是因为 github.com/go-playground/validator/v10 从 v10.14.0 升到了 v10.26.0 导致的。这给我造成了非常严重的客户信任危机。

代码中使用的结构体 param.OrderItemsCreateInput 是这样的:

package param

type OrderItem struct {
	SkuId   int64 `json:"skuId,omitempty" form:"skuId" binding:"required,gte=1"`
	Subject int8  `json:"subject,omitempty" form:"subject" binding:"required,oneof=1 2"`
	Amount  int64 `json:"amount,omitempty" form:"amount" binding:"required,gte=1"`
}

type OrderItemsCreateInput struct {
	OrderItems []OrderItem `json:"orderItems,omitempty" form:"orderItems" binding:"required,dive,min=1,max=100"`
}

Version

v10.26.0

Example Code

func (ctrl *OrderItemController) CreateOrderItems(c *gin.Context) {
	var in param.OrderItemsCreateInput
	if err := c.ShouldBindWith(&in, binding.JSON); err != nil {
		c.JSON(http.StatusBadRequest, ctrl.ApiCheck(c, gin.H{"msg": ctrl.processError(err)}))
		return
	}
	session, err := concurrency.NewSession(g.EtcdCli())
	if err != nil {
		c.JSON(http.StatusInternalServerError, ctrl.ApiFail(c, gin.H{"msg": err.Error()}))
		return
	}
	defer func(session *concurrency.Session) {
		if err := session.Close(); err != nil {
			g.LogWithContext(c.Request.Context()).Error("关闭etcd的session出现错误", zap.Error(err))
		}
	}(session)
	mutex := concurrency.NewMutex(session, fmt.Sprintf("/create-order-item-lock/%d", ctrl.MustLoginUser(c).UserId))
	// 竞争锁,最多等待3秒
	ctx, cancel := context.WithTimeout(c, time.Second*3)
	defer cancel()
	err = mutex.TryLock(ctx)
	if errors.Is(err, concurrency.ErrLocked) {
		c.JSON(http.StatusOK, ctrl.ApiFail(c, gin.H{"msg": "这会儿下单的人太多了"}))
		return
	}
	if err != nil {
		c.JSON(http.StatusInternalServerError, ctrl.ApiFail(c, gin.H{"msg": err.Error()}))
		return
	}
	defer func(mutex *concurrency.Mutex, ctx context.Context) {
		if err := mutex.Unlock(ctx); err != nil {
			g.Log().Error("释放etcd的锁出现错误", zap.Error(err))
		}
	}(mutex, ctx)
	// 创建订单
	if orderId, err := ctrl.createOrderItemsAction(c, in); err != nil {
		c.JSON(http.StatusInternalServerError, ctrl.ApiFail(c, gin.H{"msg": err.Error()}))
		return
	} else {
		c.JSON(http.StatusOK, ctrl.ApiOk(c, gin.H{
			"code": http.StatusOK,
			"data": map[string]interface{}{
				"orderId": orderId,
			},
		}))
		return
	}
}
@ccpwcn ccpwcn added the bug label Apr 3, 2025
@deankarn
Copy link
Contributor

deankarn commented Apr 3, 2025

Hey @ccpwcn , sorry to hear about this issue. Nothing should have changed that would affected these validations, especially such core ones.

Is it possible to provide a simpler reproducible example only using the validator code not through gin? Mainly so I can take a look tomorrow , it’s very late here, will help me debug faster 🙏 If not I can likely piece it together from your example.

@deankarn
Copy link
Contributor

deankarn commented Apr 3, 2025

I tried really quickly to reproduce but was unable to using the below code.

I even checked the git blame, the gte logic hasn't changed since 2023 and that was a fix for float types and not int64 which I think are used in your case and not changes since 2020, so it must be upstream of that, but if I'm unable to reproduce it would be hard to know where the issue lies.

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

type OrderItem struct {
	SkuId   int64 `json:"skuId,omitempty" form:"skuId" binding:"required,gte=1"`
	Subject int8  `json:"subject,omitempty" form:"subject" binding:"required,oneof=1 2"`
	Amount  int64 `json:"amount,omitempty" form:"amount" binding:"required,gte=1"`
}

type OrderItemsCreateInput struct {
	OrderItems []OrderItem `json:"orderItems,omitempty" form:"orderItems" binding:"required,dive,min=1,max=100"`
}

func main() {
	validator := validator.New()

	input := OrderItemsCreateInput{
		OrderItems: []OrderItem{
			{
				SkuId:   3,
				Subject: 2,
				Amount:  3,
			},
		},
	}

	errs := validator.Struct(input)

	fmt.Println(errs)

}

@nodivbyzero
Copy link
Contributor

@deankarn
I successfully reproduced this crash/panic

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

type OrderItem struct {
	SkuId   int64 `json:"skuId,omitempty" form:"skuId" validate:"required,gte=1"`
	Subject int8  `json:"subject,omitempty" form:"subject" validate:"required,oneof=1 2"`
	Amount  int64 `json:"amount,omitempty" form:"amount" validate:"required,gte=1"`
}

type OrderItemsCreateInput struct {
	OrderItems []OrderItem `json:"orderItems,omitempty" form:"orderItems" validate:"required,dive,min=1,max=100"`
}

func main() {
	validator := validator.New()

	input := OrderItemsCreateInput{
		OrderItems: []OrderItem{
			{
				SkuId:   3,
				Subject: 2,
				Amount:  3,
			},
		},
	}

	errs := validator.Struct(input)

	fmt.Println(errs)

}

@deankarn
Copy link
Contributor

deankarn commented Apr 4, 2025

Do you mean this exact same code fails on your computer @nodivbyzero ?

What version of Go and OS are you using, I was using:

  • Go v1.24.2
  • Macos Sequoia 15.4

@ccpwcn
Copy link
Author

ccpwcn commented Apr 4, 2025

Hey @ccpwcn , sorry to hear about this issue. Nothing should have changed that would affected these validations, especially such core ones.

Is it possible to provide a simpler reproducible example only using the validator code not through gin? Mainly so I can take a look tomorrow , it’s very late here, will help me debug faster 🙏 If not I can likely piece it together from your example.

I used go version go1.23.2 windows/amd64, Windows 10, No more code for this bug, the code are customer private, but this bug is bound to occur.

@ccpwcn
Copy link
Author

ccpwcn commented Apr 4, 2025

Hey @ccpwcn , sorry to hear about this issue. Nothing should have changed that would affected these validations, especially such core ones.

Is it possible to provide a simpler reproducible example only using the validator code not through gin? Mainly so I can take a look tomorrow , it’s very late here, will help me debug faster 🙏 If not I can likely piece it together from your example.

v10.14.0 no problem, v10.26.0 is bound to crash/panic.

@nodivbyzero
Copy link
Contributor

@deankarn not exactly the same. I changed binding:"required,dive,min=1,max=100" to validate:"required,dive,min=1,max=100" in the provided example.

My Go version:

$ go version  
go version go1.24.1 darwin/arm64

Here is the unit-test which fails right now:

	type Foo struct {
		A int
	}
	type Bar struct {
		B []Foo `validate:"dive,min=1"`
	}

	fooBarTest := &Bar{
		B: []Foo{
			{
				A: 1,
			},
		},
	}
	errs = validate.Struct(fooBarTest)
	Equal(t, errs, nil)

@deankarn
Copy link
Contributor

deankarn commented Apr 5, 2025

I am confused, the original bug report stack trace shows the gte validation erroring which is only on the inner struct.

I see now though why it would fail, this should have never worked before, if it did it was a bug, because min and max shouldn’t work with a struct.

@ccpwcn
Copy link
Author

ccpwcn commented Apr 5, 2025

Image
You're right. To constrain the maximum and minimum values of a number, I should use lt、lte、gt、gte.

The problem is:
Indeed, our usage is incorrect, however, this still cannot explain the issue of not reporting an error in v10.14.0 but reporting an error in v10.26.0. After all, my code has not been modified for a long time, and the sudden error caught me off guard.

@ccpwcn
Copy link
Author

ccpwcn commented Apr 5, 2025

Image You're right. To constrain the maximum and minimum values of a number, I should use lt、lte、gt、gte.

The problem is: Indeed, our usage is incorrect, however, this still cannot explain the issue of not reporting an error in v10.14.0 but reporting an error in v10.26.0. After all, my code has not been modified for a long time, and the sudden error caught me off guard.

It can prompt me that a certain option is incorrect, but crashing directly is still too uncomfortable.

@ccpwcn
Copy link
Author

ccpwcn commented Apr 5, 2025

Image
But, I look it again, my code no problem!!! min, max options used on slice, gte used on int64, oneof used on int8.

@deankarn
Copy link
Contributor

deankarn commented Apr 5, 2025

But, I look it again, my code no problem!!! min, max options used on slice, gte used on int64, oneof used on int8.

But the current definition is not running min & max on a/the slice because they are after the dive , they are being applied to each OrderItem in the slice. If you want them to apply to the slice of OrderItem they need to come before the dive.

This not me deflecting blame for having a bug, but this is also why it’s important to have unit tests to catch unexpected things before they can affect a customer. I highly recommend everyone always them :)

@ccpwcn
Copy link
Author

ccpwcn commented Apr 8, 2025

But, I look it again, my code no problem!!! min, max options used on slice, gte used on int64, oneof used on int8.

But the current definition is not running min & max on a/the slice because they are after the dive , they are being applied to each OrderItem in the slice. If you want them to apply to the slice of OrderItem they need to come before the dive.

This not me deflecting blame for having a bug, but this is also why it’s important to have unit tests to catch unexpected things before they can affect a customer. I highly recommend everyone always them :)

I see, Are you saying that if I want to constrain the []OrderItems slice itself, I should place the constraint before the dive, and if I want to constrain every member of the []OrerItems slice, I should place it after the dive? What do I think is wrong with this? Because in a slice, there may not only be int types, but also other types. How can we use min and max to constrain them uniformly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants