Unguarded this.pieces[index] still throws in v3.0.21 (#3091 was closed with no change)
What
this.pieces[index] is read without a null check in four places in lib/torrent.js, and a piece is legitimately null there. This was reported in #3091, which was closed as completed on 2026-07-27 with no comment and no linked commit or PR. I could not reopen it (no permission), hence this issue.
A fix is up: #3103
Still present in v3.0.21
| site | code |
|---|---|
downloaded getter |
downloaded += (piece.length - piece.missing) |
speedRanker() |
let missing = self.pieces[index].missing |
_request |
const piece = self.pieces[index] then piece.reserve() |
_hotswap |
this.pieces[index].cancel(...) |
I diffed lib/torrent.js between v2.8.5 and v3.0.21 in full: the differing hunks are the user agent, _peersLength becoming a getter, web-seed pipelining, secure on outgoing peers, and the arr2hex removal. Nothing touches piece nulling or any of these reads, so nothing about #3091 changed between those versions.
Why the piece is null
_markVerified nulls the piece, while bitfield.set(index, true) runs in the async store.put callback — the window the TODO in _onPiece already anticipates. Guarding on the bitfield alone is therefore not enough.
Impact
With select/deselect used for streaming prioritisation the window is hit constantly. In our proxy the picker threw Cannot read properties of null (reading 'reserve'/'missing') about once a second and the download stopped entirely after a seek; the downloaded getter also threw during a plain full-file download whenever torrent.progress was polled while pieces were verifying.
Reproduced on v3.0.21
The regression test in #3103 nulls pieces[0] while bitfield.get(0) is still false, then reads torrent.downloaded and calls _request. On master at v3.0.21:
not ok 2 reading downloaded does not throw
TypeError: Cannot read properties of null (reading 'length')
at get downloaded (lib/torrent.js:227:30)
TypeError: Cannot read properties of null (reading 'reserve')
at Torrent._request (lib/torrent.js:1941:68)#3103 guards all four sites, each preserving the meaning the surrounding code already gives to "this piece needs nothing", and the same test then passes.
Source: webtorrent/webtorrent