From b27ddbadb5ecc869959d1affbd2d99812a23475f Mon Sep 17 00:00:00 2001 From: Nikolaus Schuetz Date: Thu, 13 Aug 2026 15:38:13 -0400 Subject: [PATCH 1/3] fix: guard LRUCache against non-positive capacity NewLRUCache and NewSyncLRUCache are exported, but calling Put on a cache created with a non-positive capacity panicked with a nil pointer dereference: the eviction path removes cache.tail.prev, which on an empty list is the head sentinel whose prev pointer is nil. Return early from Put when capacity is non-positive, since such a cache holds nothing. Add a regression test that fails (panics) before this change and passes after. --- util/util.go | 5 +++++ util/util_test.go | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/util/util.go b/util/util.go index d4e0f27d..57e98c5e 100644 --- a/util/util.go +++ b/util/util.go @@ -391,6 +391,11 @@ func (cache *LRUCache) Get(key interface{}) (value interface{}, ok bool) { } func (cache *LRUCache) Put(key interface{}, value interface{}) { + if cache.capacity <= 0 { + // A cache with non-positive capacity holds nothing, so there is nothing + // to store and no eviction to perform. + return + } n, ok := cache.m[key] if ok { cache.remove(n, false) diff --git a/util/util_test.go b/util/util_test.go index 45082b52..b25f9b83 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -232,6 +232,18 @@ func TestLRUCache(t *testing.T) { testCacheEqual(t, cache, []int{1, 3, 4}) } +func TestLRUCacheNonPositiveCapacity(t *testing.T) { + // A cache created with a non-positive capacity holds nothing and must not + // panic on Put. Previously the eviction path removed cache.tail.prev, which + // on an empty list is the head sentinel whose prev pointer is nil, causing a + // nil pointer dereference. + cache := NewLRUCache(0) + cache.Put("a", 1) + if _, ok := cache.Get("a"); ok { + t.Errorf("NewLRUCache(0): Get(\"a\") ok = true, want false (zero-capacity cache holds nothing)") + } +} + func testEscapeStringLiterals(t *testing.T, input string, expected string) { t.Helper() result := EscapeStringLiterals(input) From 7bd9e27a2c69d40de058b7b179475f50c8b7eee5 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Fri, 21 Aug 2026 14:41:20 +0800 Subject: [PATCH 2/3] Update util_test.go --- util/util_test.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/util/util_test.go b/util/util_test.go index b25f9b83..45082b52 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -232,18 +232,6 @@ func TestLRUCache(t *testing.T) { testCacheEqual(t, cache, []int{1, 3, 4}) } -func TestLRUCacheNonPositiveCapacity(t *testing.T) { - // A cache created with a non-positive capacity holds nothing and must not - // panic on Put. Previously the eviction path removed cache.tail.prev, which - // on an empty list is the head sentinel whose prev pointer is nil, causing a - // nil pointer dereference. - cache := NewLRUCache(0) - cache.Put("a", 1) - if _, ok := cache.Get("a"); ok { - t.Errorf("NewLRUCache(0): Get(\"a\") ok = true, want false (zero-capacity cache holds nothing)") - } -} - func testEscapeStringLiterals(t *testing.T, input string, expected string) { t.Helper() result := EscapeStringLiterals(input) From cbf928d5bd9ca06b3183a509f0be49c8f52f3122 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Fri, 21 Aug 2026 14:41:37 +0800 Subject: [PATCH 3/3] Update util.go --- util/util.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/util/util.go b/util/util.go index 57e98c5e..71a9ade7 100644 --- a/util/util.go +++ b/util/util.go @@ -392,10 +392,9 @@ func (cache *LRUCache) Get(key interface{}) (value interface{}, ok bool) { func (cache *LRUCache) Put(key interface{}, value interface{}) { if cache.capacity <= 0 { - // A cache with non-positive capacity holds nothing, so there is nothing - // to store and no eviction to perform. return } + n, ok := cache.m[key] if ok { cache.remove(n, false)