3789983e89
Reviewed-by: darcy, ihse
453 lines
15 KiB
Java
453 lines
15 KiB
Java
/*
|
|
* 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();
|
|
}
|
|
}
|
|
}
|