# HG changeset patch # User jlahoda # Date 1544515824 -3600 # Node ID 9745e4e36dd1cab96860b2464382b003d84532e8 # Parent 4aa8fe00ace9ee04c7701cf34aa6cf56ddfb1d02 8214114: Switch expressions with try-catch statements Summary: 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 diff -r 4aa8fe00ace9 -r 9745e4e36dd1 src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java Tue Dec 11 08:05:38 2018 +0800 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java Tue Dec 11 09:10:24 2018 +0100 @@ -1121,7 +1121,7 @@ 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); diff -r 4aa8fe00ace9 -r 9745e4e36dd1 src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java Tue Dec 11 08:05:38 2018 +0800 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java Tue Dec 11 09:10:24 2018 +0100 @@ -1633,7 +1633,7 @@ 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 @@ 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) { diff -r 4aa8fe00ace9 -r 9745e4e36dd1 src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java Tue Dec 11 08:05:38 2018 +0800 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java Tue Dec 11 09:10:24 2018 +0100 @@ -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 @@ boolean inCondSwitchExpression; Chain switchExpressionTrueChain; Chain switchExpressionFalseChain; + List stackBeforeSwitchExpression; /** Generate code to load an integer constant. * @param n The integer to be loaded. @@ -1178,13 +1177,59 @@ } private void doHandleSwitchExpression(JCSwitchExpression tree) { - int prevLetExprStart = code.setLetExprStackPos(code.state.stacksize); + List 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 cases) { int limit = code.nextreg; @@ -1659,14 +1704,17 @@ } public void visitBreak(JCBreak tree) { - int tmpPos = code.pendingStatPos; Assert.check(code.isStatementStart()); - Env targetEnv = unwind(tree.target, env); - code.pendingStatPos = tmpPos; + final Env 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 @@ } 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 unwindBreak(JCBreak tree) { + int tmpPos = code.pendingStatPos; + Env targetEnv = unwind(tree.target, env); + code.pendingStatPos = tmpPos; + return targetEnv; + } public void visitContinue(JCContinue tree) { int tmpPos = code.pendingStatPos; @@ -2138,7 +2195,7 @@ 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); diff -r 4aa8fe00ace9 -r 9745e4e36dd1 test/langtools/tools/javac/switchexpr/ExpressionSwitchBugs.java --- a/test/langtools/tools/javac/switchexpr/ExpressionSwitchBugs.java Tue Dec 11 08:05:38 2018 +0800 +++ b/test/langtools/tools/javac/switchexpr/ExpressionSwitchBugs.java Tue Dec 11 09:10:24 2018 +0100 @@ -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 static void main(String... args) { new ExpressionSwitchBugs().testNested(); new ExpressionSwitchBugs().testAnonymousClasses(); + new ExpressionSwitchBugs().testFields(); } private void testNested() { @@ -84,6 +85,33 @@ } } + 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; } diff -r 4aa8fe00ace9 -r 9745e4e36dd1 test/langtools/tools/javac/switchexpr/ExpressionSwitchEmbedding.java --- a/test/langtools/tools/javac/switchexpr/ExpressionSwitchEmbedding.java Tue Dec 11 08:05:38 2018 +0800 +++ b/test/langtools/tools/javac/switchexpr/ExpressionSwitchEmbedding.java Tue Dec 11 09:10:24 2018 +0100 @@ -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 @@ -66,6 +66,50 @@ { 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; if (switch (i) { case 1: i = 0; break true; case 2: i = 1; break true; @@ -92,6 +136,50 @@ } } { + 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) { case 1: i = 0; break true; @@ -119,6 +207,49 @@ } } { + 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; do { @@ -146,6 +277,60 @@ 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; + } + } } diff -r 4aa8fe00ace9 -r 9745e4e36dd1 test/langtools/tools/javac/switchexpr/TryCatch.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/langtools/tools/javac/switchexpr/TryCatch.java Tue Dec 11 09:10:24 2018 +0100 @@ -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]; + } + } +}