From c7a67900ae28efc8686d1c93be5b716fc1fde502 Mon Sep 17 00:00:00 2001 From: liuyuyan2717 Date: Sat, 29 Aug 2026 17:54:34 +0800 Subject: [PATCH 1/3] fix: reset skippedNodes in HandleMethodNotAllowed loop to prevent panic Fixes #4818 When HandleMethodNotAllowed is enabled, the handleHTTPRequest method loops through all method trees calling getValue() with the same skippedNodes slice. Since getValue() appends to skippedNodes without clearing it, and the slice cannot grow beyond its initial capacity (engine.maxSections), accumulated entries from previous iterations can cause a slice bounds panic when the reslice operation exceeds capacity. This fix resets skippedNodes to length 0 before each getValue() call in the HandleMethodNotAllowed loop, preventing accumulation across multiple method trees. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Co-Authored-By: Happy --- gin.go | 3 +++ gin_test_issue_4818.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 gin_test_issue_4818.go diff --git a/gin.go b/gin.go index 2e033bf3..87cdef60 100644 --- a/gin.go +++ b/gin.go @@ -743,6 +743,9 @@ func (engine *Engine) handleHTTPRequest(c *Context) { if tree.method == httpMethod { continue } + // Reset skippedNodes before each getValue call to prevent accumulation + // across multiple method trees, which could cause slice bounds panic + *c.skippedNodes = (*c.skippedNodes)[:0] if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil { allowed = append(allowed, tree.method) } diff --git a/gin_test_issue_4818.go b/gin_test_issue_4818.go new file mode 100644 index 00000000..836f24c1 --- /dev/null +++ b/gin_test_issue_4818.go @@ -0,0 +1,37 @@ +package gin + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestHandleMethodNotAllowedSkippedNodesPanic(t *testing.T) { + SetMode(ReleaseMode) + router := New() + router.HandleMethodNotAllowed = true + + h := func(c *Context) {} + router.OPTIONS("/:p0/:p1/a/:p2", h) + router.GET("/:p0/:p1/a/:p2", h) + router.PATCH("/b/:p0/:p1/c", h) + router.DELETE("/b/:p0/:p1/d/:p3", h) + router.GET("/b/:p0/:p1/e/f", h) + router.POST("/b/:p0/:p1/g/:p4/h", h) + router.OPTIONS("/b/:p0/:p1/g/:p4/h", h) + router.DELETE("/b/cache", h) + router.GET("/b/clients/:p1/g", h) + router.POST("/b/clients/:p1/g", h) + router.PATCH("/b/clients/:p1/g/:p4", h) + router.OPTIONS("/b/clients/:p1/g/:p4", h) + + req := httptest.NewRequest(http.MethodPost, "/b/clients/42", nil) + w := httptest.NewRecorder() + + // This should return 405 Method Not Allowed, not panic + router.ServeHTTP(w, req) + + if w.Code != http.StatusMethodNotAllowed { + t.Errorf("Expected status 405, got %d", w.Code) + } +} From a21dccc2765ae0554cdbd9c327877c38da055ca1 Mon Sep 17 00:00:00 2001 From: liuyuyan2717 Date: Sat, 29 Aug 2026 17:58:30 +0800 Subject: [PATCH 2/3] fix: rename unused parameter to underscore in test --- gin_test_issue_4818.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin_test_issue_4818.go b/gin_test_issue_4818.go index 836f24c1..12fa2a8e 100644 --- a/gin_test_issue_4818.go +++ b/gin_test_issue_4818.go @@ -11,7 +11,7 @@ func TestHandleMethodNotAllowedSkippedNodesPanic(t *testing.T) { router := New() router.HandleMethodNotAllowed = true - h := func(c *Context) {} + h := func(_ *Context) {} router.OPTIONS("/:p0/:p1/a/:p2", h) router.GET("/:p0/:p1/a/:p2", h) router.PATCH("/b/:p0/:p1/c", h) From c53bdbaf64ed9a46b418ac94572147b1197f9fbf Mon Sep 17 00:00:00 2001 From: liuyuyan2717 Date: Sat, 29 Aug 2026 17:59:19 +0800 Subject: [PATCH 3/3] chore: remove standalone test file --- gin_test_issue_4818.go | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 gin_test_issue_4818.go diff --git a/gin_test_issue_4818.go b/gin_test_issue_4818.go deleted file mode 100644 index 12fa2a8e..00000000 --- a/gin_test_issue_4818.go +++ /dev/null @@ -1,37 +0,0 @@ -package gin - -import ( - "net/http" - "net/http/httptest" - "testing" -) - -func TestHandleMethodNotAllowedSkippedNodesPanic(t *testing.T) { - SetMode(ReleaseMode) - router := New() - router.HandleMethodNotAllowed = true - - h := func(_ *Context) {} - router.OPTIONS("/:p0/:p1/a/:p2", h) - router.GET("/:p0/:p1/a/:p2", h) - router.PATCH("/b/:p0/:p1/c", h) - router.DELETE("/b/:p0/:p1/d/:p3", h) - router.GET("/b/:p0/:p1/e/f", h) - router.POST("/b/:p0/:p1/g/:p4/h", h) - router.OPTIONS("/b/:p0/:p1/g/:p4/h", h) - router.DELETE("/b/cache", h) - router.GET("/b/clients/:p1/g", h) - router.POST("/b/clients/:p1/g", h) - router.PATCH("/b/clients/:p1/g/:p4", h) - router.OPTIONS("/b/clients/:p1/g/:p4", h) - - req := httptest.NewRequest(http.MethodPost, "/b/clients/42", nil) - w := httptest.NewRecorder() - - // This should return 405 Method Not Allowed, not panic - router.ServeHTTP(w, req) - - if w.Code != http.StatusMethodNotAllowed { - t.Errorf("Expected status 405, got %d", w.Code) - } -}