LambdaStackTrace.java revision 12973:2e63fa2efdb1
1/* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24/* 25 * @test 26 * @bug 8025636 27 * @summary Synthetic frames should be hidden in exceptions 28 * @modules java.base/jdk.internal.org.objectweb.asm 29 * jdk.compiler 30 * @compile -XDignore.symbol.file LUtils.java LambdaStackTrace.java 31 * @run main LambdaStackTrace 32 */ 33 34import jdk.internal.org.objectweb.asm.ClassWriter; 35 36import java.io.File; 37import java.io.FileOutputStream; 38import java.io.IOException; 39import java.lang.reflect.InvocationTargetException; 40import java.lang.reflect.Method; 41import java.util.ArrayList; 42 43import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT; 44import static jdk.internal.org.objectweb.asm.Opcodes.ACC_INTERFACE; 45import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; 46import static jdk.internal.org.objectweb.asm.Opcodes.V1_7; 47 48public class LambdaStackTrace { 49 50 static File classes = new File(System.getProperty("test.classes")); 51 52 public static void main(String[] args) throws Exception { 53 testBasic(); 54 testBridgeMethods(); 55 } 56 57 /** 58 * Test the simple case 59 */ 60 private static void testBasic() throws Exception { 61 try { 62 Runnable r = () -> { 63 throw new RuntimeException(); 64 }; 65 r.run(); 66 } catch (Exception ex) { 67 // Before 8025636 the stacktrace would look like: 68 // at LambdaStackTrace.lambda$main$0(LambdaStackTrace.java:37) 69 // at LambdaStackTrace$$Lambda$1/1937396743.run(<Unknown>:1000000) 70 // at LambdaStackTrace.testBasic(LambdaStackTrace.java:40) 71 // at ... 72 // 73 // We are verifying that the middle frame above is gone. 74 75 verifyFrames(ex.getStackTrace(), 76 "LambdaStackTrace\\..*", 77 "LambdaStackTrace.testBasic"); 78 } 79 } 80 81 /** 82 * Test the more complicated case with bridge methods. 83 * 84 * We set up the following interfaces: 85 * 86 * interface Maker { 87 * Object make(); 88 * } 89 * interface StringMaker extends Maker { 90 * String make(); 91 * } 92 * 93 * And we will use them like so: 94 * 95 * StringMaker sm = () -> { throw new RuntimeException(); }; 96 * sm.make(); 97 * ((Maker)m).make(); 98 * 99 * The first call is a "normal" interface call, the second will use a 100 * bridge method. In both cases the generated lambda frame should 101 * be removed from the stack trace. 102 */ 103 private static void testBridgeMethods() throws Exception { 104 // setup 105 generateInterfaces(); 106 compileCaller(); 107 108 // test 109 StackTraceElement[] frames = call("Caller", "callStringMaker"); 110 verifyFrames(frames, 111 "Caller\\..*", 112 "Caller.callStringMaker"); 113 114 frames = call("Caller", "callMaker"); 115 verifyFrames(frames, 116 "Caller\\..*", 117 "Caller.callMaker"); 118 } 119 120 private static void generateInterfaces() throws IOException { 121 // We can't let javac compile these interfaces because in > 1.8 it will insert 122 // bridge methods into the interfaces - we want code that looks like <= 1.7, 123 // so we generate it. 124 try (FileOutputStream fw = new FileOutputStream(new File(classes, "Maker.class"))) { 125 fw.write(generateMaker()); 126 } 127 try (FileOutputStream fw = new FileOutputStream(new File(classes, "StringMaker.class"))) { 128 fw.write(generateStringMaker()); 129 } 130 } 131 132 private static byte[] generateMaker() { 133 // interface Maker { 134 // Object make(); 135 // } 136 ClassWriter cw = new ClassWriter(0); 137 cw.visit(V1_7, ACC_INTERFACE | ACC_ABSTRACT, "Maker", null, "java/lang/Object", null); 138 cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, "make", 139 "()Ljava/lang/Object;", null, null); 140 cw.visitEnd(); 141 return cw.toByteArray(); 142 } 143 144 private static byte[] generateStringMaker() { 145 // interface StringMaker extends Maker { 146 // String make(); 147 // } 148 ClassWriter cw = new ClassWriter(0); 149 cw.visit(V1_7, ACC_INTERFACE | ACC_ABSTRACT, "StringMaker", null, "java/lang/Object", new String[]{"Maker"}); 150 cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, "make", 151 "()Ljava/lang/String;", null, null); 152 cw.visitEnd(); 153 return cw.toByteArray(); 154 } 155 156 157 static void emitCode(File f) { 158 ArrayList<String> scratch = new ArrayList<>(); 159 scratch.add("public class Caller {"); 160 scratch.add(" public static void callStringMaker() {"); 161 scratch.add(" StringMaker sm = () -> { throw new RuntimeException(); };"); 162 scratch.add(" sm.make();"); 163 scratch.add(" }"); 164 scratch.add(" public static void callMaker() {"); 165 scratch.add(" StringMaker sm = () -> { throw new RuntimeException(); };"); 166 scratch.add(" ((Maker) sm).make();"); // <-- This will call the bridge method 167 scratch.add(" }"); 168 scratch.add("}"); 169 LUtils.createFile(f, scratch); 170 } 171 172 static void compileCaller() { 173 File caller = new File(classes, "Caller.java"); 174 emitCode(caller); 175 LUtils.compile("-cp", classes.getAbsolutePath(), "-d", classes.getAbsolutePath(), caller.getAbsolutePath()); 176 } 177 178 private static void verifyFrames(StackTraceElement[] stack, String... patterns) throws Exception { 179 for (int i = 0; i < patterns.length; i++) { 180 String cm = stack[i].getClassName() + "." + stack[i].getMethodName(); 181 if (!cm.matches(patterns[i])) { 182 System.err.println("Actual trace did not match expected trace at frame " + i); 183 System.err.println("Expected frame patterns:"); 184 for (int j = 0; j < patterns.length; j++) { 185 System.err.println(" " + j + ": " + patterns[j]); 186 } 187 System.err.println("Actual frames:"); 188 for (int j = 0; j < patterns.length; j++) { 189 System.err.println(" " + j + ": " + stack[j]); 190 } 191 throw new Exception("Incorrect stack frames found"); 192 } 193 } 194 } 195 196 private static StackTraceElement[] call(String clazz, String method) throws Exception { 197 Class<?> c = Class.forName(clazz); 198 try { 199 Method m = c.getDeclaredMethod(method); 200 m.invoke(null); 201 } catch(InvocationTargetException ex) { 202 return ex.getTargetException().getStackTrace(); 203 } 204 throw new Exception("Expected exception to be thrown"); 205 } 206} 207