8214114: Switch expressions with try-catch statements

When switch expression contains try-catch, move the stack values into locals before the executing the switch expression, and back when it is done.

Reviewed-by: mcimadamore, vromero
This commit is contained in:
Jan Lahoda 2018-12-11 09:10:24 +01:00
parent e048289d95
commit 655c5d7f35
6 changed files with 707 additions and 14 deletions
src/jdk.compiler/share/classes/com/sun/tools/javac
test/langtools/tools/javac/switchexpr

@ -1121,7 +1121,7 @@ public class Attr extends JCTree.Visitor {
public void visitVarDef(JCVariableDecl tree) {
// Local variables have not been entered yet, so we need to do it now:
if (env.info.scope.owner.kind == MTH) {
if (env.info.scope.owner.kind == MTH || env.info.scope.owner.kind == VAR) {
if (tree.sym != null) {
// parameters have already been entered
env.info.scope.enter(tree.sym);

@ -1633,7 +1633,7 @@ public class Flow {
protected boolean trackable(VarSymbol sym) {
return
sym.pos >= startPos &&
((sym.owner.kind == MTH ||
((sym.owner.kind == MTH || sym.owner.kind == VAR ||
isFinalUninitializedField(sym)));
}
@ -2009,7 +2009,7 @@ public class Flow {
lint = lint.augment(tree.sym);
try{
boolean track = trackable(tree.sym);
if (track && tree.sym.owner.kind == MTH) {
if (track && (tree.sym.owner.kind == MTH || tree.sym.owner.kind == VAR)) {
newVar(tree);
}
if (tree.init != null) {

@ -25,8 +25,6 @@
package com.sun.tools.javac.jvm;
import java.util.function.BiConsumer;
import com.sun.tools.javac.tree.TreeInfo.PosKind;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
@ -166,6 +164,7 @@ public class Gen extends JCTree.Visitor {
boolean inCondSwitchExpression;
Chain switchExpressionTrueChain;
Chain switchExpressionFalseChain;
List<LocalItem> stackBeforeSwitchExpression;
/** Generate code to load an integer constant.
* @param n The integer to be loaded.
@ -1178,13 +1177,59 @@ public class Gen extends JCTree.Visitor {
}
private void doHandleSwitchExpression(JCSwitchExpression tree) {
int prevLetExprStart = code.setLetExprStackPos(code.state.stacksize);
List<LocalItem> prevStackBeforeSwitchExpression = stackBeforeSwitchExpression;
int limit = code.nextreg;
try {
handleSwitch(tree, tree.selector, tree.cases);
stackBeforeSwitchExpression = List.nil();
if (hasTry(tree)) {
//if the switch expression contains try-catch, the catch handlers need to have
//an empty stack. So stash whole stack to local variables, and restore it before
//breaks:
while (code.state.stacksize > 0) {
Type type = code.state.peek();
Name varName = names.fromString(target.syntheticNameChar() +
"stack" +
target.syntheticNameChar() +
tree.pos +
target.syntheticNameChar() +
code.state.stacksize);
VarSymbol var = new VarSymbol(Flags.SYNTHETIC, varName, type,
this.env.enclMethod.sym);
LocalItem item = items.new LocalItem(type, code.newLocal(var));
stackBeforeSwitchExpression = stackBeforeSwitchExpression.prepend(item);
item.store();
}
}
int prevLetExprStart = code.setLetExprStackPos(code.state.stacksize);
try {
handleSwitch(tree, tree.selector, tree.cases);
} finally {
code.setLetExprStackPos(prevLetExprStart);
}
} finally {
code.setLetExprStackPos(prevLetExprStart);
stackBeforeSwitchExpression = prevStackBeforeSwitchExpression;
code.endScopes(limit);
}
}
//where:
private boolean hasTry(JCSwitchExpression tree) {
boolean[] hasTry = new boolean[1];
new TreeScanner() {
@Override
public void visitTry(JCTry tree) {
hasTry[0] = true;
}
@Override
public void visitClassDef(JCClassDecl tree) {
}
@Override
public void visitLambda(JCLambda tree) {
}
}.scan(tree);
return hasTry[0];
}
private void handleSwitch(JCTree swtch, JCExpression selector, List<JCCase> cases) {
int limit = code.nextreg;
@ -1659,14 +1704,17 @@ public class Gen extends JCTree.Visitor {
}
public void visitBreak(JCBreak tree) {
int tmpPos = code.pendingStatPos;
Assert.check(code.isStatementStart());
Env<GenContext> targetEnv = unwind(tree.target, env);
code.pendingStatPos = tmpPos;
final Env<GenContext> targetEnv;
if (tree.isValueBreak()) {
//restore stack as it was before the switch expression:
for (LocalItem li : stackBeforeSwitchExpression) {
li.load();
}
if (inCondSwitchExpression) {
CondItem value = genCond(tree.value, CRT_FLOW_TARGET);
Chain falseJumps = value.jumpFalse();
targetEnv = unwindBreak(tree);
code.resolve(value.trueJumps);
Chain trueJumps = code.branch(goto_);
if (switchExpressionTrueChain == null) {
@ -1684,13 +1732,22 @@ public class Gen extends JCTree.Visitor {
} else {
genExpr(tree.value, pt).load();
code.state.forceStackTop(tree.target.type);
targetEnv = unwindBreak(tree);
targetEnv.info.addExit(code.branch(goto_));
}
} else {
targetEnv = unwindBreak(tree);
targetEnv.info.addExit(code.branch(goto_));
}
endFinalizerGaps(env, targetEnv);
}
//where:
private Env<GenContext> unwindBreak(JCBreak tree) {
int tmpPos = code.pendingStatPos;
Env<GenContext> targetEnv = unwind(tree.target, env);
code.pendingStatPos = tmpPos;
return targetEnv;
}
public void visitContinue(JCContinue tree) {
int tmpPos = code.pendingStatPos;
@ -2138,7 +2195,7 @@ public class Gen extends JCTree.Visitor {
res = items.makeMemberItem(sym, true);
}
result = res;
} else if (sym.kind == VAR && sym.owner.kind == MTH) {
} else if (sym.kind == VAR && (sym.owner.kind == MTH || sym.owner.kind == VAR)) {
result = items.makeLocalItem((VarSymbol)sym);
} else if (isInvokeDynamic(sym)) {
result = items.makeDynamicItem(sym);

@ -23,7 +23,7 @@
/*
* @test
* @bug 8206986 8214529
* @bug 8206986 8214114 8214529
* @summary Verify various corner cases with nested switch expressions.
* @compile --enable-preview -source 12 ExpressionSwitchBugs.java
* @run main/othervm --enable-preview ExpressionSwitchBugs
@ -33,6 +33,7 @@ public class ExpressionSwitchBugs {
public static void main(String... args) {
new ExpressionSwitchBugs().testNested();
new ExpressionSwitchBugs().testAnonymousClasses();
new ExpressionSwitchBugs().testFields();
}
private void testNested() {
@ -84,6 +85,33 @@ public class ExpressionSwitchBugs {
}
}
private void testFields() {
check(3, field);
check(3, ExpressionSwitchBugs.staticField);
}
private final int value = 2;
private final int field = id(switch(value) {
case 0 -> -1;
case 2 -> {
int temp = 0;
temp += 3;
break temp;
}
default -> throw new IllegalStateException();
});
private static final int staticValue = 2;
private static final int staticField = new ExpressionSwitchBugs().id(switch(staticValue) {
case 0 -> -1;
case 2 -> {
int temp = 0;
temp += 3;
break temp;
}
default -> throw new IllegalStateException();
});
private int id(int i) {
return i;
}

@ -23,7 +23,7 @@
/*
* @test
* @bug 8214031
* @bug 8214031 8214114
* @summary Verify switch expressions embedded in various statements work properly.
* @compile --enable-preview -source 12 ExpressionSwitchEmbedding.java
* @run main/othervm --enable-preview ExpressionSwitchEmbedding
@ -63,6 +63,50 @@ public class ExpressionSwitchEmbedding {
throw new IllegalStateException();
}
}
{
int i = 6;
int o = 0;
while (switch (i) {
case 1: try { new ExpressionSwitchEmbedding().throwException(); } catch (Throwable t) { i = 0; break true; }
case 2: try { new ExpressionSwitchEmbedding().throwException(); } catch (Throwable t) { i = 1; break true; }
case 3, 4:
try {
new ExpressionSwitchEmbedding().throwException();
} catch (Throwable t) {
i--;
if (i == 2 || i == 4) {
try {
break switch (i) {
case 2 -> throw new ResultException(true);
case 4 -> false;
default -> throw new IllegalStateException();
};
} catch (ResultException ex) {
break ex.result;
}
} else {
break true;
}
}
default:
try {
new ExpressionSwitchEmbedding().throwException();
} catch (Throwable t) {
i--;
break switch (i) {
case -1 -> false;
case 3 -> true;
default -> true;
};
}
throw new AssertionError();
}) {
o++;
}
if (o != 6 && i >= 0) {
throw new IllegalStateException();
}
}
{
int i = 6;
int o = 0;
@ -91,6 +135,50 @@ public class ExpressionSwitchEmbedding {
throw new IllegalStateException();
}
}
{
int i = 6;
int o = 0;
if (switch (i) {
case 1: try { new ExpressionSwitchEmbedding().throwException(); } catch (Throwable t) { i = 0; break true; }
case 2: try { new ExpressionSwitchEmbedding().throwException(); } catch (Throwable t) { i = 1; break true; }
case 3, 4:
try {
new ExpressionSwitchEmbedding().throwException();
} catch (Throwable t) {
i--;
if (i == 2 || i == 4) {
try {
break switch (i) {
case 2 -> throw new ResultException(true);
case 4 -> false;
default -> throw new IllegalStateException();
};
} catch (ResultException ex) {
break ex.result;
}
} else {
break true;
}
}
default:
try {
new ExpressionSwitchEmbedding().throwException();
} catch (Throwable t) {
i--;
break switch (i) {
case -1 -> false;
case 3 -> true;
default -> true;
};
}
throw new AssertionError();
}) {
o++;
}
if (o != 1 && i != 5) {
throw new IllegalStateException();
}
}
{
int o = 0;
for (int i = 6; (switch (i) {
@ -118,6 +206,49 @@ public class ExpressionSwitchEmbedding {
throw new IllegalStateException();
}
}
{
int o = 0;
for (int i = 6; (switch (i) {
case 1: try { new ExpressionSwitchEmbedding().throwException(); } catch (Throwable t) { i = 0; break true; }
case 2: try { new ExpressionSwitchEmbedding().throwException(); } catch (Throwable t) { i = 1; break true; }
case 3, 4:
try {
new ExpressionSwitchEmbedding().throwException();
} catch (Throwable t) {
i--;
if (i == 2 || i == 4) {
try {
break switch (i) {
case 2 -> throw new ResultException(true);
case 4 -> false;
default -> throw new IllegalStateException();
};
} catch (ResultException ex) {
break ex.result;
}
} else {
break true;
}
}
default:
try {
new ExpressionSwitchEmbedding().throwException();
} catch (Throwable t) {
i--;
break switch (i) {
case -1 -> false;
case 3 -> true;
default -> true;
};
}
throw new AssertionError();
}); ) {
o++;
}
if (o != 6) {
throw new IllegalStateException();
}
}
{
int i = 6;
int o = 0;
@ -146,6 +277,60 @@ public class ExpressionSwitchEmbedding {
throw new IllegalStateException();
}
}
{
int i = 6;
int o = 0;
do {
o++;
} while (switch (i) {
case 1: try { new ExpressionSwitchEmbedding().throwException(); } catch (Throwable t) { i = 0; break true; }
case 2: try { new ExpressionSwitchEmbedding().throwException(); } catch (Throwable t) { i = 1; break true; }
case 3, 4:
try {
new ExpressionSwitchEmbedding().throwException();
} catch (Throwable t) {
i--;
if (i == 2 || i == 4) {
try {
break switch (i) {
case 2 -> throw new ResultException(true);
case 4 -> false;
default -> throw new IllegalStateException();
};
} catch (ResultException ex) {
break ex.result;
}
} else {
break true;
}
}
default:
try {
new ExpressionSwitchEmbedding().throwException();
} catch (Throwable t) {
i--;
break switch (i) {
case -1 -> false;
case 3 -> true;
default -> true;
};
}
throw new AssertionError();
});
if (o != 6 && i >= 0) {
throw new IllegalStateException();
}
}
}
private void throwException() {
throw new RuntimeException();
}
private static final class ResultException extends RuntimeException {
public final boolean result;
public ResultException(boolean result) {
this.result = result;
}
}
}

@ -0,0 +1,423 @@
/*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8214114
* @summary Verify try-catch inside a switch expression works properly.
* @compile --enable-preview -source 12 TryCatch.java
* @run main/othervm --enable-preview TryCatch
*/
public class TryCatch {
public static void main(String[] args) {
{
int val = 3;
for (int p : new int[] {0, 1, 2}) {
int res = 1 + new TryCatch().id(switch(p) {
case 0 -> switch (p + 1) {
case 1:
try {
new TryCatch().throwException();
break -1;
} catch(Throwable ex) {
break val;
}
default: break -1;
};
case 1 -> {
try {
break new TryCatch().id(switch (p + 1) {
case 2:
try {
new TryCatch().throwException();
break -1;
} catch(Throwable ex) {
throw ex;
}
default: break -1;
});
} catch(Throwable ex) {
break val;
}
}
default -> {
try {
new TryCatch().throwException();
break -1;
} catch(Throwable ex) {
break val;
}
}
} - 1);
if (res != 3) {
throw new AssertionError("Unexpected result: " + res);
}
}
}
{
int val = 3;
for (int p : new int[] {0, 1, 2}) {
int x;
int res = new TryCatch().id(val == 3 && switch(p) {
case 0 -> switch (p + 1) {
case 1:
try {
new TryCatch().throwException();
break false;
} catch(Throwable ex) {
break true;
}
default: break false;
};
case 1 -> {
try {
break new TryCatch().id(switch (p + 1) {
case 2:
try {
new TryCatch().throwException();
break false;
} catch(Throwable ex) {
throw ex;
}
default: break false;
});
} catch(Throwable ex) {
break true;
}
}
default -> {
try {
new TryCatch().throwException();
break false;
} catch(Throwable ex) {
break true;
}
}
} && (x = 1) == 1 && x == 1 ? val : -1);
if (res != 3) {
throw new AssertionError("Unexpected result: " + res);
}
}
}
{
int val = 3;
for (E e : new E[] {E.A, E.B, E.C}) {
int res = 1 + new TryCatch().id(switch(e) {
case A -> switch (e.next()) {
case B:
try {
new TryCatch().throwException();
break -1;
} catch(Throwable ex) {
break val;
}
default: break -1;
};
case B -> {
try {
break new TryCatch().id(switch (e.next()) {
case C:
try {
new TryCatch().throwException();
break -1;
} catch(Throwable ex) {
throw ex;
}
default: break -1;
});
} catch(Throwable ex) {
break val;
}
}
default -> {
try {
new TryCatch().throwException();
break -1;
} catch(Throwable ex) {
break val;
}
}
} - 1);
if (res != 3) {
throw new AssertionError("Unexpected result: " + res);
}
}
}
{
int val = 3;
for (E e : new E[] {E.A, E.B, E.C}) {
int x;
int res = new TryCatch().id(val == 3 && switch(e) {
case A -> switch (e.next()) {
case B:
try {
new TryCatch().throwException();
break false;
} catch(Throwable ex) {
break true;
}
default: break false;
};
case B -> {
try {
break new TryCatch().id(switch (e.next()) {
case C:
try {
new TryCatch().throwException();
break false;
} catch(Throwable ex) {
throw ex;
}
default: break false;
});
} catch(Throwable ex) {
break true;
}
}
default -> {
try {
new TryCatch().throwException();
break false;
} catch(Throwable ex) {
break true;
}
}
} && (x = 1) == 1 && x == 1 ? val : -1);
if (res != 3) {
throw new AssertionError("Unexpected result: " + res);
}
}
}
{
int val = 3;
for (String s : new String[] {"", "a", "b"}) {
int res = 1 + new TryCatch().id(switch(s) {
case "" -> switch (s + "c") {
case "c":
try {
new TryCatch().throwException();
break -1;
} catch(Throwable ex) {
break val;
}
default: break -1;
};
case "a" -> {
try {
break new TryCatch().id(switch (s + "c") {
case "ac":
try {
new TryCatch().throwException();
break -1;
} catch(Throwable ex) {
throw ex;
}
default: break -1;
});
} catch(Throwable ex) {
break val;
}
}
default -> {
try {
new TryCatch().throwException();
break -1;
} catch(Throwable ex) {
break val;
}
}
} - 1);
if (res != 3) {
throw new AssertionError("Unexpected result: " + res);
}
}
}
{
int val = 3;
for (String s : new String[] {"", "a", "b"}) {
int x;
int res = new TryCatch().id(val == 3 && switch(s) {
case "" -> switch (s + "c") {
case "c":
try {
new TryCatch().throwException();
break false;
} catch(Throwable ex) {
break true;
}
default: break false;
};
case "a" -> {
try {
break new TryCatch().id(switch (s + "c") {
case "ac":
try {
new TryCatch().throwException();
break false;
} catch(Throwable ex) {
throw ex;
}
default: break false;
});
} catch(Throwable ex) {
break true;
}
}
default -> {
try {
new TryCatch().throwException();
break false;
} catch(Throwable ex) {
break true;
}
}
} && (x = 1) == 1 && x == 1 ? val : -1);
if (res != 3) {
throw new AssertionError("Unexpected result: " + res);
}
}
}
{
int res = new FieldHolder().intTest;
if (res != 3) {
throw new AssertionError("Unexpected result: " + res);
}
}
{
int res = FieldHolder.intStaticTest;
if (res != 3) {
throw new AssertionError("Unexpected result: " + res);
}
}
{
boolean res = new FieldHolder().booleanTest;
if (!res) {
throw new AssertionError("Unexpected result: " + res);
}
}
{
boolean res = FieldHolder.booleanStaticTest;
if (!res) {
throw new AssertionError("Unexpected result: " + res);
}
}
}
static class FieldHolder {
private final int intTest = switch (0) {
case -1: break -1;
default:
try {
break new TryCatch().id(switch (2) {
case 2:
try {
new TryCatch().throwException();
break -1;
} catch(Throwable ex) {
throw ex;
}
default: break -1;
});
} catch(Throwable ex) {
break 3;
}
};
private static final int intStaticTest = switch (0) {
case -1: break -1;
default:
try {
break new TryCatch().id(switch (2) {
case 2:
try {
new TryCatch().throwException();
break -1;
} catch(Throwable ex) {
throw ex;
}
default: break -1;
});
} catch(Throwable ex) {
break 3;
}
};
private final boolean booleanTest = switch (0) {
case -1: break false;
default:
try {
break new TryCatch().id(switch (2) {
case 2:
try {
new TryCatch().throwException();
break false;
} catch(Throwable ex) {
throw ex;
}
default: break false;
});
} catch(Throwable ex) {
break true;
}
};
private static final boolean booleanStaticTest = switch (0) {
case -1: break false;
default:
try {
break new TryCatch().id(switch (2) {
case 2:
try {
new TryCatch().throwException();
break false;
} catch(Throwable ex) {
throw ex;
}
default: break false;
});
} catch(Throwable ex) {
break true;
}
};
}
private int id(int i) {
return i;
}
private boolean id(boolean b) {
return b;
}
private void throwException() {
throw new RuntimeException();
}
enum E {
A, B, C;
public E next() {
return values()[(ordinal() + 1) % values().length];
}
}
}