13 KiB
Deep Boundary & I/O — Extended Checklist
Context Gathering (REQUIRED)
Before applying the checklist, read the TARGET FILES (not just the diff) to understand:
- Data ranges: What are the valid ranges for indices, sizes, positions in this code?
- Sentinel values: What does -1, 0, null, MAX_VALUE mean in this context?
- Buffer lifecycle: Who allocates, positions, slices, and releases buffers?
- I/O contracts: What does the underlying read/write API guarantee? Partial reads?
- Arithmetic units: What units are sizes, timestamps, and positions in?
Off-by-One & Range Depth
Time unit at API boundary (weight: high)
- Does a timestamp cross an API boundary in the wrong unit?
- Is a time value displayed or returned in a unit that differs from what the caller expects?
- Is the wrong
TimeUnitenum argument passed?
Boundary comparison (weight: high)
- State the boundary condition in English. Does the operator (
<vs<=) match? - Is an inclusive bound treated as exclusive (or vice versa)?
- Is the equality case at the boundary missed?
- Does a chunk-boundary alignment assertion fire for legitimate partial chunks?
Integer overflow (weight: high)
- Does multiplying
intconfig by 1024 or 1024*1024 overflow without long promotion? - Does a
Comparatoruse integer subtraction (overflow risk)? - Does a narrowing cast on large values (file positions, byte counts) silently truncate?
Comparator bugs (weight: medium)
- Does the comparator use
*.compare()methods (not subtraction)? - Does it handle equal elements correctly?
- Does it satisfy the transitivity contract?
- Are operands consistent in ordering across comparisons?
Recursive iterator overflow (weight: medium)
- Does
computeNext(),hasNext(), or a helper call itself recursively? - Will it overflow the stack on large inputs?
Recursive wrapping chain causes stack overflow (weight: low)
A defensive wrapper (e.g., unmodifiableCollection) re-applied on every rebuild without checking if the input is already wrapped. Chain depth grows with invocation count and overflows the stack during traversal.
MB/GB to bytes overflow (weight: medium)
- When converting MB/GB to bytes: is at least one operand
longbefore multiplication? - Does the intermediate product overflow at 2 GB?
Loop stop condition (weight: medium)
- Does the loop use
> limitinstead of>= limit(or vice versa)? - Does it process one too many or one too few items?
Buffer offset adjustment (weight: medium)
- Does
buffer.array()account forposition()andarrayOffset()? - After
slice(), is the non-zero offset accounted for?
Binary search post-adjustment off-by-one (weight: low)
After binary search in a sorted range structure, adjusting the returned index by +1 for an exact match is wrong when the invariant requires the element to stay at the matched position.
Binary search return value not adjusted for non-zero fromIndex base (weight: low)
When binarySearch accepts a non-zero fromIndex, a custom implementation may return a relative index while the caller expects absolute.
Binary search with transposed forward / reverse sublist ranges (weight: low)
When a binary search is split into forward and reversed halves, are the correct sublist ranges passed to each direction? Transposed ranges silently skip valid matches.
Int intermediate in file-position arithmetic overflows (weight: low)
When an array or buffer offset is computed from a multi-gigabyte file position, an early (int) cast silently truncates.
For-each loop with manual counter incremented at body start is off-by-one (weight: low)
A manual counter incremented at the top of a for-each loop body is always one ahead of the current element when used as an array index.
Compressed-section boundary at exact chunk multiple includes extra chunk (weight: low)
An endpoint that falls exactly on a chunk boundary does not require the next chunk; failing to check this sends one extra chunk.
Accumulated counter with constant increment exceeds valid input domain (weight: low)
The accumulated value can drift past the boundary of the valid input range.
Loop intended to check one beyond interval uses wrong comparator (weight: low)
A scan loop meant to "check one extra entry beyond the interval" silently skips that entry when the boundary uses < instead of <=.
Null & Bounds Check Depth
Map / registry lookup null (weight: high)
- Is the result of a map or registry lookup null-checked before use?
- Can a background task race with a concurrent removal of the entry it just looked up?
- Do paths that accept user-supplied keys null-guard the lookup result?
Collection/array bounds (weight: high)
list.get(0)withoutisEmpty()?array[idx]without length check?Optional.get()withoutisPresent()?- Parallel-list length mismatch?
indexOf/substring without -1 check (weight: high)
- Is the return value of
indexOf/lastIndexOfused as an offset intosubstring(...)without a!= -1guard? - Does
split(...)return a single element when the delimiter is absent, and does the caller assume a multi-element result?
Nullable lifecycle fields (weight: high)
- Is a field initialized in
start()/setup()rather than constructor? - Is it dereferenced during shutdown or startup race?
- Is it accessed from a wrong construction path?
Infinite loops (weight: high)
- Does a
while(true)orwhile(!done)have an explicit escape path? - Is there a timeout, maximum-retry count, or interrupt check?
- Can the condition EVER become true given possible upstream stalls?
Missing instanceof before cast (weight: high)
- Is every cast preceded by a matching
instanceofguard? - When a new implementation is added, does it pass existing type checks?
Empty collection edge cases (weight: high)
- What happens with zero items? Does the code guard against empty inputs?
- Does empty credentials, config, or options cause a different (broken) code path?
Empty or impossible range not detected before execution (weight: low)
A logically empty range (e.g., start > end, or bounds that can never overlap) is not detected before execution, causing wasted work or incorrect results.
Nullable subtype-specific field accessed without subtype check (weight: low)
A field populated only for certain subtypes is accessed without checking the subtype, causing NPE.
Null-accumulator first-encounter guard skips initialization (weight: low)
Guards of the form if (field != null && ...) incorrectly skip the first-encounter case when the field starts as null.
Claim / lock method returns null (weight: low)
A "mark for exclusive use" or "claim" method returns null meaning nothing available, but callers dereference directly.
Constructor parameter assigned as null instead of passed value (weight: low)
this.field = null instead of this.field = parameter silently discards the configuration.
Update validation rejects no-op changes due to missing backfill (weight: medium)
When an update enforces immutability, it must copy the existing value into the incoming object so a no-change update does not trigger a spurious "field was changed" error.
Helper silently returns empty on invalid input (weight: low)
A helper logs a warning and returns an empty collection on invalid input; callers block waiting for work that was never submitted.
File.listFiles() null (weight: medium)
- Is
File.listFiles()result checked for null before iteration? - Is it called on a non-directory path?
File.length() returns 0 for directories (weight: low)
When computing disk usage by summing file.length(), directories return 0 instead of recursive content size.
poll() before processing prevents retry on exception (weight: low)
In a cleanup loop, poll() removes an item before processing finishes; any exception leaves it unprocessed.
Division by zero (weight: low)
- Is a runtime-derived divisor (e.g.,
collection.size()) checked for zero?
ByteBuffer & I/O Depth
Position-advancing get (specific pattern)
- Does no-arg
ByteBuffer.get()advance position inside a comparator or shared view? - Is the buffer owned by this code, or is it a shared/read-only view?
Shared buffer passed to helper without snapshot (weight: medium)
- Is a shared buffer handed to a helper whose relative reads advance position as a side effect?
- Is there a subsequent reader of the same buffer that will read from the wrong offset?
- Does a rewind-to-restore pattern race with concurrent readers of the same buffer?
Write-side buffer capacity not checked before put (weight: medium)
- Does a write of a fixed-width trailer / marker / end-of-segment token precede a
remaining() >= Ncheck? - Does an encode loop exit on input-consumed instead of output-flushed — so when output exceeds input, the tail is silently truncated?
- Does a decompressor variant that does not validate output bounds run on untrusted network data?
Zero-remaining buffer not guarded before length-prefix read (weight: low)
Before reading a length prefix (or fixed-width header) from a buffer, is remaining() >= headerSize checked? A legitimately-empty component otherwise throws an underflow exception instead of being treated as "no content".
rewind() instead of flip() after write (weight: low)
After populating a buffer for handoff to a reader, flip() sets limit = position then resets position to 0. Using rewind() instead resets position only, exposing uninitialized bytes beyond the written region.
NIO read completeness (weight: low)
- Does
Channel.read()assume a single call fills the entire buffer? - Is there a loop until
remaining() == 0or EOF?
EOF handling (specific pattern)
- Does NIO
read()distinguish -1 (EOF) from 0 (empty buffer)? - Does
InputStream.read()return 0 at end-of-stream instead of -1?
Partial file left on exception during write (weight: low)
- Does an exception in the write path leave a half-written file without an abort / delete step? A subsequent read treats it as complete.
- Does writing to an existing file fail to truncate first, leaving stale bytes beyond the new content?
Retry/backoff unbounded (weight: medium)
- Does exponential backoff have a ceiling?
- Can the lower bound grow past the upper bound?
Sets.union lazy view (weight: low)
- Is
Sets.unionaccumulated in a loop, creating chain depth = iteration count? - Should the view be materialized into a concrete set?
Interval & Range Depth
Interval endpoint operator (weight: low)
- At shared boundaries: is
< 0vs<= 0consistent with open/closed convention? - Does the documented convention match the implementation?
Half-open to closed conversion (weight: low)
- When wrapping
[a, b)API for[a, b]range: is upper bound shifted by +1?
Range bound partial enforcement (weight: low)
- Is start boundary enforced but finish boundary ignored (or vice versa)?
Wrap-around range causes infinite linear scan (weight: low)
In a backward or reverse iteration, does the loop guard use strict < (or >) against an endpoint that may lie on the wrong side of the start in a wrapping range?
Arithmetic Pitfalls
Missing parentheses (weight: low)
- Does
n + 1 / 2lack parentheses, evaluating asn + 0?
Floating-point loop drift (weight: low)
- Does a float/double accumulator loop from 0.0 to 1.0 miss the final value?
Ceiling division (specific pattern)
- Is
(n + d - 1) / dused correctly for ceiling division? - Is
round-down-to-multipleproducing zero when value < N?
Capacity vs read bounds conflated (weight: low)
- Is buffer capacity treated as exclusive for both reads AND seeks?
- Should seek-to-end (
<= capacity) be allowed while read-at-end is not?
Compressed vs uncompressed size (weight: medium)
- Is the compressed or uncompressed size used for file-split decisions?
- Are progress numerator and denominator using the same size metric?
nanoTime deadline addition overflows (weight: low)
Computing a deadline as nanoTime() + duration overflows near Long.MAX_VALUE; the safe pattern is nanoTime() - start < duration.
Arithmetic on Integer.MAX_VALUE sentinel overflows without guard (weight: low)
Any limit + 1 pattern where limit may be Integer.MAX_VALUE silently wraps to negative.
Probabilistic gate does not short-circuit on boundary values (weight: low)
A probability check always calls the RNG even when configured to 0.0 or 1.0.
Expiry / deadline computed from raw sentinel without substitution (weight: low)
- Is a configuration value like
retention = -1(meaning "infinite") passed as-is tonow + duration, producing an already-expired instant? - Is an epoch-offset constant defined as negative and then subtracted (double-negation) instead of added?
Serialized size miscounted against actual write (weight: medium)
- Does the size calculation add a field's length prefix but forget the payload bytes?
- Does a conditional field get counted unconditionally AND again inside its branch — declared size > actual bytes written?
- Does the field order used by the size calculation diverge from the write path so a refactor changes one but not the other?