Skip to content

Fix: Add bounds check for slotTimers to prevent silent trade recording failure#75

Open
Reggie-Reuss wants to merge 1 commit intoFlipping-Utilities:masterfrom
Reggie-Reuss:fix/slot-timer-bounds-check
Open

Fix: Add bounds check for slotTimers to prevent silent trade recording failure#75
Reggie-Reuss wants to merge 1 commit intoFlipping-Utilities:masterfrom
Reggie-Reuss:fix/slot-timer-bounds-check

Conversation

@Reggie-Reuss
Copy link

@Reggie-Reuss Reggie-Reuss commented Mar 14, 2026

Summary

  • hydrateSlotTimers() only checks for null, but Gson deserializes an empty JSON array [] as a non-null empty ArrayList — bypassing initialization entirely
  • The three .get(slot) calls in screenOfferEvent() then throw IndexOutOfBoundsException on every GE offer event, silently dropping all real-time trades
  • GE History Tab imports (slot -1) are unaffected since they bypass screenOfferEvent(), which masks the failure — the plugin appears functional but records nothing
  • Added bounds checks in setWidgetsOnSlotTimers() for the same pattern

How I found this

I discovered this while investigating why only certain items were saving in my trade history. After my data recovery from a RuneLite crash that corrupted both my main JSON and backup JSON simultaneously, the recovered file had "slotTimers": []. This caused 16 days of silent trade recording failure before I traced it to IndexOutOfBoundsException errors in client.log.

Full root cause analysis: Phase 6: slotTimers Bug

Is this likely in normal use?

In a typical crash, probably not — the plugin falls back to the backup file which usually has valid 8-entry slotTimers. But it can happen in rare edge cases:

  • Both main + backup files corrupted simultaneously (my case — documented here)
  • File truncated at exactly the slotTimers boundary (theoretically possible for medium-sized account files in the 8-16KB range)
  • Manual JSON editing or third-party tools that produce "slotTimers": []
  • Schema changes between plugin versions

The fix is a one-line change to the root cause (null check → null || size() != 8) plus two defensive guards at the .get() call sites.

Changes

File Change
AccountData.hydrateSlotTimers Reinitialize if size() != 8, not just null
NewOfferEventPipelineHandler.screenOfferEvent Early return + log.warn if slotTimers undersized
FlippingPlugin.setWidgetsOnSlotTimers Skip loop if list undersized

Note

