Skip to content

Commit

Permalink
8292301: [REDO v2] C2 crash when allocating array of size too large
Browse files Browse the repository at this point in the history
Reviewed-by: xliu, thartmann, kvn
  • Loading branch information
rwestrel committed Sep 28, 2022
1 parent c13e0ef commit 1ea0d6b
Show file tree
Hide file tree
Showing 19 changed files with 435 additions and 108 deletions.
2 changes: 1 addition & 1 deletion src/hotspot/share/gc/shared/c2/barrierSetC2.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ class BarrierSetC2: public CHeapObj<mtGC> {
virtual void verify_gc_barriers(Compile* compile, CompilePhase phase) const {}
#endif

virtual bool final_graph_reshaping(Compile* compile, Node* n, uint opcode) const { return false; }
virtual bool final_graph_reshaping(Compile* compile, Node* n, uint opcode, Unique_Node_List& dead_nodes) const { return false; }

virtual bool escape_add_to_con_graph(ConnectionGraph* conn_graph, PhaseGVN* gvn, Unique_Node_List* delayed_worklist, Node* n, uint opcode) const { return false; }
virtual bool escape_add_final_edges(ConnectionGraph* conn_graph, PhaseGVN* gvn, Node* n, uint opcode) const { return false; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,7 @@ bool ShenandoahBarrierSetC2::has_only_shenandoah_wb_pre_uses(Node* n) {
return n->outcnt() > 0;
}

bool ShenandoahBarrierSetC2::final_graph_reshaping(Compile* compile, Node* n, uint opcode) const {
bool ShenandoahBarrierSetC2::final_graph_reshaping(Compile* compile, Node* n, uint opcode, Unique_Node_List& dead_nodes) const {
switch (opcode) {
case Op_CallLeaf:
case Op_CallLeafNoFP: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class ShenandoahBarrierSetC2 : public BarrierSetC2 {
#endif

virtual Node* ideal_node(PhaseGVN* phase, Node* n, bool can_reshape) const;
virtual bool final_graph_reshaping(Compile* compile, Node* n, uint opcode) const;
virtual bool final_graph_reshaping(Compile* compile, Node* n, uint opcode, Unique_Node_List& dead_nodes) const;

virtual bool escape_add_to_con_graph(ConnectionGraph* conn_graph, PhaseGVN* gvn, Unique_Node_List* delayed_worklist, Node* n, uint opcode) const;
virtual bool escape_add_final_edges(ConnectionGraph* conn_graph, PhaseGVN* gvn, Node* n, uint opcode) const;
Expand Down
49 changes: 1 addition & 48 deletions src/hotspot/share/opto/callnode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1551,6 +1551,7 @@ AllocateNode::AllocateNode(Compile* C, const TypeFunc *atype,
init_req( KlassNode , klass_node);
init_req( InitialTest , initial_test);
init_req( ALength , topnode);
init_req( ValidLengthTest , topnode);
C->add_macro_node(this);
}

Expand All @@ -1577,54 +1578,6 @@ Node *AllocateNode::make_ideal_mark(PhaseGVN *phase, Node* obj, Node* control, N
return mark_node;
}

//=============================================================================
Node* AllocateArrayNode::Ideal(PhaseGVN *phase, bool can_reshape) {
if (remove_dead_region(phase, can_reshape)) return this;
// Don't bother trying to transform a dead node
if (in(0) && in(0)->is_top()) return NULL;

const Type* type = phase->type(Ideal_length());
if (type->isa_int() && type->is_int()->_hi < 0) {
if (can_reshape) {
PhaseIterGVN *igvn = phase->is_IterGVN();
// Unreachable fall through path (negative array length),
// the allocation can only throw so disconnect it.
Node* proj = proj_out_or_null(TypeFunc::Control);
Node* catchproj = NULL;
if (proj != NULL) {
for (DUIterator_Fast imax, i = proj->fast_outs(imax); i < imax; i++) {
Node *cn = proj->fast_out(i);
if (cn->is_Catch()) {
catchproj = cn->as_Multi()->proj_out_or_null(CatchProjNode::fall_through_index);
break;
}
}
}
if (catchproj != NULL && catchproj->outcnt() > 0 &&
(catchproj->outcnt() > 1 ||
catchproj->unique_out()->Opcode() != Op_Halt)) {
assert(catchproj->is_CatchProj(), "must be a CatchProjNode");
Node* nproj = catchproj->clone();
igvn->register_new_node_with_optimizer(nproj);

Node *frame = new ParmNode( phase->C->start(), TypeFunc::FramePtr );
frame = phase->transform(frame);
// Halt & Catch Fire
Node* halt = new HaltNode(nproj, frame, "unexpected negative array length");
igvn->add_input_to(phase->C->root(), halt);
phase->transform(halt);

igvn->replace_node(catchproj, phase->C->top());
return this;
}
} else {
// Can't correct it during regular GVN so register for IGVN
phase->C->record_for_igvn(this);
}
}
return NULL;
}

// Retrieve the length from the AllocateArrayNode. Narrow the type with a
// CastII, if appropriate. If we are not allowed to create new nodes, and
// a CastII is appropriate, return NULL.
Expand Down
10 changes: 5 additions & 5 deletions src/hotspot/share/opto/callnode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,7 @@ class AllocateNode : public CallNode {
KlassNode, // type (maybe dynamic) of the obj.
InitialTest, // slow-path test (may be constant)
ALength, // array length (or TOP if none)
ValidLengthTest,
ParmLimit
};

Expand All @@ -883,6 +884,7 @@ class AllocateNode : public CallNode {
fields[KlassNode] = TypeInstPtr::NOTNULL;
fields[InitialTest] = TypeInt::BOOL;
fields[ALength] = t; // length (can be a bad length)
fields[ValidLengthTest] = TypeInt::BOOL;

const TypeTuple *domain = TypeTuple::make(ParmLimit, fields);

Expand Down Expand Up @@ -977,18 +979,16 @@ class AllocateNode : public CallNode {
//
class AllocateArrayNode : public AllocateNode {
public:
AllocateArrayNode(Compile* C, const TypeFunc *atype, Node *ctrl, Node *mem, Node *abio,
Node* size, Node* klass_node, Node* initial_test,
Node* count_val
)
AllocateArrayNode(Compile* C, const TypeFunc* atype, Node* ctrl, Node* mem, Node* abio, Node* size, Node* klass_node,
Node* initial_test, Node* count_val, Node* valid_length_test)
: AllocateNode(C, atype, ctrl, mem, abio, size, klass_node,
initial_test)
{
init_class_id(Class_AllocateArray);
set_req(AllocateNode::ALength, count_val);
set_req(AllocateNode::ValidLengthTest, valid_length_test);
}
virtual int Opcode() const;
virtual Node *Ideal(PhaseGVN *phase, bool can_reshape);

// Dig the length operand out of a array allocation site.
Node* Ideal_length() {
Expand Down
11 changes: 11 additions & 0 deletions src/hotspot/share/opto/cfgnode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2693,6 +2693,17 @@ const Type* CatchNode::Value(PhaseGVN* phase) const {
// Rethrows always throw exceptions, never return
if (call->entry_point() == OptoRuntime::rethrow_stub()) {
f[CatchProjNode::fall_through_index] = Type::TOP;
} else if (call->is_AllocateArray()) {
Node* klass_node = call->in(AllocateNode::KlassNode);
Node* length = call->in(AllocateNode::ALength);
const Type* length_type = phase->type(length);
const Type* klass_type = phase->type(klass_node);
Node* valid_length_test = call->in(AllocateNode::ValidLengthTest);
const Type* valid_length_test_t = phase->type(valid_length_test);
if (length_type == Type::TOP || klass_type == Type::TOP || valid_length_test_t == Type::TOP ||
valid_length_test_t->is_int()->is_con(0)) {
f[CatchProjNode::fall_through_index] = Type::TOP;
}
} else if( call->req() > TypeFunc::Parms ) {
const Type *arg0 = phase->type( call->in(TypeFunc::Parms) );
// Check for null receiver to virtual or interface calls
Expand Down
77 changes: 46 additions & 31 deletions src/hotspot/share/opto/compile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3086,7 +3086,7 @@ void Compile::eliminate_redundant_card_marks(Node* n) {

//------------------------------final_graph_reshaping_impl----------------------
// Implement items 1-5 from final_graph_reshaping below.
void Compile::final_graph_reshaping_impl( Node *n, Final_Reshape_Counts &frc) {
void Compile::final_graph_reshaping_impl(Node *n, Final_Reshape_Counts& frc, Unique_Node_List& dead_nodes) {

if ( n->outcnt() == 0 ) return; // dead node
uint nop = n->Opcode();
Expand Down Expand Up @@ -3137,9 +3137,9 @@ void Compile::final_graph_reshaping_impl( Node *n, Final_Reshape_Counts &frc) {
}
#endif
// Count FPU ops and common calls, implements item (3)
bool gc_handled = BarrierSet::barrier_set()->barrier_set_c2()->final_graph_reshaping(this, n, nop);
bool gc_handled = BarrierSet::barrier_set()->barrier_set_c2()->final_graph_reshaping(this, n, nop, dead_nodes);
if (!gc_handled) {
final_graph_reshaping_main_switch(n, frc, nop);
final_graph_reshaping_main_switch(n, frc, nop, dead_nodes);
}

// Collect CFG split points
Expand All @@ -3148,7 +3148,7 @@ void Compile::final_graph_reshaping_impl( Node *n, Final_Reshape_Counts &frc) {
}
}

void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& frc, uint nop) {
void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& frc, uint nop, Unique_Node_List& dead_nodes) {
switch( nop ) {
// Count all float operations that may use FPU
case Op_AddF:
Expand Down Expand Up @@ -3770,22 +3770,8 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f
// that input may be a chain of Phis. If those phis have no
// other use, then the MemBarAcquire keeps them alive and
// register allocation can be confused.
ResourceMark rm;
Unique_Node_List wq;
wq.push(n->in(MemBarNode::Precedent));
dead_nodes.push(n->in(MemBarNode::Precedent));
n->set_req(MemBarNode::Precedent, top());
while (wq.size() > 0) {
Node* m = wq.pop();
if (m->outcnt() == 0 && m != top()) {
for (uint j = 0; j < m->req(); j++) {
Node* in = m->in(j);
if (in != NULL) {
wq.push(in);
}
}
m->disconnect_inputs(this);
}
}
}
break;
}
Expand Down Expand Up @@ -3858,7 +3844,7 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f
//------------------------------final_graph_reshaping_walk---------------------
// Replacing Opaque nodes with their input in final_graph_reshaping_impl(),
// requires that the walk visits a node's inputs before visiting the node.
void Compile::final_graph_reshaping_walk( Node_Stack &nstack, Node *root, Final_Reshape_Counts &frc ) {
void Compile::final_graph_reshaping_walk(Node_Stack& nstack, Node* root, Final_Reshape_Counts& frc, Unique_Node_List& dead_nodes) {
Unique_Node_List sfpt;

frc._visited.set(root->_idx); // first, mark node as visited
Expand All @@ -3884,7 +3870,7 @@ void Compile::final_graph_reshaping_walk( Node_Stack &nstack, Node *root, Final_
}
} else {
// Now do post-visit work
final_graph_reshaping_impl( n, frc );
final_graph_reshaping_impl(n, frc, dead_nodes);
if (nstack.is_empty())
break; // finished
n = nstack.node(); // Get node from stack
Expand Down Expand Up @@ -3984,7 +3970,8 @@ bool Compile::final_graph_reshaping() {
// Visit everybody reachable!
// Allocate stack of size C->live_nodes()/2 to avoid frequent realloc
Node_Stack nstack(live_nodes() >> 1);
final_graph_reshaping_walk(nstack, root(), frc);
Unique_Node_List dead_nodes;
final_graph_reshaping_walk(nstack, root(), frc, dead_nodes);

// Check for unreachable (from below) code (i.e., infinite loops).
for( uint i = 0; i < frc._tests.size(); i++ ) {
Expand All @@ -3997,7 +3984,7 @@ bool Compile::final_graph_reshaping() {
// 'fall-thru' path, so expected kids is 1 less.
if (n->is_PCTable() && n->in(0) && n->in(0)->in(0)) {
if (n->in(0)->in(0)->is_Call()) {
CallNode *call = n->in(0)->in(0)->as_Call();
CallNode* call = n->in(0)->in(0)->as_Call();
if (call->entry_point() == OptoRuntime::rethrow_stub()) {
required_outcnt--; // Rethrow always has 1 less kid
} else if (call->req() > TypeFunc::Parms &&
Expand All @@ -4006,22 +3993,27 @@ bool Compile::final_graph_reshaping() {
// detected that the virtual call will always result in a null
// pointer exception. The fall-through projection of this CatchNode
// will not be populated.
Node *arg0 = call->in(TypeFunc::Parms);
Node* arg0 = call->in(TypeFunc::Parms);
if (arg0->is_Type() &&
arg0->as_Type()->type()->higher_equal(TypePtr::NULL_PTR)) {
required_outcnt--;
}
} else if (call->entry_point() == OptoRuntime::new_array_Java() &&
call->req() > TypeFunc::Parms+1 &&
call->is_CallStaticJava()) {
// Check for negative array length. In such case, the optimizer has
} else if (call->entry_point() == OptoRuntime::new_array_Java() ||
call->entry_point() == OptoRuntime::new_array_nozero_Java()) {
// Check for illegal array length. In such case, the optimizer has
// detected that the allocation attempt will always result in an
// exception. There is no fall-through projection of this CatchNode .
Node *arg1 = call->in(TypeFunc::Parms+1);
if (arg1->is_Type() &&
arg1->as_Type()->type()->join(TypeInt::POS)->empty()) {
assert(call->is_CallStaticJava(), "static call expected");
assert(call->req() == call->jvms()->endoff() + 1, "missing extra input");
uint valid_length_test_input = call->req() - 1;
Node* valid_length_test = call->in(valid_length_test_input);
call->del_req(valid_length_test_input);
if (valid_length_test->find_int_con(1) == 0) {
required_outcnt--;
}
dead_nodes.push(valid_length_test);
assert(n->outcnt() == required_outcnt, "malformed control flow");
continue;
}
}
}
Expand All @@ -4030,6 +4022,16 @@ bool Compile::final_graph_reshaping() {
record_method_not_compilable("malformed control flow");
return true; // Not all targets reachable!
}
} else if (n->is_PCTable() && n->in(0) && n->in(0)->in(0) && n->in(0)->in(0)->is_Call()) {
CallNode* call = n->in(0)->in(0)->as_Call();
if (call->entry_point() == OptoRuntime::new_array_Java() ||
call->entry_point() == OptoRuntime::new_array_nozero_Java()) {
assert(call->is_CallStaticJava(), "static call expected");
assert(call->req() == call->jvms()->endoff() + 1, "missing extra input");
uint valid_length_test_input = call->req() - 1;
dead_nodes.push(call->in(valid_length_test_input));
call->del_req(valid_length_test_input); // valid length test useless now
}
}
// Check that I actually visited all kids. Unreached kids
// must be infinite loops.
Expand All @@ -4048,6 +4050,19 @@ bool Compile::final_graph_reshaping() {
}
}

while (dead_nodes.size() > 0) {
Node* m = dead_nodes.pop();
if (m->outcnt() == 0 && m != top()) {
for (uint j = 0; j < m->req(); j++) {
Node* in = m->in(j);
if (in != NULL) {
dead_nodes.push(in);
}
}
m->disconnect_inputs(this);
}
}

#ifdef IA32
// If original bytecodes contained a mixture of floats and doubles
// check if the optimizer has made it homogeneous, item (3).
Expand Down
6 changes: 3 additions & 3 deletions src/hotspot/share/opto/compile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1125,9 +1125,9 @@ class Compile : public Phase {
#endif
// Function calls made by the public function final_graph_reshaping.
// No need to be made public as they are not called elsewhere.
void final_graph_reshaping_impl( Node *n, Final_Reshape_Counts &frc);
void final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& frc, uint nop);
void final_graph_reshaping_walk( Node_Stack &nstack, Node *root, Final_Reshape_Counts &frc );
void final_graph_reshaping_impl(Node *n, Final_Reshape_Counts& frc, Unique_Node_List& dead_nodes);
void final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& frc, uint nop, Unique_Node_List& dead_nodes);
void final_graph_reshaping_walk(Node_Stack& nstack, Node* root, Final_Reshape_Counts& frc, Unique_Node_List& dead_nodes);
void eliminate_redundant_card_marks(Node* n);

// Logic cone optimization.
Expand Down
16 changes: 13 additions & 3 deletions src/hotspot/share/opto/graphKit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2601,7 +2601,9 @@ void GraphKit::make_slow_call_ex(Node* call, ciInstanceKlass* ex_klass, bool sep
// Make a catch node with just two handlers: fall-through and catch-all
Node* i_o = _gvn.transform( new ProjNode(call, TypeFunc::I_O, separate_io_proj) );
Node* catc = _gvn.transform( new CatchNode(control(), i_o, 2) );
Node* norm = _gvn.transform( new CatchProjNode(catc, CatchProjNode::fall_through_index, CatchProjNode::no_handler_bci) );
Node* norm = new CatchProjNode(catc, CatchProjNode::fall_through_index, CatchProjNode::no_handler_bci);
_gvn.set_type_bottom(norm);
C->record_for_igvn(norm);
Node* excp = _gvn.transform( new CatchProjNode(catc, CatchProjNode::catch_all_index, CatchProjNode::no_handler_bci) );

{ PreserveJVMState pjvms(this);
Expand Down Expand Up @@ -3852,20 +3854,28 @@ Node* GraphKit::new_array(Node* klass_node, // array klass (maybe variable)
initial_slow_test = initial_slow_test->as_Bool()->as_int_value(&_gvn);
}

const TypeOopPtr* ary_type = _gvn.type(klass_node)->is_klassptr()->as_instance_type();
Node* valid_length_test = _gvn.intcon(1);
if (ary_type->isa_aryptr()) {
BasicType bt = ary_type->isa_aryptr()->elem()->array_element_basic_type();
jint max = TypeAryPtr::max_array_length(bt);
Node* valid_length_cmp = _gvn.transform(new CmpUNode(length, intcon(max)));
valid_length_test = _gvn.transform(new BoolNode(valid_length_cmp, BoolTest::le));
}

// Create the AllocateArrayNode and its result projections
AllocateArrayNode* alloc
= new AllocateArrayNode(C, AllocateArrayNode::alloc_type(TypeInt::INT),
control(), mem, i_o(),
size, klass_node,
initial_slow_test,
length);
length, valid_length_test);

// Cast to correct type. Note that the klass_node may be constant or not,
// and in the latter case the actual array type will be inexact also.
// (This happens via a non-constant argument to inline_native_newArray.)
// In any case, the value of klass_node provides the desired array type.
const TypeInt* length_type = _gvn.find_int_type(length);
const TypeOopPtr* ary_type = _gvn.type(klass_node)->is_klassptr()->as_instance_type();
if (ary_type->isa_aryptr() && length_type != NULL) {
// Try to get a better type than POS for the size
ary_type = ary_type->is_aryptr()->cast_to_size(length_type);
Expand Down
15 changes: 11 additions & 4 deletions src/hotspot/share/opto/loopopts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2037,7 +2037,13 @@ void PhaseIdealLoop::clone_loop_handle_data_uses(Node* old, Node_List &old_new,
// in the loop to break the loop, then test is again outside of the
// loop to determine which way the loop exited.
// Loop predicate If node connects to Bool node through Opaque1 node.
if (use->is_If() || use->is_CMove() || C->is_predicate_opaq(use) || use->Opcode() == Op_Opaque4) {
//
// If the use is an AllocateArray through its ValidLengthTest input,
// make sure the Bool/Cmp input is cloned down to avoid a Phi between
// the AllocateArray node and its ValidLengthTest input that could cause
// split if to break.
if (use->is_If() || use->is_CMove() || C->is_predicate_opaq(use) || use->Opcode() == Op_Opaque4 ||
(use->Opcode() == Op_AllocateArray && use->in(AllocateNode::ValidLengthTest) == old)) {
// Since this code is highly unlikely, we lazily build the worklist
// of such Nodes to go split.
if (!split_if_set) {
Expand Down Expand Up @@ -2413,9 +2419,10 @@ void PhaseIdealLoop::finish_clone_loop(Node_List* split_if_set, Node_List* split
if (split_if_set) {
while (split_if_set->size()) {
Node *iff = split_if_set->pop();
if (iff->in(1)->is_Phi()) {
Node *b = clone_iff(iff->in(1)->as_Phi());
_igvn.replace_input_of(iff, 1, b);
uint input = iff->Opcode() == Op_AllocateArray ? AllocateNode::ValidLengthTest : 1;
if (iff->in(input)->is_Phi()) {
Node *b = clone_iff(iff->in(input)->as_Phi());
_igvn.replace_input_of(iff, input, b);
}
}
}
Expand Down
Loading

1 comment on commit 1ea0d6b

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.