From c7a67900ae28efc8686d1c93be5b716fc1fde502 Mon Sep 17 00:00:00 2001 From: liuyuyan2717 Date: Sat, 29 Aug 2026 17:54:34 +0800 Subject: [PATCH] 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) + } +}