Java is not my primary language, so this would benefit from review by someone more experienced with the codebase — similar to my other PR (#74). Happy to adjust based on feedback.

Summary by CodeRabbit

  • Bug Fixes
    • Improved stability by adding validation checks for timer data initialization, preventing potential runtime errors when timer data is missing or incomplete.

If slotTimers is deserialized as an empty list rather than null (e.g.
from a recovered or manually constructed JSON file), hydrateSlotTimers()
skips initialization because it only checks for null. The three
unguarded .get(slot) calls in screenOfferEvent() then throw
IndexOutOfBoundsException on every GE offer event, silently dropping
all real-time trades with no user-visible error.

- AccountData.hydrateSlotTimers: reinitialize if size != 8, not just null
- NewOfferEventPipelineHandler.screenOfferEvent: early return with log
  warning if slotActivityTimers is missing or undersized
- FlippingPlugin.setWidgetsOnSlotTimers: skip loop if list undersized
@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

Three methods now include defensive validation for slot timers. Methods check that slot timers exist and have exactly 8 elements before proceeding with operations dependent on that state, adding early-return guards and reinitializing timers when needed.

Changes

Cohort / File(s) Summary
Slot Timer Validation
src/main/java/com/flippingutilities/controller/FlippingPlugin.java, src/main/java/com/flippingutilities/controller/NewOfferEventPipelineHandler.java
Added early-return guards to prevent widget binding and offer screening when slot timers are missing or undersized. FlippingPlugin skips iteration when slotTimers.size() < 8; NewOfferEventPipelineHandler logs a warning and returns empty when slotActivityTimers is null or size < 8.
Slot Timer Initialization
src/main/java/com/flippingutilities/model/AccountData.java
Modified hydrateSlotTimers() to reinitialize slot timers when size is not exactly 8, while reusing existing timers with size 8 and refreshing their internal references.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 Eight timers stand in line so neat,
Now guards ensure they're all complete,
No null shall slip through undetected,
Our slot machinery, well-protected!
The rabbits hop with checks so tight,
Each validation set just right. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding bounds checks for slotTimers to prevent trade recording failure due to silent exceptions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can disable the changed files summary in the walkthrough.

Disable the reviews.changed_files_summary setting to disable the changed files summary in the walkthrough.

@AntonyGarand
Copy link
Contributor

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/java/com/flippingutilities/controller/FlippingPlugin.java (1)

1004-1015: Consider adding a similar guard for consistency.

The startSlotTimers method accesses getSlotTimers() without validating size. While forEach won't throw on an undersized list, this could lead to silent partial updates if timers are missing. For consistency with the other guards, consider adding a null/size check, or at minimum a log warning.

♻️ Optional: Add defensive check
 private ScheduledFuture startSlotTimers() {
-    return executor.scheduleAtFixedRate(() ->
-            dataHandler.viewAccountData(currentlyLoggedInAccount).getSlotTimers().forEach(slotWidgetTimer ->
+    return executor.scheduleAtFixedRate(() -> {
+            List<SlotActivityTimer> slotTimers = dataHandler.viewAccountData(currentlyLoggedInAccount).getSlotTimers();
+            if (slotTimers == null || slotTimers.size() < 8) {
+                log.warn("slotTimers missing or undersized in startSlotTimers, skipping update");
+                return;
+            }
+            slotTimers.forEach(slotWidgetTimer ->
                     clientThread.invokeLater(() -> {
                         try {
                             slotsPanel.updateTimerDisplays(slotWidgetTimer.getSlotIndex(), slotWidgetTimer.createFormattedTimeString());
                             slotWidgetTimer.updateTimerDisplay();
                         } catch (Exception e) {
                             log.error("exception when trying to update timer", e);
                         }
-                    })), 1000, 1000, TimeUnit.MILLISECONDS);
+                    }));
+        }, 1000, 1000, TimeUnit.MILLISECONDS);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/flippingutilities/controller/FlippingPlugin.java` around
lines 1004 - 1015, The startSlotTimers lambda calls
dataHandler.viewAccountData(currentlyLoggedInAccount).getSlotTimers() and then
blindly iterates/update slotsPanel.updateTimerDisplays and
slotWidgetTimer.updateTimerDisplay; add a defensive check to validate the
returned slot timers collection (null check and a size/emptiness check or
expected-size check) before the forEach, and log a warning (including
currentlyLoggedInAccount) and skip the update if validation fails so you avoid
silent partial updates and keep behavior consistent with other guards.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/java/com/flippingutilities/controller/FlippingPlugin.java`:
- Around line 1004-1015: The startSlotTimers lambda calls
dataHandler.viewAccountData(currentlyLoggedInAccount).getSlotTimers() and then
blindly iterates/update slotsPanel.updateTimerDisplays and
slotWidgetTimer.updateTimerDisplay; add a defensive check to validate the
returned slot timers collection (null check and a size/emptiness check or
expected-size check) before the forEach, and log a warning (including
currentlyLoggedInAccount) and skip the update if validation fails so you avoid
silent partial updates and keep behavior consistent with other guards.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c2fe29d7-611d-4bfc-9dd9-5ca5ea70217c

📥 Commits

Reviewing files that changed from the base of the PR and between ac7d1a3 and 67b9ea7.

📒 Files selected for processing (3)
  • src/main/java/com/flippingutilities/controller/FlippingPlugin.java
  • src/main/java/com/flippingutilities/controller/NewOfferEventPipelineHandler.java
  • src/main/java/com/flippingutilities/model/AccountData.java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants