{"record":{"id":"649366215a383dd8","repo":"vitessio/vitess","slug":"negative-stack-position","errorCode":null,"errorMessage":"negative stack position","messagePattern":"negative stack position","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"go/vt/vtgate/evalengine/compiler_asm.go","lineNumber":90,"sourceCode":"\t}\n}\n\nfunc (asm *assembler) jumpFrom() *jump {\n\treturn &jump{from: len(asm.ins)}\n}\n\nfunc (asm *assembler) jumpDestination(jumps ...*jump) {\n\tfor _, j := range jumps {\n\t\tif j != nil {\n\t\t\tj.to = len(asm.ins)\n\t\t}\n\t}\n}\n\nfunc (asm *assembler) adjustStack(offset int) {\n\tasm.stack.cur += offset\n\tif asm.stack.cur < 0 {\n\t\tpanic(\"negative stack position\")\n\t}\n\tif asm.stack.cur > asm.stack.max {\n\t\tasm.stack.max = asm.stack.cur\n\t}\n\tif asm.log != nil {\n\t\tasm.log.Stack(asm.stack.cur-offset, asm.stack.cur)\n\t}\n}\n\nfunc (asm *assembler) emit(f frame, instruction string, args ...any) {\n\tif asm.log != nil {\n\t\tasm.log.Instruction(instruction, args...)\n\t}\n\tasm.ins = append(asm.ins, f)\n}\n\nfunc (asm *assembler) Add_dd() {\n\tasm.adjustStack(-1)","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtgate/evalengine/compiler_asm.go#L72-L108","documentation":"The evalengine compiler tracks the virtual operand stack depth while emitting bytecode. adjustStack(offset) applies an instruction's net stack effect and panics if the running depth would go negative, meaning the compiler emitted more pops than pushes — a compiler bug, not a query error. The stack.max is also updated here for bytecode sizing.","triggerScenarios":"Emitting an instruction (e.g. Add_dd, Add_ii, BitOp_and_bb) with a wrong stack offset in a new opcode definition, or mismatched push/pop accounting when compiling a nested expression so pops exceed pushes.","commonSituations":"Contributing a new opcode or rewriting instruction emission in compiler_asm.go/compiler.go; a wrongly defined pop count (e.g. -2 offset for an instruction that actually pops 1).","solutions":["Re-check the stack offset defined for the opcode being emitted (net effect should be pushes - pops, e.g. binary ops are -1)","Trace the compile path with asm.log enabled (the assembler logs stack transitions) to find where depth goes negative","Add a compiler unit test that asserts final stack depth is exactly 1 for the compiled expression"],"exampleFix":"// before\nasm.emit(Add_ii)\nasm.adjustStack(-2) // wrong: net effect of binary add is -1\n// after\nasm.emit(Add_ii)\nasm.adjustStack(-1)","handlingStrategy":"validation","validationCode":"// Go: assert opcode stack effect matches emission\nif want := pushCount(op) - popCount(op); want != declaredOffset(op) {\n\treturn fmt.Errorf(\"opcode %v stack offset mismatch: got %d, declared %d\", op, want, declaredOffset(op))\n}","typeGuard":null,"tryCatchPattern":"func safeCompile(expr string) (prog *program, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = vterrors.Errorf(vtrpcpb.Code_INTERNAL, \"compiler error: %v\", r)\n\t\t}\n\t}()\n\treturn compile(expr)\n}","preventionTips":["Define each opcode's stack effect in one place and derive adjustStack offsets from it","Assert compiled programs end with stack depth exactly 1 in a compile test over a corpus of expressions","Enable the assembler's stack logging when debugging emission changes"],"tags":["go","panic","compiler","bytecode","internal-invariant"],"backgroundTag":"stack-underflow-panic","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}