/* * Copyright (c) 2016, 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. */ package selectionresolution; import java.io.File; import java.io.FileWriter; import java.util.HashMap; /** * One individual test case. This class also defines a builder, which * can be used to build up cases. */ public class SelectionResolutionTestCase { public enum InvokeInstruction { INVOKESTATIC, INVOKESPECIAL, INVOKEINTERFACE, INVOKEVIRTUAL; } /** * The class data (includes interface data). */ public final HashMap classdata; /** * The hierarchy shape. */ public final HierarchyShape hier; /** * The invoke instruction to use. */ public final InvokeInstruction invoke; /** * Which class is the methodref (or interface methodref). */ public final int methodref; /** * Which class is the objectref. */ public final int objectref; /** * Which class is the callsite (this must be a class, not an interface. */ public final int callsite; /** * The expected result. */ public final Result result; private SelectionResolutionTestCase(final HashMap classdata, final HierarchyShape hier, final InvokeInstruction invoke, final int methodref, final int objectref, final int callsite, final int expected) { this.classdata = classdata; this.hier = hier; this.invoke = invoke; this.methodref = methodref; this.objectref = objectref; this.callsite = callsite; this.result = Result.is(expected); } private SelectionResolutionTestCase(final HashMap classdata, final HierarchyShape hier, final InvokeInstruction invoke, final int methodref, final int objectref, final int callsite, final Result result) { this.classdata = classdata; this.hier = hier; this.invoke = invoke; this.methodref = methodref; this.objectref = objectref; this.callsite = callsite; this.result = result; } private static int currError = 0; private String dumpClasses(final ClassConstruct[] classes) throws Exception { final String errorDirName = "error_" + currError++; final File errorDir = new File(errorDirName); errorDir.mkdirs(); for (int i = 0; i < classes.length; i++) { classes[i].writeClass(errorDir); } try (final FileWriter fos = new FileWriter(new File(errorDir, "description.txt"))) { fos.write(this.toString()); } return errorDirName; } /** * Run this case, return an error message, or null. * * @return An error message, or null if the case succeeded. */ public String run() { /* Uncomment this line to print EVERY case */ System.err.println("Running\n" + this); final ClassBuilder builder = new ClassBuilder(this, ClassBuilder.ExecutionMode.DIRECT); try { final ByteCodeClassLoader bcl = new ByteCodeClassLoader(); final ClassConstruct[] classes = builder.build(); try { bcl.addClasses(classes); bcl.loadAll(); // Grab the callsite class. final Class testclass = bcl.findClass(builder.getCallsiteClass().getDottedName()); // Get the 'test' method out of it and call it. The // return value tess which class that got selected. final java.lang.reflect.Method method = testclass.getDeclaredMethod("test"); final int actual = (Integer) method.invoke(null); // Check the result. if (!result.complyWith(actual)) { final String dump = dumpClasses(classes); return "Failed:\n" + this + "\nExpected " + result + " got " + actual + "\nClasses written to " + dump; } } catch (Throwable t) { // This catch block is handling exceptions that we // might expect to see. final Throwable actual = t.getCause(); if (actual == null) { final String dump = dumpClasses(classes); System.err.println("Unexpected exception in test\n" + this + "\nClasses written to " + dump); throw t; } else if (result == null) { final String dump = dumpClasses(classes); return "Failed:\n" + this + "\nUnexpected exception " + actual + "\nClasses written to " + dump; } else if (!result.complyWith(actual)) { final String dump = dumpClasses(classes); return "Failed:\n" + this + "\nExpected " + this.result + " got " + actual + "\nClasses written to " + dump; } } } catch(Throwable e) { throw new RuntimeException(e); } return null; } private static void addPackage(final StringBuilder sb, final ClassData cd) { switch (cd.packageId) { case SAME: sb.append("Same."); break; case DIFFERENT: sb.append("Different."); break; case OTHER: sb.append("Other."); break; case PLACEHOLDER: sb.append("_."); break; default: throw new RuntimeException("Impossible case"); } } public String toString() { final StringBuilder sb = new StringBuilder(); //sb.append("hierarchy:\n" + hier + "\n"); sb.append("invoke: " + invoke + "\n"); if (methodref != -1) { if (hier.isClass(methodref)) { sb.append("methodref: C" + methodref + "\n"); } else { sb.append("methodref: I" + methodref + "\n"); } } if (objectref != -1) { if (hier.isClass(objectref)) { sb.append("objectref: C" + objectref + "\n"); } else { sb.append("objectref: I" + objectref + "\n"); } } if (callsite != -1) { if (hier.isClass(callsite)) { sb.append("callsite: C" + callsite + "\n"); } else { sb.append("callsite: I" + callsite + "\n"); } } sb.append("result: " + result + "\n"); sb.append("classes:\n\n"); for(int i = 0; classdata.containsKey(i); i++) { final ClassData cd = classdata.get(i); if (hier.isClass(i)) { sb.append("class "); addPackage(sb, cd); sb.append("C" + i); } else { sb.append("interface "); addPackage(sb, cd); sb.append("I" + i); } boolean first = true; for(final int j : hier.classes()) { if (hier.inherits(i, j)) { if (first) { sb.append(" extends C" + j); } else { sb.append(", C" + j); } } } first = true; for(final int j : hier.interfaces()) { if (hier.inherits(i, j)) { if (first) { sb.append(" implements I" + j); } else { sb.append(", I" + j); } } } sb.append(cd); } return sb.toString(); } /** * A builder, facilitating building up test cases. */ public static class Builder { /** * A map from class (or interface) id's to ClassDatas */ public final HashMap classdata; /** * The hierarchy shape. */ public final HierarchyShape hier; /** * Which invoke instruction to use. */ public InvokeInstruction invoke; /** * The id of the methodref (or interface methodref). */ public int methodref = -1; /** * The id of the object ref. Note that for the generator * framework to work, this must be set to something. If an * objectref isn't used, just set it to the methodref. */ public int objectref = -1; /** * The id of the callsite. */ public int callsite = -1; /** * The id of the expected result. This is used to store the * expected resolution result. */ public int expected; /** * The expected result. This needs to be set before the final * test case is built. */ public Result result; /** * Create an empty Builder object. */ public Builder() { classdata = new HashMap<>(); hier = new HierarchyShape(); } private Builder(final HashMap classdata, final HierarchyShape hier, final InvokeInstruction invoke, final int methodref, final int objectref, final int callsite, final int expected, final Result result) { this.classdata = classdata; this.hier = hier; this.invoke = invoke; this.methodref = methodref; this.objectref = objectref; this.callsite = callsite; this.expected = expected; this.result = result; } private Builder(final Builder other) { this((HashMap) other.classdata.clone(), other.hier.copy(), other.invoke, other.methodref, other.objectref, other.callsite, other.expected, other.result); } public SelectionResolutionTestCase build() { if (result != null) { return new SelectionResolutionTestCase(classdata, hier, invoke, methodref, objectref, callsite, result); } else { return new SelectionResolutionTestCase(classdata, hier, invoke, methodref, objectref, callsite, expected); } } /** * Set the expected result. */ public void setResult(final Result result) { this.result = result; } /** * Add a class, and return its id. * * @return The new class' id. */ public int addClass(final ClassData data) { final int id = hier.addClass(); classdata.put(id, data); return id; } /** * Add an interface, and return its id. * * @return The new class' id. */ public int addInterface(final ClassData data) { final int id = hier.addInterface(); classdata.put(id, data); return id; } /** * Make a copy of this builder. */ public Builder copy() { return new Builder(this); } public String toString() { final StringBuilder sb = new StringBuilder(); //sb.append("hierarchy:\n" + hier + "\n"); sb.append("invoke: " + invoke + "\n"); if (methodref != -1) { if (hier.isClass(methodref)) { sb.append("methodref: C" + methodref + "\n"); } else { sb.append("methodref: I" + methodref + "\n"); } } if (objectref != -1) { if (hier.isClass(objectref)) { sb.append("objectref: C" + objectref + "\n"); } else { sb.append("objectref: I" + objectref + "\n"); } } if (callsite != -1) { if (hier.isClass(callsite)) { sb.append("callsite: C" + callsite + "\n"); } else { sb.append("callsite: I" + callsite + "\n"); } } if (expected != -1) { if (hier.isClass(expected)) { sb.append("expected: C" + expected + "\n"); } else { sb.append("expected: I" + expected + "\n"); } } sb.append("result: " + result + "\n"); sb.append("classes:\n\n"); for(int i = 0; classdata.containsKey(i); i++) { final ClassData cd = classdata.get(i); if (hier.isClass(i)) { sb.append("class "); addPackage(sb, cd); sb.append("C" + i); } else { sb.append("interface "); addPackage(sb, cd); sb.append("I" + i); } boolean first = true; for(final int j : hier.classes()) { if (hier.inherits(i, j)) { if (first) { sb.append(" extends C" + j); } else { sb.append(", C" + j); } } } first = true; for(final int j : hier.interfaces()) { if (hier.inherits(i, j)) { if (first) { sb.append(" implements I" + j); } else { sb.append(", I" + j); } } } sb.append(cd); } return sb.toString(); } } }