# JVM Bug Analysis: SIGSEGV in LoadNode::can_split_through_phi_base ## Context The application crashes ~12 minutes after startup with a JVM SIGSEGV. The crash is in the C2 JIT compiler (not application code). JDK version is OpenJDK 25.0.1+8-27. ## Root Cause Null pointer dereference in LoadNode::can_split_through_phi_base -- the null check on `base` is placed *after* the first dereference of `base`. ### The Buggy Code (src/hotspot/share/opto/memnode.cpp) bool LoadNode::can_split_through_phi_base(PhaseGVN* phase) { Node* mem = in(Memory); Node* address = in(Address); intptr_t ignore = 0; Node* base = AddPNode::Ideal_base_and_offset(address, phase, ignore); if (base->is_CastPP()) { // <-- BUG: dereferences base before null check base = base->in(1); } if (req() > 3 || base == nullptr || !base->is_Phi()) { // null check is too late return false; } // ... } AddPNode::Ideal_base_and_offset() returns nullptr when the address node is not a recognized AddP form. The code then calls base->is_CastPP() which reads base->_class_id at offset 0x2c from the null pointer, causing the SIGSEGV. ### Disassembly Confirmation The crash instruction from the hs_err log: => 0x00007f47c6f9130e: 8b 50 2c mov edx, [rax+0x2c] ; RAX=0x0 -> SIGSEGV Preceding instructions: call AddPNode::Ideal_base_and_offset ; returns base in RAX (= 0x0) mov esi, [rbx+0x18] ; loads this->_cnt mov edx, [rax+0x2c] ; reads base->_class_id -> CRASH Offset 0x2c in a Node object is the _class_id field used by is_CastPP(). RAX=0x0 means Ideal_base_and_offset returned null. ### Call Path ConnectionGraph::compute_escape() -> adjust_scalar_replaceable_state(ptn, reducible_merges) -> can_reduce_phi(use_n->as_Phi()) -> can_reduce_check_users(ophi, 0) -> [for AddP users] use_use->as_Load()->can_split_through_phi_base(_igvn) -> AddPNode::Ideal_base_and_offset() returns nullptr -> base->is_CastPP() // SIGSEGV ### Contrast With split_through_phi (Same File) The companion function split_through_phi handles null correctly: Node* base = AddPNode::Ideal_base_and_offset(address, phase, ignore); bool base_is_phi = (base != nullptr) && base->is_Phi(); // proper null guard can_split_through_phi_base was likely factored out of split_through_phi as a precondition check but the null guard was not carried over. ### Bug Status - Unfixed as of JDK master (JDK 26 dev), jdk-25+22, and JDK 25.0.1+8 - Introduced as part of the allocation-merge reduction work (JDK-8287061 and related) - Not yet reported in the OpenJDK bug tracker (no matching entry found) ### The Fix Move the null check before the first dereference: bool LoadNode::can_split_through_phi_base(PhaseGVN* phase) { Node* mem = in(Memory); Node* address = in(Address); intptr_t ignore = 0; Node* base = AddPNode::Ideal_base_and_offset(address, phase, ignore); if (base == nullptr) { // null check FIRST return false; } if (base->is_CastPP()) { // now safe to dereference base = base->in(1); } if (req() > 3 || !base->is_Phi()) { return false; } // ... rest unchanged }