{"record":{"id":"a5d26b0b4ca49b78","repo":"oracle/graal","slug":"s-must-not-kill-s","errorCode":null,"errorMessage":"%s must not kill %s","messagePattern":"(.+?) must not kill (.+?)","errorType":"exception","errorClass":"GraalError","httpStatus":null,"severity":"error","filePath":"compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/Graph.java","lineNumber":774,"sourceCode":"         * This method dispatches the event to user-defined triggers. The methods that change the\n         * graph (typically in Graph and Node) must call this method to dispatch the event.\n         *\n         * @param e an event\n         * @param node the node related to {@code e}\n         */\n        final void event(NodeEvent e, Node node) {\n            switch (e) {\n                case CONTROL_FLOW_CHANGED:\n                    controlFlowChanged(node);\n                    break;\n                case INPUT_CHANGED:\n                    inputChanged(node);\n                    break;\n                case ZERO_USAGES:\n                    GraalError.guarantee(node.isAlive(), \"must be alive\");\n                    usagesDroppedToZero(node);\n                    if (!node.isAlive()) {\n                        throw new GraalError(\"%s must not kill %s\", this, node);\n                    }\n                    break;\n                case NODE_ADDED:\n                    nodeAdded(node);\n                    break;\n                case NODE_REMOVED:\n                    nodeRemoved(node);\n                    break;\n                case BEFORE_DECODING_FIELDS:\n                    beforeDecodingFields(node);\n                    break;\n                case AFTER_DECODING_FIELDS:\n                    afterDecodingFields(node);\n                    break;\n            }\n            changed(e, node);\n        }\n","sourceCodeStart":756,"sourceCodeEnd":792,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/Graph.java#L756-L792","documentation":"When a node's usage count drops to zero, Graph delivers a ZERO_USAGES NodeEvent to registered NodeEventListeners. After invoking the listener's usagesDroppedToZero callback, the graph guarantees the node must still be alive; if the callback itself killed (removed) the node, this GraalError('%s must not kill %s') is thrown naming the listener and the killed node. It is a contract violation by the listener, not a graph corruption bug.","triggerScenarios":"Implementing a NodeEventListener (e.g. via Graph.addNodeEventListener / NodeEventListener for incremental verification, tracking, or custom cleanup) whose usagesDroppedToZero override removes or replaces the node — for example calling node.replaceAndDelete(), node.safeDelete(), or rewriting inputs in a way that deletes the just-orphaned node inside the callback.","commonSituations":"Custom instrumentation/verification passes that watch for dead nodes and eagerly delete them; porting listeners written against older Graal where the contract was laxer; debugging utilities that 'clean up' zero-usage nodes inline instead of deferring.","solutions":["Do not delete the node inside usagesDroppedToZero; only observe it. Defer any deletion to after the event (e.g. collect the node in a list and process it when the event scope closes).","If you must rewire, replace usages of the node rather than deleting the node itself while inside the callback.","Check whether you actually need ZERO_USAGES handling at all — NodeEventListener.usagesDroppedToZero has a default no-op implementation; override it only for genuine observation."],"exampleFix":"// before\ngraph.addNodeEventListener(new NodeEventListener() {\n    @Override\n    public void usagesDroppedToZero(Node node) {\n        node.safeDelete(); // GraalError: listener must not kill node\n    }\n});\n\n// after\nList<Node> dead = new ArrayList<>();\ngraph.addNodeEventListener(new NodeEventListener() {\n    @Override\n    public void usagesDroppedToZero(Node node) {\n        dead.add(node); // observe only; delete later\n    }\n});\n// ... after event scope:\n// dead.forEach(Node::safeDelete);","handlingStrategy":"validation","validationCode":"// contract check inside a listener\nclass ObservingListener implements NodeEventListener {\n    private final List<Node> zeroUsage = new ArrayList<>();\n    @Override public void usagesDroppedToZero(Node n) {\n        // observe only — never mutate/delete here\n        zeroUsage.add(n);\n    }\n    // process zeroUsage after the event scope ends\n}\n\nstatic boolean listenerSafe(NodeEventListener l, Node n) {\n    l.event(NodeEvent.ZERO_USAGES, n);\n    return n.isAlive(); // false means the listener violated the contract\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never delete, replaceAndDelete, or rewire the reported node inside usagesDroppedToZero — only record it.","Queue nodes observed in ZERO_USAGES and act on them after the NodeEventScope closes.","Add assertions in tests that your listener leaves every event node alive."],"tags":["graph-ir","node-events","listener-contract","api-misuse","graal-error"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}