@@ -860,22 +860,34 @@ protected void handleReturnMismatch(StructuredGraph g, FrameState fs) {
860860 }
861861 }
862862
863- private static final class Target {
863+ protected static final class Target {
864864 final FixedNode entry ;
865865 final FixedNode originalEntry ;
866866 final FrameStateBuilder state ;
867867
868- Target (FixedNode entry , FrameStateBuilder state ) {
868+ public Target (FixedNode entry , FrameStateBuilder state ) {
869869 this .entry = entry ;
870870 this .state = state ;
871871 this .originalEntry = null ;
872872 }
873873
874- Target (FixedNode entry , FrameStateBuilder state , FixedNode originalEntry ) {
874+ public Target (FixedNode entry , FrameStateBuilder state , FixedNode originalEntry ) {
875875 this .entry = entry ;
876876 this .state = state ;
877877 this .originalEntry = originalEntry ;
878878 }
879+
880+ public FixedNode getEntry () {
881+ return entry ;
882+ }
883+
884+ public FixedNode getOriginalEntry () {
885+ return originalEntry ;
886+ }
887+
888+ public FrameStateBuilder getState () {
889+ return state ;
890+ }
879891 }
880892
881893 @ SuppressWarnings ("serial" )
@@ -918,7 +930,7 @@ static boolean containsReturnValue(List<ReturnToCallerData> list, ValueNode valu
918930 private LineNumberTable lnt ;
919931 private BitSet emittedLineNumbers ;
920932
921- private ValueNode methodSynchronizedObject ;
933+ protected ValueNode methodSynchronizedObject ;
922934
923935 private List <ReturnToCallerData > returnDataList ;
924936 private ValueNode unwindValue ;
@@ -1934,7 +1946,7 @@ protected void genInvokeSpecial(int cpi, int opcode) {
19341946 genInvokeSpecial (target );
19351947 }
19361948
1937- void genInvokeSpecial (JavaMethod target ) {
1949+ protected void genInvokeSpecial (JavaMethod target ) {
19381950 if (callTargetIsResolved (target )) {
19391951 assert target != null ;
19401952 assert target .getSignature () != null ;
@@ -2953,6 +2965,10 @@ private void beforeReturn(ValueNode x, JavaKind kind) {
29532965 */
29542966 append (new FinalFieldBarrierNode (entryBCI == INVOCATION_ENTRY_BCI ? originalReceiver : null ));
29552967 }
2968+ int expectedDepth = frameState .getMethod ().isSynchronized () ? 1 : 0 ;
2969+ if (frameState .lockDepth (false ) > expectedDepth ) {
2970+ handleUnstructuredLocking ("too few monitorexits exiting frame" , false );
2971+ }
29562972 synchronizedEpilogue (BytecodeFrame .AFTER_BCI , x , kind );
29572973 }
29582974
@@ -2968,29 +2984,61 @@ protected void genMonitorEnter(ValueNode x, int bci) {
29682984 monitorEnter .setStateAfter (createFrameState (bci , monitorEnter ));
29692985 }
29702986
2971- protected void genMonitorExit (ValueNode x , ValueNode escapedValue , int bci , boolean epilogue ) {
2972- // If a bytecode attempts to pop the last lock in a synchronized method then this method
2973- // doesn't having properly matching locks so we should bailout. Normally this is detected by
2974- // the final exit underflowing the lock stack but there is no guarantee that the exit is
2975- // ever parsed so we should bailout here instead.
2976- int expectedDepth = frameState .getMethod ().isSynchronized () && !epilogue ? 1 : 0 ;
2977- if (frameState .lockDepth (false ) == expectedDepth ) {
2978- throw bailout ("unbalanced monitors: too many exits" );
2987+ protected void genMonitorExit (ValueNode x , ValueNode escapedValue , int bci , boolean needsNullCheck ) {
2988+ /*
2989+ * This null check ensures Java spec compatibility, where a NullPointerException has
2990+ * precedence over an IllegalMonitorStateException. In the presence of structured locking,
2991+ * this check should fold away, as the non-nullness is already proven by the corresponding
2992+ * monitorenter.
2993+ */
2994+ ValueNode maybeNullCheckedX = needsNullCheck ? maybeEmitExplicitNullCheck (x ) : x ;
2995+ /*
2996+ * Graal enforces structured locking. In accordance to Rule 2, it has to throw an
2997+ * IllegalMonitorStateException (or deopt) if the number of monitorexits exceeds the number
2998+ * of monitorenters at any time during method execution.
2999+ */
3000+ if (frameState .lockDepth (false ) == 0 ) {
3001+ handleUnstructuredLocking ("too many monitorexits" , false );
3002+ return ;
29793003 }
29803004 MonitorIdNode monitorId = frameState .peekMonitorId ();
29813005 ValueNode lockedObject = frameState .popLock ();
29823006 // if we merged two monitor ids we trust the merging logic checked the correct enter bcis
29833007 if (!monitorId .isMultipleEntry ()) {
29843008 ValueNode originalLockedObject = GraphUtil .originalValue (lockedObject , false );
2985- ValueNode originalX = GraphUtil .originalValue (x , false );
3009+ ValueNode originalX = GraphUtil .originalValue (maybeNullCheckedX , false );
29863010 if (originalLockedObject != originalX ) {
2987- throw bailout (String .format ("unbalanced monitors: mismatch at monitorexit, %s != %s" , originalLockedObject , originalX ));
3011+ // at this point, the lock objects could still be equal, but not visibly to the
3012+ // parser; add a run-time check
3013+ LogicNode eq = append (genObjectEquals (maybeNullCheckedX , lockedObject ));
3014+ IfNode ifNode = append (new IfNode (eq , null , null , BranchProbabilityNode .EXTREMELY_FAST_PATH_PROFILE ));
3015+
3016+ // lock objects not equal
3017+ ifNode .setFalseSuccessor (graph .add (new BeginNode ()));
3018+ lastInstr = ifNode .falseSuccessor ();
3019+ FrameStateBuilder oldState = frameState .copy ();
3020+ frameState .pushLock (lockedObject , monitorId );
3021+ handleMismatchAtMonitorexit ();
3022+ frameState = oldState ;
3023+
3024+ // lock objects equal
3025+ ifNode .setTrueSuccessor (graph .add (new BeginNode ()));
3026+ lastInstr = ifNode .trueSuccessor ();
29883027 }
29893028 }
29903029 MonitorExitNode monitorExit = append (new MonitorExitNode (lockedObject , monitorId , escapedValue ));
29913030 monitorExit .setStateAfter (createFrameState (bci , monitorExit ));
29923031 }
29933032
3033+ @ SuppressWarnings ("unused" )
3034+ protected void handleUnstructuredLocking (String msg , boolean isDeadEnd ) {
3035+ throw bailout ("Unstructured locking: " + msg );
3036+ }
3037+
3038+ protected void handleMismatchAtMonitorexit () {
3039+ append (new DeoptimizeNode (DeoptimizationAction .InvalidateRecompile , DeoptimizationReason .TransferToInterpreter ));
3040+ }
3041+
29943042 protected void genJsr (int dest ) {
29953043 BciBlock successor = currentBlock .getJsrSuccessor ();
29963044 assert successor .startBci == dest : successor .startBci + " != " + dest + " @" + bci ();
@@ -3191,10 +3239,16 @@ private Target checkUnwind(FixedNode target, BciBlock targetBlock, FrameStateBui
31913239 if (targetBlock != blockMap .getUnwindBlock ()) {
31923240 return new Target (target , state );
31933241 }
3194- FrameStateBuilder newState = state ;
3195- newState = newState .copy ();
3242+ FrameStateBuilder newState = state .copy ();
31963243 newState .setRethrowException (false );
3197- if (!method .isSynchronized ()) {
3244+ if (!method .isSynchronized () || methodSynchronizedObject == null ) {
3245+ /*
3246+ * methodSynchronizedObject==null indicates that the methodSynchronizeObject has been
3247+ * released unexpectedly due to unstructured locking but we are already on the path to
3248+ * the unwind for throwing an IllegalMonitorStateException. Thus, we need to break up an
3249+ * exception loop in the unwind path, which would repeatedly try to release the
3250+ * methodSynchronizedObject via the synchronizedEpilogue.
3251+ */
31983252 return new Target (target , newState );
31993253 }
32003254 FixedWithNextNode originalLast = lastInstr ;
@@ -3224,11 +3278,11 @@ private void setEntryState(BciBlock block, FrameStateBuilder entryState) {
32243278 this .entryStateArray [block .id ] = entryState ;
32253279 }
32263280
3227- private void setFirstInstruction (BciBlock block , FixedWithNextNode firstInstruction ) {
3281+ protected void setFirstInstruction (BciBlock block , FixedWithNextNode firstInstruction ) {
32283282 this .firstInstructionArray [block .id ] = firstInstruction ;
32293283 }
32303284
3231- private FixedWithNextNode getFirstInstruction (BciBlock block ) {
3285+ protected FixedWithNextNode getFirstInstruction (BciBlock block ) {
32323286 return firstInstructionArray [block .id ];
32333287 }
32343288
@@ -3272,6 +3326,19 @@ private FixedNode createTarget(BciBlock block, FrameStateBuilder state, boolean
32723326 assert !block .isExceptionEntry () || state .stackSize () == 1 : Assertions .errorMessage (block , state );
32733327
32743328 try (DebugCloseable context = openNodeContext (state , block .startBci )) {
3329+ if (block == blockMap .getUnwindBlock ()) {
3330+ int expectedDepth = state .getMethod ().isSynchronized () && methodSynchronizedObject != null ? 1 : 0 ;
3331+ /*
3332+ * methodSynchronizeObject==null indicates that the methodSynchronizeObject has been
3333+ * released unexpectedly but we are already on the path to the unwind for throwing
3334+ * an IllegalMonitorStateException. Thus, we need to break up an exception loop in
3335+ * the unwind path.
3336+ */
3337+ if (state .lockDepth (false ) != expectedDepth ) {
3338+ return handleUnstructuredLockingForUnwindTarget ("too few monitorexits exiting frame" , state );
3339+ }
3340+ }
3341+
32753342 if (getFirstInstruction (block ) == null ) {
32763343 /*
32773344 * This is the first time we see this block as a branch target. Create and return a
@@ -3294,8 +3361,7 @@ private FixedNode createTarget(BciBlock block, FrameStateBuilder state, boolean
32943361 } else {
32953362 setFirstInstruction (block , graph .add (new BeginNode ()));
32963363 }
3297- Target target = checkUnwind (getFirstInstruction (block ), block , state );
3298- target = checkLoopExit (target , block );
3364+ Target target = checkLoopExit (checkUnwind (getFirstInstruction (block ), block , state ), block );
32993365 FixedNode result = target .entry ;
33003366 FrameStateBuilder currentEntryState = target .state == state ? (canReuseState ? state : state .copy ()) : target .state ;
33013367 setEntryState (block , currentEntryState );
@@ -3313,7 +3379,7 @@ private FixedNode createTarget(BciBlock block, FrameStateBuilder state, boolean
33133379 */
33143380 LoopBeginNode loopBegin = (LoopBeginNode ) getFirstInstruction (block );
33153381 LoopEndNode loopEnd = graph .add (new LoopEndNode (loopBegin ));
3316- Target target = checkLoopExit (new Target (loopEnd , state .copy ()), block );
3382+ Target target = checkUnstructuredLocking ( checkLoopExit (new Target (loopEnd , state .copy ()), block ), block , getEntryState ( block ) );
33173383 FixedNode result = target .entry ;
33183384 /*
33193385 * It is guaranteed that a loop header cannot be an ExceptionDispatchBlock. By the
@@ -3324,8 +3390,8 @@ private FixedNode createTarget(BciBlock block, FrameStateBuilder state, boolean
33243390 assert !getEntryState (block ).rethrowException ();
33253391 target .state .setRethrowException (false );
33263392 getEntryState (block ).merge (loopBegin , target .state );
3327-
33283393 debug .log ("createTarget %s: merging backward branch to loop header %s, result: %s" , block , loopBegin , result );
3394+
33293395 return result ;
33303396 }
33313397 assert currentBlock == null || currentBlock .getId () < block .getId () : "must not be backward branch" ;
@@ -3361,16 +3427,29 @@ private FixedNode createTarget(BciBlock block, FrameStateBuilder state, boolean
33613427
33623428 // The EndNode for the newly merged edge.
33633429 EndNode newEnd = graph .add (new EndNode ());
3364- Target target = checkLoopExit (checkUnwind (newEnd , block , state ), block );
3430+ Target target = checkUnstructuredLocking ( checkLoopExit (checkUnwind (newEnd , block , state ), block ), block , getEntryState ( block ) );
33653431 FixedNode result = target .entry ;
33663432 getEntryState (block ).merge (mergeNode , target .state );
33673433 mergeNode .addForwardEnd (newEnd );
3368-
33693434 debug .log ("createTarget %s: merging state, result: %s" , block , result );
3435+
33703436 return result ;
33713437 }
33723438 }
33733439
3440+ @ SuppressWarnings ("unused" )
3441+ protected FixedNode handleUnstructuredLockingForUnwindTarget (String msg , FrameStateBuilder state ) {
3442+ throw bailout ("Unstructured locking: " + msg );
3443+ }
3444+
3445+ @ SuppressWarnings ("unused" )
3446+ protected Target checkUnstructuredLocking (Target target , BciBlock targetBlock , FrameStateBuilder mergeState ) {
3447+ if (mergeState .areLocksMergeableWith (target .state )) {
3448+ return target ;
3449+ }
3450+ throw bailout ("Locks cannot be merged. Possibly unstructured locking, which is not supported." );
3451+ }
3452+
33743453 /**
33753454 * Returns a block begin node with the specified state. If the specified probability is 0, the
33763455 * block deoptimizes immediately.
@@ -3429,9 +3508,7 @@ protected void processBlock(BciBlock block) {
34293508 }
34303509
34313510 private void handleUnwindBlock () {
3432- if (frameState .lockDepth (false ) != 0 ) {
3433- throw bailout ("unbalanced monitors: too few exits exiting frame" );
3434- }
3511+ GraalError .guarantee (frameState .lockDepth (false ) == 0 , "Unstructured locking: unreleased locks at unwind block!" );
34353512 assert !frameState .rethrowException ();
34363513 if (parent == null ) {
34373514 createUnwind ();
@@ -3470,12 +3547,9 @@ private void synchronizedEpilogue(int bci, ValueNode currentReturnValue, JavaKin
34703547 // push the return value on the stack
34713548 frameState .push (currentReturnValueKind , currentReturnValue );
34723549 }
3473- genMonitorExit (methodSynchronizedObject , currentReturnValue , bci , true );
3550+ genMonitorExit (methodSynchronizedObject , currentReturnValue , bci , false );
34743551 assert !frameState .rethrowException ();
34753552 }
3476- if (frameState .lockDepth (false ) != 0 ) {
3477- throw bailout ("unbalanced monitors: too few exits exiting frame" );
3478- }
34793553 }
34803554 }
34813555
@@ -5673,7 +5747,7 @@ public final void processBytecode(int bci, int opcode) {
56735747 case CHECKCAST : genCheckCast (stream .readCPI ()); break ;
56745748 case INSTANCEOF : genInstanceOf (stream .readCPI ()); break ;
56755749 case MONITORENTER : genMonitorEnter (frameState .pop (JavaKind .Object ), stream .nextBCI ()); break ;
5676- case MONITOREXIT : genMonitorExit (frameState .pop (JavaKind .Object ), null , stream .nextBCI (), false ); break ;
5750+ case MONITOREXIT : genMonitorExit (frameState .pop (JavaKind .Object ), null , stream .nextBCI (), true ); break ;
56775751 case MULTIANEWARRAY : genNewMultiArray (stream .readCPI ()); break ;
56785752 case IFNULL : genIfNull (Condition .EQ ); break ;
56795753 case IFNONNULL : genIfNull (Condition .NE ); break ;
0 commit comments