#3104·webtorrent

Unguarded this.pieces[index] still throws in v3.0.21 (#3091 was closed with no change)

Author: SilentImpCreated Aug 19, 2026Updated Aug 19, 2026

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.