"""Controlled kill: a client sends sap_push_source (testclasses include) and is killed with SIGKILL `delay` seconds after the request left. Then the enqueue table is read and a second write is tried.""" import json, os, signal, subprocess, sys, time sys.path.insert(0, "/Users/erhankeseli/projects/abap-llm/harness/scripts_probe") from lockprobe import * MAIN = """CLASS {n} DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. METHODS run RETURNING VALUE(rv) TYPE i. ENDCLASS. CLASS {n} IMPLEMENTATION. METHOD run. rv = 1. ENDMETHOD. ENDCLASS. """ def tc(extra): body = "".join(" cl_abap_unit_assert=>assert_equals( act = %d exp = %d ).\n" % (i, i) for i in range(extra)) return ("CLASS ltc DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.\n PRIVATE SECTION.\n METHODS t1 FOR TESTING.\n" "ENDCLASS.\nCLASS ltc IMPLEMENTATION.\n METHOD t1.\n" + body + " ENDMETHOD.\nENDCLASS.\n") VICTIM = r''' import sys, json sys.path.insert(0, "/Users/erhankeseli/projects/abap-llm/harness") from harness.adt_client import load_env; load_env("/Users/erhankeseli/projects/abap-llm/harness/.env") from harness.mcp_client import McpClient args = json.loads(sys.argv[1]) m = McpClient().open() print("SENT", flush=True) m.call("sap_push_source", args) print("DONE", flush=True) ''' def attempt(delay, n, extra=40): name = "ZPROBE0KL_%03d" % n with McpClient() as m: print("create", m.call("sap_create_object", {"objectType": "CLAS", "objectName": name, "packageName": "$TMP", "description": "kill test"})[1][:60]) print("main ", m.call("sap_push_source", {"objectType": "CLAS", "objectName": name, "source": MAIN.format(n=name.lower())})[1][:70]) args = {"objectType": "CLAS", "objectName": name, "includeType": "testclasses", "source": tc(extra)} p = subprocess.Popen([sys.executable, "-c", VICTIM, json.dumps(args)], stdout=subprocess.PIPE, text=True) assert p.stdout.readline().strip() == "SENT" t0 = time.time() time.sleep(delay) done_before_kill = p.poll() is not None try: os.kill(p.pid, signal.SIGKILL) except ProcessLookupError: done_before_kill = True p.wait() print("victim killed %.2f s after the request was sent (finished before kill: %s)" % (time.time() - t0, done_before_kill)) time.sleep(3) out = {"name": name, "delay": delay} with McpClient() as m: out["enqueue"] = read_locks(m) e, t = m.call("sap_push_source", {"objectType": "CLAS", "objectName": name, "includeType": "testclasses", "source": tc(extra + 1)}) out["second_write"] = t[:300] e, t = m.call("sap_push_element", {"objectType": "CLAS", "objectName": name, "element": "RUN", "source": " METHOD run.\n rv = 2.\n ENDMETHOD.\n"}) out["push_element"] = t[:300] out["leak"] = "currently editing" in out["second_write"] or "[LOCK]" in out["second_write"] or "[LOCK]" in out["push_element"] if not out["leak"]: out["delete"] = delete_uris(["/sap/bc/adt/oo/classes/" + name.lower()]) return out if __name__ == "__main__": delays = [float(x) for x in sys.argv[1].split(",")] n0 = int(sys.argv[2]) if len(sys.argv) > 2 else 1 for i, d in enumerate(delays): o = attempt(d, n0 + i, int(sys.argv[3]) if len(sys.argv) > 3 else 40) print(json.dumps({k: (v if k != "enqueue" else v[:700]) for k, v in o.items()}, indent=1)) if o["leak"]: print("LEAK at delay", d, "- stopping, object", o["name"], "needs SM12"); break