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 * @compile -XDignore.symbol.file LUtils.java LambdaStackTrace.java 30 * @run main LambdaStackTrace 31 */ 32 33 import jdk.internal.org.objectweb.asm.ClassWriter; 34 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.io.IOException; 38 import java.lang.reflect.InvocationTargetException; 39 import java.lang.reflect.Method; 40 import java.util.ArrayList; 41 42 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT; 43 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_INTERFACE; 44 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; 45 import static jdk.internal.org.objectweb.asm.Opcodes.V1_7; 46 47 public class LambdaStackTrace { 48 49 static File classes = new File(System.getProperty("test.classes")); 50 51 public static void main(String[] args) throws Exception { 52 testBasic(); 53 testBridgeMethods(); 54 } 55 56 /** 57 * Test the simple case 58 */ 59 private static void testBasic() throws Exception { 60 try { 61 Runnable r = () -> { 62 throw new RuntimeException(); 63 }; 64 r.run(); 65 } catch (Exception ex) { 66 // Before 8025636 the stacktrace would look like: 67 // at LambdaStackTrace.lambda$main$0(LambdaStackTrace.java:37) 68 // at LambdaStackTrace$$Lambda$1/1937396743.run(<Unknown>:1000000) 69 // at LambdaStackTrace.testBasic(LambdaStackTrace.java:40) 70 // at ... 71 // 72 // We are verifying that the middle frame above is gone. 73 74 verifyFrames(ex.getStackTrace(), 75 "LambdaStackTrace\\..*", 76 "LambdaStackTrace.testBasic"); 77 } 78 } 79 80 /** 81 * Test the more complicated case with bridge methods. 82 * 83 * We set up the following interfaces: 84 * 85 * interface Maker { 86 * Object make(); 87 * } 88 * interface StringMaker extends Maker { 89 * String make(); 90 * } 91 * 92 * And we will use them like so: 93 * 94 * StringMaker sm = () -> { throw new RuntimeException(); }; 95 * sm.make(); 96 * ((Maker)m).make(); 97 * 98 * The first call is a "normal" interface call, the second will use a 99 * bridge method. In both cases the generated lambda frame should 100 * be removed from the stack trace. 101 */ 102 private static void testBridgeMethods() throws Exception { 103 // setup 104 generateInterfaces(); 105 compileCaller(); 106 107 // test 108 StackTraceElement[] frames = call("Caller", "callStringMaker"); 109 verifyFrames(frames, 110 "Caller\\..*", 111 "Caller.callStringMaker"); 112 113 frames = call("Caller", "callMaker"); 114 verifyFrames(frames, 115 "Caller\\..*", 116 "Caller.callMaker"); 117 } 118 119 private static void generateInterfaces() throws IOException { 120 // We can't let javac compile these interfaces because in > 1.8 it will insert 121 // bridge methods into the interfaces - we want code that looks like <= 1.7, 122 // so we generate it. 123 try (FileOutputStream fw = new FileOutputStream(new File(classes, "Maker.class"))) { 124 fw.write(generateMaker()); 125 } 126 try (FileOutputStream fw = new FileOutputStream(new File(classes, "StringMaker.class"))) { 127 fw.write(generateStringMaker()); 128 } 129 } 130 131 private static byte[] generateMaker() { 132 // interface Maker { 133 // Object make(); 134 // } 135 ClassWriter cw = new ClassWriter(0); 136 cw.visit(V1_7, ACC_INTERFACE | ACC_ABSTRACT, "Maker", null, "java/lang/Object", null); 137 cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, "make", 138 "()Ljava/lang/Object;", null, null); 139 cw.visitEnd(); 140 return cw.toByteArray(); 141 } 142 143 private static byte[] generateStringMaker() { 144 // interface StringMaker extends Maker { 145 // String make(); 146 // } 147 ClassWriter cw = new ClassWriter(0); 148 cw.visit(V1_7, ACC_INTERFACE | ACC_ABSTRACT, "StringMaker", null, "java/lang/Object", new String[]{"Maker"}); 149 cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, "make", 150 "()Ljava/lang/String;", null, null); 151 cw.visitEnd(); 152 return cw.toByteArray(); 153 } 154 155 156 static void emitCode(File f) { 157 ArrayList<String> scratch = new ArrayList<>(); 158 scratch.add("public class Caller {"); 159 scratch.add(" public static void callStringMaker() {"); 160 scratch.add(" StringMaker sm = () -> { throw new RuntimeException(); };"); 161 scratch.add(" sm.make();"); 162 scratch.add(" }"); 163 scratch.add(" public static void callMaker() {"); 164 scratch.add(" StringMaker sm = () -> { throw new RuntimeException(); };"); 165 scratch.add(" ((Maker) sm).make();"); // <-- This will call the bridge method 166 scratch.add(" }"); 167 scratch.add("}"); 168 LUtils.createFile(f, scratch); 169 } 170 171 static void compileCaller() { 172 File caller = new File(classes, "Caller.java"); 173 emitCode(caller); 174 LUtils.compile("-cp", classes.getAbsolutePath(), "-d", classes.getAbsolutePath(), caller.getAbsolutePath()); 175 } 176 177 private static void verifyFrames(StackTraceElement[] stack, String... patterns) throws Exception { 178 for (int i = 0; i < patterns.length; i++) { 179 String cm = stack[i].getClassName() + "." + stack[i].getMethodName(); 180 if (!cm.matches(patterns[i])) { 181 System.err.println("Actual trace did not match expected trace at frame " + i); 182 System.err.println("Expected frame patterns:"); 183 for (int j = 0; j < patterns.length; j++) { 184 System.err.println(" " + j + ": " + patterns[j]); 185 } 186 System.err.println("Actual frames:"); 187 for (int j = 0; j < patterns.length; j++) { 188 System.err.println(" " + j + ": " + stack[j]); 189 } 190 throw new Exception("Incorrect stack frames found"); 191 } 192 } 193 } 194 195 private static StackTraceElement[] call(String clazz, String method) throws Exception { 196 Class<?> c = Class.forName(clazz); 197 try { 198 Method m = c.getDeclaredMethod(method); 199 m.invoke(null); 200 } catch(InvocationTargetException ex) { 201 return ex.getTargetException().getStackTrace(); 202 } 203 throw new Exception("Expected exception to be thrown"); 204 } 205 }