blob: c1250d572ba6e6fe23d445378b1b721cf08f12cb [file] [log] [blame]
/*
* 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<Integer, ClassData> 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<Integer, ClassData> 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<Integer, ClassData> 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<Integer, ClassData> 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<Integer, ClassData> 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<Integer, ClassData>) 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();
}
}
}