{"record":{"id":"d21d33684f5a18f0","repo":"oracle/graal","slug":"accessing-node-id-in-s-across-d-graph-compressio","errorCode":null,"errorMessage":"accessing node id in %s across %d graph compression%s","messagePattern":"accessing node id in (.+?) across (.+?) graph compression(.+?)","errorType":"exception","errorClass":"GraalGraphError","httpStatus":null,"severity":"error","filePath":"compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/NodeIdAccessor.java","lineNumber":52,"sourceCode":"    NodeIdAccessor(Graph graph) {\n        this.graph = graph;\n        this.epoch = graph.compressions;\n    }\n\n    Graph getGraph() {\n        return graph;\n    }\n\n    /**\n     * Verifies that node identifiers have not changed since this object was created.\n     *\n     * @return true if the check succeeds\n     * @throws GraalGraphError if the check fails\n     */\n    boolean verifyIdsAreStable() {\n        int compressions = graph.compressions - epoch;\n        if (compressions != 0) {\n            throw new GraalGraphError(\"accessing node id in %s across %d graph compression%s\", graph, compressions, compressions == 1 ? \"\" : \"s\");\n        }\n        return true;\n    }\n\n    /**\n     * Gets the identifier for a node. If assertions are enabled, this method asserts that the\n     * identifier is stable.\n     */\n    int getNodeId(Node node) {\n        assert verifyIdsAreStable();\n        if (!node.isAlive()) {\n            throw new InternalError(node.toString());\n        }\n        return node.id();\n    }\n}\n","sourceCodeStart":34,"sourceCodeEnd":69,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/graph/NodeIdAccessor.java#L34-L69","documentation":"Node ids are dense array indices that are renumbered whenever a graph is compressed (StructuredGraph.maybeCompress reuses ids of deleted nodes to keep arrays small). A NodeIdAccessor records the graph's compression counter (epoch) at creation; verifyIdsAreStable() throws GraalGraphError if any compression happened since, because an id captured earlier may now point at a different node. The check runs via assert, so it fires with assertions enabled (the default in Graal development runs).","triggerScenarios":"Creating a NodeIdAccessor (or NodeMap/NodeBitMap keyed by ids) in one phase, then calling getNodeId() after a phase that triggers graph compression between them. Caching raw node ids (int values) across maybeCompress() calls. Long-lived maps built in an early phase and consulted in a late phase.","commonSituations":"Custom phases that keep NodeMaps alive across phase suites; Graal runs compression at phase-suite boundaries to reclaim id space. Debug code that stashes node ids for later correlation. Timing-dependent hits: only graphs with enough deletions actually compress, so the bug appears intermittently.","solutions":["Recreate NodeIdAccessor-dependent structures (NodeMap, NodeBitMap) after any point where compression may occur instead of caching them across phases.","Never store raw node ids; store the Node objects or use Node.getId() only for transient logging within one phase.","Register with the graph's compression events / check graph.compressions against your epoch before using ids (mirror verifyIdsAreStable).","As a stopgap for experiments you can disable compression (e.g. StructuredGraph option governing maybeCompress), but fix the lifecycle for real code."],"exampleFix":"// before\nNodeMap<T> map = new NodeMap<>(graph);\nrunOtherPhases(graph); // may compress the graph\nmap.set(someNode, value); // id epoch mismatch -> GraalGraphError\n\n// after\nrunOtherPhases(graph);\nNodeMap<T> map = new NodeMap<>(graph); // build after the last possible compression\nmap.set(someNode, value);","handlingStrategy":"validation","validationCode":"// NodeIdAccessor is package-private; the caller-level equivalent is to not cache id-dependent state:\nint epoch = graph.getCompressions(); // use whatever accessor your Graal version exposes (graph.compressions)\n// ... later, before using ids:\nif (epoch != graph.getCompressions()) {\n    rebuildIdKeyedStructures(); // recreate NodeMap/NodeBitMap now\n}","typeGuard":null,"tryCatchPattern":"try {\n    int id = accessor.getNodeId(node); // throws GraalGraphError via assert when enabled\n} catch (GraalGraphError e) {\n    // ids stale: rebuild accessor and retry the lookup\n    accessor = new NodeIdAccessor(graph);\n    id = accessor.getNodeId(node);\n}","preventionTips":["Scope NodeMap/NodeBitMap/NodeIdAccessor lifetimes to a single phase; rebuild across phase-suite boundaries where compression happens.","Never persist raw node ids beyond the phase that produced them.","Run development builds with assertions enabled so instability is caught at first access."],"tags":["java","graal","graal-compiler","graph","node-ids","compression","lifecycle"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}