Exercise 3: Does the Guard Catch a Corrupted Operand? — Possible Solution ==================================================================== THE TEST ------------------------------ chunk = Chunk() emit_constant(chunk, 2.0, 1) emit_constant(chunk, 3.0, 1) chunk.write(OP_ADD, 1) chunk.write(OP_RETURN, 1) # code bytes: [0, 0, 0, 1, 1, 6] # ^oc ^ix ^oc ^ix ^oc ^oc # positions: 0 1 2 3 4 5 # Corrupt position 3 -- the OPERAND (constant-pool index) of the # second OP_CONSTANT, currently 1, NOT an opcode byte at all chunk.code[3] = 99 run_vm(chunk) # using the FIXED VM, with the else/raise guard RESULT ------------------------------ IndexError: list index out of range NOT the guard's own RuntimeError("Unknown opcode: ...") -- a completely different, unrelated-looking error. WHY THIS WORKS AS AN ANSWER ------------------------------ The guard added in this chapter only runs inside the dispatch chain's own "instr didn't match any known opcode" else branch. But position 3 is never read as an opcode in the first place -- it's read as an OPERAND, one step after OP_CONSTANT at position 2 already matched successfully and entered the `if instr == OP_CONSTANT` branch. Inside that branch, the corrupted value (99) is used directly as `constants[idx]` -- and since this chunk's constant pool only has 2 entries (indices 0 and 1), `constants[99]` raises Python's own IndexError, deep inside a completely different code path than the one the chapter's guard protects. This confirms the guard is narrower than it might first appear: it only defends against an unrecognized OPCODE byte, not against a VALID opcode paired with a corrupted OPERAND. The chapter's own "opcode vs. operand" finding explains exactly why -- the VM has no way to know, just by looking at a lone byte, whether it's currently expecting an opcode or an operand; it only knows because of WHERE it is in the fetch-decode-execute loop at that moment. A byte that's wrong in operand position produces a completely different failure than a byte that's wrong in opcode position, and one guard clause can't cover both. Extending the guard to also range-check every OP_CONSTANT operand against len(chunk.constants) would catch this specific case too -- but that's a genuinely separate fix, not a byproduct of the one already added, and is left as an open, honestly unclosed loop for this chapter (matching that hand-corrupted bytecode is out of this course's real scope in the first place, since Chapter 3's own compiler will never emit an out-of-range index).