From 6ccbc24a4a80790fec4be6c54fb9d9d7f44813e3 Mon Sep 17 00:00:00 2001 From: Houmgaor Date: Thu, 5 Feb 2026 08:51:09 +0100 Subject: [PATCH] fix(gacha): prevent infinite loop in getRandomEntries Add guards for edge cases: - Empty entries with rolls > 0 - Zero or negative rolls - Zero total weight in non-box mode - Box mode with more rolls than available entries Previously these cases caused infinite loops or panics. --- server/channelserver/handlers_shop_gacha.go | 9 +++ .../channelserver/handlers_shop_gacha_test.go | 57 +++++++++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/server/channelserver/handlers_shop_gacha.go b/server/channelserver/handlers_shop_gacha.go index 1341e341c..cad091df6 100644 --- a/server/channelserver/handlers_shop_gacha.go +++ b/server/channelserver/handlers_shop_gacha.go @@ -353,10 +353,16 @@ func addGachaItem(s *Session, items []GachaItem) { func getRandomEntries(entries []GachaEntry, rolls int, isBox bool) ([]GachaEntry, error) { var chosen []GachaEntry + if len(entries) == 0 || rolls <= 0 { + return chosen, nil + } var totalWeight float64 for i := range entries { totalWeight += entries[i].Weight } + if !isBox && totalWeight <= 0 { + return chosen, nil + } for { if rolls == len(chosen) { break @@ -371,6 +377,9 @@ func getRandomEntries(entries []GachaEntry, rolls int, isBox bool) ([]GachaEntry } } } else { + if len(entries) == 0 { + break + } result := rand.Intn(len(entries)) chosen = append(chosen, entries[result]) entries[result] = entries[len(entries)-1] diff --git a/server/channelserver/handlers_shop_gacha_test.go b/server/channelserver/handlers_shop_gacha_test.go index bb7c9de76..324390ce9 100644 --- a/server/channelserver/handlers_shop_gacha_test.go +++ b/server/channelserver/handlers_shop_gacha_test.go @@ -231,11 +231,9 @@ func TestGachaItemStruct(t *testing.T) { } } -func TestGetRandomEntries_EmptyEntriesZeroRolls(t *testing.T) { - // Note: getRandomEntries with empty entries and rolls > 0 causes infinite loop. - // Only test the valid case of 0 rolls with empty entries. +func TestGetRandomEntries_EmptyEntries(t *testing.T) { entries := []GachaEntry{} - result, err := getRandomEntries(entries, 0, false) + result, err := getRandomEntries(entries, 5, false) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -244,6 +242,57 @@ func TestGetRandomEntries_EmptyEntriesZeroRolls(t *testing.T) { } } +func TestGetRandomEntries_EmptyEntriesBoxMode(t *testing.T) { + entries := []GachaEntry{} + result, err := getRandomEntries(entries, 5, true) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if len(result) != 0 { + t.Errorf("expected empty result, got %d entries", len(result)) + } +} + +func TestGetRandomEntries_NegativeRolls(t *testing.T) { + entries := []GachaEntry{{ID: 1, Weight: 1.0}} + result, err := getRandomEntries(entries, -5, false) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if len(result) != 0 { + t.Errorf("expected empty result, got %d entries", len(result)) + } +} + +func TestGetRandomEntries_ZeroTotalWeight(t *testing.T) { + entries := []GachaEntry{ + {ID: 1, Weight: 0}, + {ID: 2, Weight: 0}, + } + result, err := getRandomEntries(entries, 5, false) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if len(result) != 0 { + t.Errorf("expected empty result with zero weight, got %d entries", len(result)) + } +} + +func TestGetRandomEntries_BoxModeMoreRollsThanEntries(t *testing.T) { + entries := []GachaEntry{ + {ID: 1, Weight: 1.0}, + {ID: 2, Weight: 1.0}, + } + // Request 5 rolls but only 2 entries - should return only 2 + result, err := getRandomEntries(entries, 5, true) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if len(result) != 2 { + t.Errorf("expected 2 results (all available), got %d", len(result)) + } +} + func TestGetRandomEntries_ZeroRolls(t *testing.T) { entries := []GachaEntry{ {ID: 1, Weight: 1.0},