Exercise 3: A Bug in the Compiler Itself — Possible Solution ==================================================================== THE BUG ------------------------------ BUGGY_BINARY_OPS = { '+': OP_SUBTRACT, # WRONG -- should be OP_ADD '-': OP_SUBTRACT, '*': OP_MULTIPLY, '/': OP_DIVIDE, } class BuggyCompiler(Compiler): def visit_binary(self, node): self.visit(node.left); self.visit(node.right) self.chunk.write(BUGGY_BINARY_OPS[node.operator], node.line) tree = Binary(Literal(2.0, line=1), '+', Literal(3.0, line=1), line=1) result = run_vm(BuggyCompiler().compile(tree)) RESULT ------------------------------ 2 + 3 -> -1.0 (correct answer is 5.0) No exception. No warning. The chunk is completely well-formed: OP_CONSTANT 0, OP_CONSTANT 1, OP_SUBTRACT, OP_RETURN -- a perfectly valid, structurally correct program that simply computes the wrong operation. WHY THIS WORKS AS AN ANSWER ------------------------------ Chapter 2's own guard exists specifically to catch a byte in the instruction stream that doesn't match ANY known opcode -- `else: raise RuntimeError(f"Unknown opcode: {instr} ...")`. OP_SUBTRACT is a perfectly real, perfectly valid opcode. It's just the WRONG real, valid opcode for this particular node. The guard's own if/elif chain matches OP_SUBTRACT on the very first branch that checks for it and runs it exactly as designed -- there is nothing malformed about this bytecode for the guard, or anything else in the VM, to object to. This is a structurally different category of bug than anything Chapter 2 covered. Chapter 2's own two corruption scenarios (an unrecognized opcode value, and a corrupted operand index) both produced bytecode that was, in some concrete sense, BROKEN -- something a sufficiently thorough checker could in principle detect by inspecting the bytecode alone. This bug produces bytecode that is entirely well-formed and entirely wrong, and no amount of inspecting the bytecode's own structure would reveal it -- you would need to know, independently, what the CORRECT opcode for '+' is supposed to be, and compare. This is exactly why this chapter tested the compiler against known-correct expected VALUES (Course 1's own published 14.0 result, Chapter 2's own -20.0 result) rather than just checking that compilation completed without error -- "it ran without crashing" and "it computed the right answer" are two genuinely different claims, and only testing against a real expected result catches a bug like this one.