0
0
mirror of https://github.com/PostHog/posthog.git synced 2024-12-01 12:21:02 +01:00
posthog/hogvm/python/debugger.py
2024-08-28 11:25:58 +02:00

324 lines
14 KiB
Python

import os
from time import sleep
from typing import Any
from hogvm.python.objects import CallFrame
from hogvm.python.operation import Operation
debug_speed = -1
def debugger(
symbol: Any, bytecode: list, colored_bytecode: list, ip: int, stack: list, call_stack: list, throw_stack: list
):
print("\033[H\033[J", end="") # noqa: T201
next_symbol = symbol
try:
next_symbol = print_symbol(Operation(next_symbol), ip, bytecode, stack, call_stack)
except ValueError:
pass
top: list = []
top.append(f"throw_stack: {throw_stack}") # noqa: T201
top.append(f"call_stack: {call_stack}") # noqa: T201
top.append(f"stack: {stack}") # noqa: T201
top.append(f"next: {next_symbol}") # noqa: T201
top.append(f"ip: {ip}") # noqa: T201
top.append("") # noqa: T201
cols = os.get_terminal_size().columns
# count how much top actually takes
header_lines = 2
for line in top:
header_lines += 1 + len(line) // cols
print(line) # noqa: T201
rows = os.get_terminal_size().lines - header_lines
rows = 2 if rows < 2 else rows
rows_from_top = 2 if rows > 2 else 0
start_ip = max(ip - rows_from_top, 0)
end_ip = min(start_ip + rows, len(bytecode))
for i in range(start_ip, end_ip):
prefix = "> " if i == ip else " "
postfix = "" if colored_bytecode[i].startswith("op.") else " "
print(f"{prefix}{i}: {postfix}{colored_bytecode[i]}") # noqa: T201
global debug_speed
if debug_speed < 0:
response = input()
if response == "help" or response == "h" or response == "?":
print("- Press <CTRL+C> to quit.") # noqa: T201
print("- Press <ENTER> to step to the next instruction.") # noqa: T201
print("- Enter a number like 1, 10, 100 or 1000 (ms) to speedwalk the code.") # noqa: T201
response = input()
try:
debug_speed = int(response)
except ValueError:
debug_speed = -1
else:
sleep(debug_speed / 1000)
def print_symbol(symbol: Operation, ip: int, bytecode: list, stack: list, call_stack: list[CallFrame]) -> str:
try:
match symbol:
case Operation.STRING:
return f"STRING({bytecode[ip+1]})"
case Operation.INTEGER:
return f"INTEGER({bytecode[ip+1]})"
case Operation.FLOAT:
return f"FLOAT({bytecode[ip+1]})"
case Operation.TRUE:
return "TRUE"
case Operation.FALSE:
return "FALSE"
case Operation.NULL:
return "NULL"
case Operation.NOT:
return f"NOT({stack[-1]})"
case Operation.AND:
return f"AND({', '.join(str(stack[-i]) for i in range(bytecode[ip+1]))})"
case Operation.OR:
return f"OR({', '.join(str(stack[-i]) for i in range(bytecode[ip+1]))})"
case Operation.PLUS:
return f"PLUS({stack[-2]}, {stack[-1]})"
case Operation.MINUS:
return f"MINUS({stack[-2]}, {stack[-1]})"
case Operation.MULTIPLY:
return f"MULTIPLY({stack[-2]}, {stack[-1]})"
case Operation.DIVIDE:
return f"DIVIDE({stack[-2]}, {stack[-1]})"
case Operation.EQ:
return f"EQ({stack[-2]}, {stack[-1]})"
case Operation.NOT_EQ:
return f"NOT_EQ({stack[-2]}, {stack[-1]})"
case Operation.GT:
return f"GT({stack[-2]}, {stack[-1]})"
case Operation.GT_EQ:
return f"GT_EQ({stack[-2]}, {stack[-1]})"
case Operation.LT:
return f"LT({stack[-2]}, {stack[-1]})"
case Operation.LT_EQ:
return f"LT_EQ({stack[-2]}, {stack[-1]})"
case Operation.LIKE:
return f"LIKE({stack[-2]}, {stack[-1]})"
case Operation.ILIKE:
return f"ILIKE({stack[-2]}, {stack[-1]})"
case Operation.NOT_LIKE:
return f"NOT_LIKE({stack[-2]}, {stack[-1]})"
case Operation.NOT_ILIKE:
return f"NOT_ILIKE({stack[-2]}, {stack[-1]})"
case Operation.IN:
return f"IN({stack[-2]}, {stack[-1]})"
case Operation.NOT_IN:
return f"NOT_IN({stack[-2]}, {stack[-1]})"
case Operation.REGEX:
return f"REGEX({stack[-2]}, {stack[-1]})"
case Operation.NOT_REGEX:
return f"NOT_REGEX({stack[-2]}, {stack[-1]})"
case Operation.IREGEX:
return f"IREGEX({stack[-2]}, {stack[-1]})"
case Operation.NOT_IREGEX:
return f"NOT_IREGEX({stack[-2]}, {stack[-1]})"
case Operation.IN_COHORT:
return f"IN_COHORT({stack[-2]}, {stack[-1]})"
case Operation.NOT_IN_COHORT:
return f"NOT_IN_COHORT({stack[-2]}, {stack[-1]})"
case Operation.GET_GLOBAL:
return f"GET_GLOBAL({', '.join(str(stack[-i]) for i in range(bytecode[ip+1]))})"
case Operation.POP:
return f"POP({stack[-1]})"
case Operation.RETURN:
if len(call_stack) > 1:
return f"RETURN({stack[-1]}) --> {call_stack[-2].ip}"
else:
return "RETURN"
case Operation.GET_LOCAL:
return f"GET_LOCAL({bytecode[ip+1]})"
case Operation.SET_LOCAL:
return f"SET_LOCAL({bytecode[ip + 1]}, {stack[-1]})"
case Operation.GET_UPVALUE:
return f"GET_UPVALUE({bytecode[ip+1]})"
case Operation.SET_UPVALUE:
return f"SET_UPVALUE({bytecode[ip + 1]})"
case Operation.CLOSE_UPVALUE:
return "CLOSE_UPVALUE"
case Operation.GET_PROPERTY:
return f"GET_PROPERTY({stack[-2]}, {stack[-1]})"
case Operation.GET_PROPERTY_NULLISH:
return f"GET_PROPERTY_NULLISH({stack[-2]}, {stack[-1]})"
case Operation.SET_PROPERTY:
return f"SET_PROPERTY({stack[-3]}, {stack[-2]}, {stack[-1]})"
case Operation.DICT:
return f"DICT({bytecode[ip+1]})"
case Operation.ARRAY:
return f"ARRAY({bytecode[ip+1]})"
case Operation.TUPLE:
return f"TUPLE({bytecode[ip+1]})"
case Operation.JUMP:
return f"JUMP({'+' if bytecode[ip+1] >= 0 else ''}{bytecode[ip+1]})"
case Operation.JUMP_IF_FALSE:
return f"JUMP_IF_FALSE({'+' if bytecode[ip+1] >= 0 else ''}{bytecode[ip+1]}, {bool(stack[-1])})"
case Operation.JUMP_IF_STACK_NOT_NULL:
return (
f"JUMP_IF_STACK_NOT_NULL({'+' if bytecode[ip+1] >= 0 else ''}{bytecode[ip+1]}, {bool(stack[-1])})"
)
case Operation.DECLARE_FN:
return f"DECLARE_FN({bytecode[ip+1]}, args={bytecode[ip+2]}, ops={bytecode[ip+3]})"
case Operation.CALLABLE:
return f"CALLABLE({bytecode[ip+1]}, args={bytecode[ip+2]}, upvalues={bytecode[ip+3]}, ops={bytecode[ip+4]})"
case Operation.CLOSURE:
return f"CLOSURE"
case Operation.CALL_GLOBAL:
return f"CALL_GLOBAL({bytecode[ip+1]}, {', '.join(str(stack[-(bytecode[ip+2] - i)]) for i in range(bytecode[ip+2]))})"
case Operation.CALL_LOCAL:
return f"CALL_LOCAL({bytecode[ip+1]} {', '.join(str(stack[-(bytecode[ip+1] - i)]) for i in range(bytecode[ip+1]))})"
case Operation.TRY:
return f"TRY(+{bytecode[ip+1]})"
case Operation.POP_TRY:
return "POP_TRY"
case Operation.THROW:
return f"THROW({stack[-1]})"
return symbol.name
except Exception as e:
return f"{symbol.name}(ERROR: {e})"
def color_bytecode(bytecode: list) -> list:
colored = ["op.START", f"version: {bytecode[1]}"] if bytecode[0] == "_H" else ["op.START"]
ip = len(colored)
while ip < len(bytecode):
symbol = bytecode[ip]
match symbol:
case Operation.STRING:
add = ["op.STRING", f"string: {bytecode[ip+1]}"]
case Operation.INTEGER:
add = ["op.INTEGER", f"integer: {bytecode[ip+1]}"]
case Operation.FLOAT:
add = ["op.FLOAT", f"float: {bytecode[ip+1]}"]
case Operation.TRUE:
add = ["op.TRUE"]
case Operation.FALSE:
add = ["op.FALSE"]
case Operation.NULL:
add = ["op.NULL"]
case Operation.NOT:
add = ["op.NOT"]
case Operation.AND:
add = ["op.AND", f"expr count: {bytecode[ip+1]}"]
case Operation.OR:
add = ["op.OR", f"expr count: {bytecode[ip+1]}"]
case Operation.PLUS:
add = ["op.PLUS"]
case Operation.MINUS:
add = ["op.MINUS"]
case Operation.MULTIPLY:
add = ["op.MULTIPLY"]
case Operation.DIVIDE:
add = ["op.DIVIDE"]
case Operation.EQ:
add = ["op.EQ"]
case Operation.NOT_EQ:
add = ["op.NOT_EQ"]
case Operation.GT:
add = ["op.GT"]
case Operation.GT_EQ:
add = ["op.GT_EQ"]
case Operation.LT:
add = ["op.LT"]
case Operation.LT_EQ:
add = ["op.LT_EQ"]
case Operation.LIKE:
add = ["op.LIKE"]
case Operation.ILIKE:
add = ["op.ILIKE"]
case Operation.NOT_LIKE:
add = ["op.NOT_LIKE"]
case Operation.NOT_ILIKE:
add = ["op.NOT_ILIKE"]
case Operation.IN:
add = ["op.IN"]
case Operation.NOT_IN:
add = ["op.NOT_IN"]
case Operation.REGEX:
add = ["op.REGEX"]
case Operation.NOT_REGEX:
add = ["op.NOT_REGEX"]
case Operation.IREGEX:
add = ["op.IREGEX"]
case Operation.NOT_IREGEX:
add = ["op.NOT_IREGEX"]
case Operation.IN_COHORT:
add = ["op.IN_COHORT"]
case Operation.NOT_IN_COHORT:
add = ["op.NOT_IN_COHORT"]
case Operation.GET_GLOBAL:
add = ["op.GET_GLOBAL", f"field count: {bytecode[ip+1]}"]
case Operation.POP:
add = ["op.POP"]
case Operation.RETURN:
add = ["op.RETURN"]
case Operation.GET_LOCAL:
add = ["op.GET_LOCAL", f"index: {bytecode[ip+1]}"]
case Operation.SET_LOCAL:
add = ["op.SET_LOCAL", f"index: {bytecode[ip+1]}"]
case Operation.GET_UPVALUE:
add = ["op.GET_UPVALUE", f"index: {bytecode[ip+1]}"]
case Operation.SET_UPVALUE:
add = ["op.SET_UPVALUE", f"index: {bytecode[ip+1]}"]
case Operation.CLOSE_UPVALUE:
add = ["op.CLOSE_UPVALUE"]
case Operation.GET_PROPERTY:
add = ["op.GET_PROPERTY"]
case Operation.GET_PROPERTY_NULLISH:
add = ["op.GET_PROPERTY_NULLISH"]
case Operation.SET_PROPERTY:
add = ["op.SET_PROPERTY"]
case Operation.DICT:
add = ["op.DICT", f"key count: {bytecode[ip+1]}"]
case Operation.ARRAY:
add = ["op.ARRAY", f"element count: {bytecode[ip+1]}"]
case Operation.TUPLE:
add = ["op.TUPLE", f"element count: {bytecode[ip+1]}"]
case Operation.JUMP:
add = ["op.JUMP", f"offset: {'+' if bytecode[ip+1] >= 0 else ''}{bytecode[ip+1]}"]
case Operation.JUMP_IF_FALSE:
add = ["op.JUMP_IF_FALSE", f"offset: {'+' if bytecode[ip+1] >= 0 else ''}{bytecode[ip+1]}"]
case Operation.JUMP_IF_STACK_NOT_NULL:
add = ["op.JUMP_IF_STACK_NOT_NULL", f"offset: {'+' if bytecode[ip+1] >= 0 else ''}{bytecode[ip+1]}"]
case Operation.DECLARE_FN:
add = ["op.DECLARE_FN", f"name: {bytecode[ip+1]}", f"args: {bytecode[ip+2]}", f"ops: {bytecode[ip+3]}"]
case Operation.CALLABLE:
add = [
"op.CALLABLE",
f"name: {bytecode[ip+1]}",
f"args: {bytecode[ip+2]}",
f"upvalues: {bytecode[ip+3]}",
f"ops: {bytecode[ip+4]}",
]
case Operation.CLOSURE:
upvalue_count = bytecode[ip + 1]
add = ["op.CLOSURE", f"upvalues: {upvalue_count}"]
for i in range(upvalue_count):
add.append(f"is_local({i}): {bytecode[ip + 2 + i * 2]}")
add.append(f"index({i}): {bytecode[ip + 2 + i * 2 + 1]}")
case Operation.CALL_LOCAL:
add = ["op.CALL_LOCAL", f"args: {bytecode[ip+1]}"]
case Operation.CALL_GLOBAL:
add = ["op.CALL_GLOBAL", f"name: {bytecode[ip+1]}", f"args: {bytecode[ip+2]}"]
case Operation.TRY:
add = ["op.TRY", f"catch: +{bytecode[ip+1]}"]
case Operation.POP_TRY:
add = ["op.POP_TRY"]
case Operation.THROW:
add = ["op.THROW"]
case _:
add = [f"ERROR: Unknown bytecode {symbol}"]
colored.extend(add)
ip += len(add)
return colored