1 /* 2 * Copyright (c) 2024, 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 import java.io.PrintWriter; 25 import java.io.StringWriter; 26 import java.lang.classfile.ClassFile; 27 import java.lang.classfile.Instruction; 28 import java.lang.classfile.Label; 29 import java.lang.classfile.MethodModel; 30 import java.lang.classfile.Opcode; 31 import java.lang.classfile.instruction.*; 32 import java.lang.invoke.MethodHandles; 33 import java.lang.reflect.AccessFlag; 34 import java.lang.reflect.code.Op; 35 import java.lang.reflect.code.OpTransformer; 36 import java.lang.reflect.code.analysis.SSA; 37 import java.lang.reflect.code.bytecode.BytecodeGenerator; 38 import java.lang.reflect.code.bytecode.BytecodeLift; 39 import java.lang.reflect.code.op.CoreOp; 40 import java.net.URI; 41 import java.nio.file.FileSystem; 42 import java.nio.file.FileSystems; 43 import java.nio.file.Files; 44 import java.nio.file.Path; 45 import java.util.*; 46 import java.util.stream.Collectors; 47 import java.util.stream.Stream; 48 import org.testng.Assert; 49 import org.testng.annotations.Ignore; 50 import org.testng.annotations.Test; 51 52 /* 53 * @test 54 * @enablePreview 55 * @run testng TestSmallCorpus 56 */ 57 public class TestSmallCorpus { 58 59 private static final FileSystem JRT = FileSystems.getFileSystem(URI.create("jrt:/")); 60 private static final ClassFile CF = ClassFile.of(ClassFile.DebugElementsOption.DROP_DEBUG, 61 ClassFile.LineNumbersOption.DROP_LINE_NUMBERS); 62 private static final int COLUMN_WIDTH = 150; 63 64 private int passed, notMatching; 65 private Map<String, Map<String, Integer>> errorStats; 66 67 @Ignore 68 @Test 69 public void testDoubleRoundtripStability() throws Exception { 70 passed = 0; 71 notMatching = 0; 72 errorStats = new LinkedHashMap<>(); 73 for (Path p : Files.walk(JRT.getPath("modules/java.base/java")) 74 .filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".class")) 75 .toList()) { 76 testDoubleRoundtripStability(p); 77 } 78 79 for (var stats : errorStats.entrySet()) { 80 System.out.println(String.format(""" 81 82 %s errors: 83 ----------------------------------------------------- 84 """, stats.getKey())); 85 stats.getValue().entrySet().stream().sorted((e1, e2) -> Integer.compare(e2.getValue(), e1.getValue())).forEach(e -> System.out.println(e.getValue() +"x " + e.getKey() + "\n")); 86 } 87 88 // @@@ There is still several failing cases and a lot of errors 89 Assert.assertTrue(notMatching < 31 && passed > 5400, String.format(""" 90 91 passed: %d 92 not matching: %d 93 %s 94 """, 95 passed, 96 notMatching, 97 errorStats.entrySet().stream().map(e -> e.getKey() + 98 " errors: " 99 + e.getValue().values().stream().mapToInt(Integer::intValue).sum()).collect(Collectors.joining("\n ")) 100 )); 101 } 102 103 private void testDoubleRoundtripStability(Path path) throws Exception { 104 var clm = CF.parse(path); 105 for (var originalModel : clm.methods()) { 106 if (originalModel.flags().has(AccessFlag.STATIC) && originalModel.code().isPresent()) try { 107 CoreOp.FuncOp firstLift = lift(originalModel); 108 try { 109 CoreOp.FuncOp firstTransform = transform(firstLift); 110 try { 111 MethodModel firstModel = lower(firstTransform); 112 try { 113 CoreOp.FuncOp secondLift = lift(firstModel); 114 try { 115 CoreOp.FuncOp secondTransform = transform(secondLift); 116 try { 117 MethodModel secondModel = lower(secondTransform); 118 119 // testing only methods passing through 120 var firstNormalized = normalize(firstModel); 121 var secondNormalized = normalize(secondModel); 122 if (!firstNormalized.equals(secondNormalized)) { 123 notMatching++; 124 System.out.println(clm.thisClass().asInternalName() + "::" + originalModel.methodName().stringValue() + originalModel.methodTypeSymbol().displayDescriptor()); 125 printInColumns(firstLift, secondLift); 126 printInColumns(firstTransform, secondTransform); 127 printInColumns(firstNormalized, secondNormalized); 128 System.out.println(); 129 } else { 130 passed++; 131 } 132 } catch (Exception e) { 133 error("second lower", e); 134 } 135 } catch (Exception e) { 136 error("second transform", e); 137 } 138 } catch (Exception e) { 139 error("second lift", e); 140 } 141 } catch (Exception e) { 142 error("first lower", e); 143 } 144 } catch (Exception e) { 145 error("first transform", e); 146 } 147 } catch (Exception e) { 148 error("first lift", e); 149 } 150 } 151 } 152 private static void printInColumns(CoreOp.FuncOp first, CoreOp.FuncOp second) { 153 StringWriter fw = new StringWriter(); 154 first.writeTo(fw); 155 StringWriter sw = new StringWriter(); 156 second.writeTo(sw); 157 printInColumns(fw.toString().lines().toList(), sw.toString().lines().toList()); 158 } 159 160 private static void printInColumns(List<String> first, List<String> second) { 161 System.out.println("-".repeat(COLUMN_WIDTH ) + "--+-" + "-".repeat(COLUMN_WIDTH )); 162 for (int i = 0; i < first.size() || i < second.size(); i++) { 163 String f = i < first.size() ? first.get(i) : ""; 164 String s = i < second.size() ? second.get(i) : ""; 165 System.out.println(" " + f + (f.length() < COLUMN_WIDTH ? " ".repeat(COLUMN_WIDTH - f.length()) : "") + (f.equals(s) ? " | " : " x ") + s); 166 } 167 } 168 169 private static CoreOp.FuncOp lift(MethodModel mm) { 170 return BytecodeLift.lift(mm); 171 } 172 173 private static CoreOp.FuncOp transform(CoreOp.FuncOp func) { 174 return SSA.transform(func.transform(OpTransformer.LOWERING_TRANSFORMER)); 175 } 176 177 private static MethodModel lower(CoreOp.FuncOp func) { 178 return CF.parse(BytecodeGenerator.generateClassData( 179 MethodHandles.lookup(), 180 func)).methods().get(0); 181 } 182 183 184 public static List<String> normalize(MethodModel mm) { 185 record El(int index, String format, Label... targets) { 186 public El(int index, Instruction i, Object format, Label... targets) { 187 this(index, trim(i.opcode()) + " " + format, targets); 188 } 189 public String toString(Map<Label, Integer> targetsMap) { 190 return "%3d: ".formatted(index) + (targets.length == 0 ? format : format.formatted(Stream.of(targets).map(l -> targetsMap.get(l)).toArray())); 191 } 192 } 193 194 Map<Label, Integer> targetsMap = new HashMap<>(); 195 List<El> elements = new ArrayList<>(); 196 Label lastLabel = null; 197 int i = 0; 198 for (var e : mm.code().orElseThrow()) { 199 var er = switch (e) { 200 case LabelTarget lt -> { 201 lastLabel = lt.label(); 202 yield null; 203 } 204 case ExceptionCatch ec -> 205 new El(i++, "ExceptionCatch start: @%d end: @%d handler: @%d" + ec.catchType().map(ct -> " catch type: " + ct.asInternalName()).orElse(""), ec.tryStart(), ec.tryEnd(), ec.handler()); 206 case BranchInstruction ins -> 207 new El(i++, ins, "@%d", ins.target()); 208 case ConstantInstruction ins -> 209 new El(i++, "LDC " + ins.constantValue()); 210 case FieldInstruction ins -> 211 new El(i++, ins, ins.owner().asInternalName() + "." + ins.name().stringValue()); 212 case InvokeDynamicInstruction ins -> 213 new El(i++, ins, ins.name().stringValue() + ins.typeSymbol() + " " + ins.bootstrapMethod() + "(" + ins.bootstrapArgs() + ")"); 214 case InvokeInstruction ins -> 215 new El(i++, ins, ins.owner().asInternalName() + "::" + ins.name().stringValue() + ins.typeSymbol().displayDescriptor()); 216 case LoadInstruction ins -> 217 new El(i++, ins, "#" + ins.slot()); 218 case StoreInstruction ins -> 219 new El(i++, ins, "#" + ins.slot()); 220 case IncrementInstruction ins -> 221 new El(i++, ins, "#" + ins.slot() + " " + ins.constant()); 222 case LookupSwitchInstruction ins -> 223 new El(i++, ins, "default: @%d" + ins.cases().stream().map(c -> ", " + c.caseValue() + ": @%d").collect(Collectors.joining()), 224 Stream.concat(Stream.of(ins.defaultTarget()), ins.cases().stream().map(SwitchCase::target)).toArray(Label[]::new)); 225 case NewMultiArrayInstruction ins -> 226 new El(i++, ins, ins.arrayType().asInternalName() + "(" + ins.dimensions() + ")"); 227 case NewObjectInstruction ins -> 228 new El(i++, ins, ins.className().asInternalName()); 229 case NewPrimitiveArrayInstruction ins -> 230 new El(i++, ins, ins.typeKind()); 231 case NewReferenceArrayInstruction ins -> 232 new El(i++, ins, ins.componentType().asInternalName()); 233 case TableSwitchInstruction ins -> 234 new El(i++, ins, "default: @%d" + ins.cases().stream().map(c -> ", " + c.caseValue() + ": @%d").collect(Collectors.joining()), 235 Stream.concat(Stream.of(ins.defaultTarget()), ins.cases().stream().map(SwitchCase::target)).toArray(Label[]::new)); 236 case TypeCheckInstruction ins -> 237 new El(i++, ins, ins.type().asInternalName()); 238 case Instruction ins -> 239 new El(i++, ins, ""); 240 default -> null; 241 }; 242 if (er != null) { 243 if (lastLabel != null) { 244 targetsMap.put(lastLabel, elements.size()); 245 lastLabel = null; 246 } 247 elements.add(er); 248 } 249 } 250 return elements.stream().map(el -> el.toString(targetsMap)).toList(); 251 } 252 253 private static String trim(Opcode opcode) { 254 var name = opcode.toString(); 255 int i = name.indexOf('_'); 256 return i > 2 ? name.substring(0, i) : name; 257 } 258 259 private void error(String category, Exception e) { 260 StringWriter sw = new StringWriter(); 261 e.printStackTrace(new PrintWriter(sw)); 262 errorStats.computeIfAbsent(category, _ -> new HashMap<>()) 263 .compute(sw.toString(), (_, i) -> i == null ? 1 : i + 1); 264 } 265 }