Fri, 18 Oct 2013 15:03:34 -0700
8026749: Missing LV table in lambda bodies
Reviewed-by: vromero, jlahoda
1.1 --- a/src/share/classes/com/sun/tools/javac/code/Flags.java Thu Oct 17 19:10:19 2013 -0700 1.2 +++ b/src/share/classes/com/sun/tools/javac/code/Flags.java Fri Oct 18 15:03:34 2013 -0700 1.3 @@ -270,6 +270,11 @@ 1.4 */ 1.5 public static final long POTENTIALLY_AMBIGUOUS = 1L<<48; 1.6 1.7 + /** 1.8 + * Flag that marks a synthetic method body for a lambda expression 1.9 + */ 1.10 + public static final long LAMBDA_METHOD = 1L<<49; 1.11 + 1.12 /** Modifier masks. 1.13 */ 1.14 public static final int 1.15 @@ -378,7 +383,8 @@ 1.16 NOT_IN_PROFILE(Flags.NOT_IN_PROFILE), 1.17 BAD_OVERRIDE(Flags.BAD_OVERRIDE), 1.18 SIGNATURE_POLYMORPHIC(Flags.SIGNATURE_POLYMORPHIC), 1.19 - THROWS(Flags.THROWS); 1.20 + THROWS(Flags.THROWS), 1.21 + LAMBDA_METHOD(Flags.LAMBDA_METHOD); 1.22 1.23 Flag(long flag) { 1.24 this.value = flag;
2.1 --- a/src/share/classes/com/sun/tools/javac/comp/Flow.java Thu Oct 17 19:10:19 2013 -0700 2.2 +++ b/src/share/classes/com/sun/tools/javac/comp/Flow.java Fri Oct 18 15:03:34 2013 -0700 2.3 @@ -1718,9 +1718,9 @@ 2.4 if (tree.body == null) { 2.5 return; 2.6 } 2.7 - /* MemberEnter can generate synthetic methods, ignore them 2.8 + /* Ignore synthetic methods, except for translated lambda methods. 2.9 */ 2.10 - if ((tree.sym.flags() & SYNTHETIC) != 0) { 2.11 + if ((tree.sym.flags() & (SYNTHETIC | LAMBDA_METHOD)) == SYNTHETIC) { 2.12 return; 2.13 } 2.14 2.15 @@ -1795,7 +1795,7 @@ 2.16 protected void initParam(JCVariableDecl def) { 2.17 inits.incl(def.sym.adr); 2.18 uninits.excl(def.sym.adr); 2.19 - } 2.20 + } 2.21 2.22 public void visitVarDef(JCVariableDecl tree) { 2.23 boolean track = trackable(tree.sym);
3.1 --- a/src/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java Thu Oct 17 19:10:19 2013 -0700 3.2 +++ b/src/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java Fri Oct 18 15:03:34 2013 -0700 3.3 @@ -30,6 +30,7 @@ 3.4 import com.sun.tools.javac.tree.TreeMaker; 3.5 import com.sun.tools.javac.tree.TreeTranslator; 3.6 import com.sun.tools.javac.code.Attribute; 3.7 +import com.sun.tools.javac.code.Flags; 3.8 import com.sun.tools.javac.code.Kinds; 3.9 import com.sun.tools.javac.code.Scope; 3.10 import com.sun.tools.javac.code.Symbol; 3.11 @@ -1315,7 +1316,9 @@ 3.12 ListBuffer<JCVariableDecl> paramBuff = new ListBuffer<JCVariableDecl>(); 3.13 int i = 0; 3.14 for (List<Type> l = ptypes; l.nonEmpty(); l = l.tail) { 3.15 - paramBuff.append(make.Param(make.paramName(i++), l.head, owner)); 3.16 + JCVariableDecl param = make.Param(make.paramName(i++), l.head, owner); 3.17 + param.sym.pos = tree.pos; 3.18 + paramBuff.append(param); 3.19 } 3.20 List<JCVariableDecl> params = paramBuff.toList(); 3.21 3.22 @@ -1755,7 +1758,7 @@ 3.23 ((VarSymbol)ret).pos = ((VarSymbol)sym).pos; 3.24 break; 3.25 case CAPTURED_VAR: 3.26 - ret = new VarSymbol(SYNTHETIC | FINAL, name, types.erasure(sym.type), translatedSym) { 3.27 + ret = new VarSymbol(SYNTHETIC | FINAL | PARAMETER, name, types.erasure(sym.type), translatedSym) { 3.28 @Override 3.29 public Symbol baseSymbol() { 3.30 //keep mapping with original captured symbol 3.31 @@ -1763,8 +1766,17 @@ 3.32 } 3.33 }; 3.34 break; 3.35 + case LOCAL_VAR: 3.36 + ret = new VarSymbol(FINAL, name, types.erasure(sym.type), translatedSym); 3.37 + ((VarSymbol) ret).pos = ((VarSymbol) sym).pos; 3.38 + break; 3.39 + case PARAM: 3.40 + ret = new VarSymbol(FINAL | PARAMETER, name, types.erasure(sym.type), translatedSym); 3.41 + ((VarSymbol) ret).pos = ((VarSymbol) sym).pos; 3.42 + break; 3.43 default: 3.44 ret = makeSyntheticVar(FINAL, name, types.erasure(sym.type), translatedSym); 3.45 + ((VarSymbol) ret).pos = ((VarSymbol) sym).pos; 3.46 } 3.47 if (ret != sym) { 3.48 ret.setDeclarationAttributes(sym.getRawAttributes()); 3.49 @@ -1845,7 +1857,7 @@ 3.50 // If instance access isn't needed, make it static. 3.51 // Interface instance methods must be default methods. 3.52 // Lambda methods are private synthetic. 3.53 - translatedSym.flags_field = SYNTHETIC | 3.54 + translatedSym.flags_field = SYNTHETIC | LAMBDA_METHOD | 3.55 PRIVATE | 3.56 (thisReferenced? (inInterface? DEFAULT : 0) : STATIC); 3.57
4.1 --- a/src/share/classes/com/sun/tools/javac/jvm/Gen.java Thu Oct 17 19:10:19 2013 -0700 4.2 +++ b/src/share/classes/com/sun/tools/javac/jvm/Gen.java Fri Oct 18 15:03:34 2013 -0700 4.3 @@ -2892,7 +2892,8 @@ 4.4 4.5 @Override 4.6 public void visitMethodDef(JCMethodDecl tree) { 4.7 - if ((tree.sym.flags() & (SYNTHETIC | GENERATEDCONSTR)) != 0) { 4.8 + if ((tree.sym.flags() & (SYNTHETIC | GENERATEDCONSTR)) != 0 4.9 + && (tree.sym.flags() & LAMBDA_METHOD) == 0) { 4.10 return; 4.11 } 4.12 if (tree.name.equals(names.clinit)) { 4.13 @@ -2906,6 +2907,7 @@ 4.14 return; 4.15 } 4.16 currentMethod = tree.sym; 4.17 + 4.18 super.visitMethodDef(tree); 4.19 } 4.20
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/test/tools/javac/lambda/LocalVariableTable.java Fri Oct 18 15:03:34 2013 -0700 5.3 @@ -0,0 +1,220 @@ 5.4 +/* 5.5 + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 5.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5.7 + * 5.8 + * This code is free software; you can redistribute it and/or modify it 5.9 + * under the terms of the GNU General Public License version 2 only, as 5.10 + * published by the Free Software Foundation. 5.11 + * 5.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 5.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 5.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 5.15 + * version 2 for more details (a copy is included in the LICENSE file that 5.16 + * accompanied this code). 5.17 + * 5.18 + * You should have received a copy of the GNU General Public License version 5.19 + * 2 along with this work; if not, write to the Free Software Foundation, 5.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 5.21 + * 5.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 5.23 + * or visit www.oracle.com if you need additional information or have any 5.24 + * questions. 5.25 + */ 5.26 + 5.27 +/* 5.28 + * @test 5.29 + * @bug 8025998 8026749 5.30 + * @summary Missing LV table in lambda bodies 5.31 + * @compile -g LocalVariableTable.java 5.32 + * @run main LocalVariableTable 5.33 + */ 5.34 + 5.35 +import java.io.*; 5.36 +import java.lang.annotation.*; 5.37 +import java.util.*; 5.38 +import com.sun.tools.classfile.*; 5.39 + 5.40 +/* 5.41 + * The test checks that a LocalVariableTable attribute is generated for the 5.42 + * method bodies representing lambda expressions, and checks that the expected 5.43 + * set of entries is found in the attribute. 5.44 + * 5.45 + * Since the bug was about missing entries in the LVT, not malformed entries, 5.46 + * the test is not intended to be a detailed test of the contents of each 5.47 + * LocalVariableTable entry: it is assumed that if a entry is present, it 5.48 + * will have the correct contents. 5.49 + * 5.50 + * The test looks for test cases represented by nested classes whose 5.51 + * name begins with "Lambda". Each such class contains a lambda expression 5.52 + * that will mapped into a lambda method, and because the test is compiled 5.53 + * with -g, these methods should have a LocalVariableTable. The set of 5.54 + * expected names in the LVT is provided in an annotation on the class for 5.55 + * the test case. 5.56 + */ 5.57 +public class LocalVariableTable { 5.58 + public static void main(String... args) throws Exception { 5.59 + new LocalVariableTable().run(); 5.60 + } 5.61 + 5.62 + void run() throws Exception { 5.63 + // the declared classes are returned in an unspecified order, 5.64 + // so for neatness, sort them by name before processing them 5.65 + Class<?>[] classes = getClass().getDeclaredClasses(); 5.66 + Arrays.sort(classes, (c1, c2) -> c1.getName().compareTo(c2.getName())); 5.67 + 5.68 + for (Class<?> c : classes) { 5.69 + if (c.getSimpleName().startsWith("Lambda")) 5.70 + check(c); 5.71 + } 5.72 + if (errors > 0) 5.73 + throw new Exception(errors + " errors found"); 5.74 + } 5.75 + 5.76 + /** Check an individual test case. */ 5.77 + void check(Class<?> c) throws Exception { 5.78 + System.err.println("Checking " + c.getSimpleName()); 5.79 + 5.80 + Expect expect = c.getAnnotation(Expect.class); 5.81 + if (expect == null) { 5.82 + error("@Expect not found for class " + c.getSimpleName()); 5.83 + return; 5.84 + } 5.85 + 5.86 + ClassFile cf = ClassFile.read(getClass().getResource(c.getName() + ".class").openStream()); 5.87 + Method m = getLambdaMethod(cf); 5.88 + if (m == null) { 5.89 + error("lambda method not found"); 5.90 + return; 5.91 + } 5.92 + 5.93 + Code_attribute code = (Code_attribute) m.attributes.get(Attribute.Code); 5.94 + if (code == null) { 5.95 + error("Code attribute not found"); 5.96 + return; 5.97 + } 5.98 + 5.99 + LocalVariableTable_attribute lvt = 5.100 + (LocalVariableTable_attribute) code.attributes.get(Attribute.LocalVariableTable); 5.101 + if (lvt == null) { 5.102 + error("LocalVariableTable attribute not found"); 5.103 + return; 5.104 + } 5.105 + 5.106 + Set<String> foundNames = new LinkedHashSet<>(); 5.107 + for (LocalVariableTable_attribute.Entry e: lvt.local_variable_table) { 5.108 + foundNames.add(cf.constant_pool.getUTF8Value(e.name_index)); 5.109 + } 5.110 + 5.111 + Set<String> expectNames = new LinkedHashSet<>(Arrays.asList(expect.value())); 5.112 + if (!foundNames.equals(expectNames)) { 5.113 + Set<String> foundOnly = new LinkedHashSet<>(foundNames); 5.114 + foundOnly.removeAll(expectNames); 5.115 + for (String s: foundOnly) 5.116 + error("Unexpected name found: " + s); 5.117 + Set<String> expectOnly = new LinkedHashSet<>(expectNames); 5.118 + expectOnly.removeAll(foundNames); 5.119 + for (String s: expectOnly) 5.120 + error("Expected name not found: " + s); 5.121 + } 5.122 + } 5.123 + 5.124 + /** Get a method whose name begins "lambda$...". */ 5.125 + Method getLambdaMethod(ClassFile cf) throws ConstantPoolException { 5.126 + for (Method m: cf.methods) { 5.127 + if (m.getName(cf.constant_pool).startsWith("lambda$")) 5.128 + return m; 5.129 + } 5.130 + return null; 5.131 + } 5.132 + 5.133 + /** Report an error. */ 5.134 + void error(String msg) { 5.135 + System.err.println("Error: " + msg); 5.136 + errors++; 5.137 + } 5.138 + 5.139 + int errors; 5.140 + 5.141 + /** 5.142 + * Annotation used to provide the set of names expected in the LVT attribute. 5.143 + */ 5.144 + @Retention(RetentionPolicy.RUNTIME) 5.145 + @interface Expect { 5.146 + String[] value(); 5.147 + } 5.148 + 5.149 + /** Functional interface with nullary method. */ 5.150 + interface Run0 { 5.151 + public void run(); 5.152 + } 5.153 + 5.154 + /** Functional interface with 1-ary method. */ 5.155 + interface Run1 { 5.156 + public void run(int a0); 5.157 + } 5.158 + 5.159 + /** Functional interface with 2-ary method. */ 5.160 + interface Run2 { 5.161 + public void run(int a0, int a1); 5.162 + } 5.163 + 5.164 + /* 5.165 + * ---------- Test cases --------------------------------------------------- 5.166 + */ 5.167 + 5.168 + @Expect({ "x" }) 5.169 + static class Lambda_Args0_Local1 { 5.170 + Run0 r = () -> { int x = 0; }; 5.171 + } 5.172 + 5.173 + @Expect({ "x", "this" }) 5.174 + static class Lambda_Args0_Local1_this { 5.175 + int v; 5.176 + Run0 r = () -> { int x = v; }; 5.177 + } 5.178 + 5.179 + @Expect({ "a" }) 5.180 + static class Lambda_Args1_Local0 { 5.181 + Run1 r = (a) -> { }; 5.182 + } 5.183 + 5.184 + @Expect({ "a", "x" }) 5.185 + static class Lambda_Args1_Local1 { 5.186 + Run1 r = (a) -> { int x = a; }; 5.187 + } 5.188 + 5.189 + @Expect({ "a", "x" }) 5.190 + static class Lambda_Args1_Local1_Captured1 { 5.191 + void m() { 5.192 + int v = 0; 5.193 + Run1 r = (a) -> { int x = a + v; }; 5.194 + } 5.195 + } 5.196 + 5.197 + @Expect({ "a1", "a2", "x1", "x2", "this" }) 5.198 + static class Lambda_Args2_Local2_Captured2_this { 5.199 + int v; 5.200 + void m() { 5.201 + int v1 = 0; 5.202 + int v2 = 0; 5.203 + Run2 r = (a1, a2) -> { 5.204 + int x1 = a1 + v1 + v; 5.205 + int x2 = a2 + v2 + v; 5.206 + }; 5.207 + } 5.208 + } 5.209 + 5.210 + @Expect({ "e" }) 5.211 + static class Lambda_Try_Catch { 5.212 + private static Runnable asUncheckedRunnable(Closeable c) { 5.213 + return () -> { 5.214 + try { 5.215 + c.close(); 5.216 + } catch (IOException e) { 5.217 + throw new UncheckedIOException(e); 5.218 + } 5.219 + }; 5.220 + } 5.221 + } 5.222 +} 5.223 +