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

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.

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
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.

1 participant