Merge "Remove useless conversion between string and type"
diff --git a/src/main/java/com/android/tools/r8/errors/Unreachable.java b/src/main/java/com/android/tools/r8/errors/Unreachable.java
index 4b17eba..0eb71d2 100644
--- a/src/main/java/com/android/tools/r8/errors/Unreachable.java
+++ b/src/main/java/com/android/tools/r8/errors/Unreachable.java
@@ -14,4 +14,8 @@
public Unreachable(String s) {
super(s);
}
+
+ public Unreachable(Throwable cause) {
+ super(cause);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index d6e1a2d..ff0e4df 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.desugar;
import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexCallSite;
@@ -21,9 +22,11 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeSuper;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
@@ -62,6 +65,7 @@
// Public for testing.
public static final String COMPANION_CLASS_NAME_SUFFIX = "-CC";
private static final String DEFAULT_METHOD_PREFIX = "$default$";
+ private static final String PRIVATE_METHOD_PREFIX = "$private$";
private final IRConverter converter;
private final InternalOptions options;
@@ -156,7 +160,7 @@
// exception but we can not report it as error since it can also be the intended
// behavior.
warnMissingType(encodedMethod.method, method.holder);
- } else if (clazz != null && (clazz.isInterface() && !clazz.isLibraryClass())) {
+ } else if (clazz.isInterface() && !clazz.isLibraryClass()) {
// NOTE: we intentionally don't desugar super calls into interface methods
// coming from android.jar since it is only possible in case v24+ version
// of android.jar is provided.
@@ -166,6 +170,35 @@
new InvokeStatic(defaultAsMethodOfCompanionClass(method),
invokeSuper.outValue(), invokeSuper.arguments()));
}
+ continue;
+ }
+
+ if (instruction.isInvokeDirect()) {
+ InvokeDirect invokeDirect = instruction.asInvokeDirect();
+ DexMethod method = invokeDirect.getInvokedMethod();
+ if (factory.isConstructor(method)) {
+ continue;
+ }
+
+ // This is a private instance method call. Note that the referenced method
+ // is expected to be in the current class since it is private, but desugaring
+ // may move some methods or their code into other classes.
+ DexClass clazz = findDefinitionFor(method.holder);
+ if (clazz == null) {
+ // Report missing class since we don't know if it is an interface.
+ warnMissingType(encodedMethod.method, method.holder);
+
+ } else if (clazz.isInterface()) {
+ if (clazz.isLibraryClass()) {
+ throw new CompilationError("Unexpected call to a private method " +
+ "defined in library class " + clazz.toSourceString(),
+ getMethodOrigin(encodedMethod.method));
+
+ }
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(privateAsMethodOfCompanionClass(method),
+ invokeDirect.outValue(), invokeDirect.arguments()));
+ }
}
}
}
@@ -204,6 +237,20 @@
return factory.createType(ccTypeDescriptor);
}
+ // Checks if `type` is a companion class.
+ private boolean isCompanionClassType(DexType type) {
+ return type.descriptor.toString().endsWith(COMPANION_CLASS_NAME_SUFFIX + ";");
+ }
+
+ // Gets the interface class for a companion class `type`.
+ private DexType getInterfaceClassType(DexType type) {
+ assert isCompanionClassType(type);
+ String descriptor = type.descriptor.toString();
+ String interfaceTypeDescriptor = descriptor.substring(0,
+ descriptor.length() - 1 - COMPANION_CLASS_NAME_SUFFIX.length()) + ";";
+ return factory.createType(interfaceTypeDescriptor);
+ }
+
private boolean isInMainDexList(DexType iface) {
return converter.appInfo.isInMainDexList(iface);
}
@@ -214,8 +261,7 @@
return factory.createMethod(getCompanionClassType(method.holder), method.proto, method.name);
}
- // Represent a default interface method as a method of companion class.
- final DexMethod defaultAsMethodOfCompanionClass(DexMethod method) {
+ private DexMethod instanceAsMethodOfCompanionClass(DexMethod method, String prefix) {
// Add an implicit argument to represent the receiver.
DexType[] params = method.proto.parameters.values;
DexType[] newParams = new DexType[params.length + 1];
@@ -225,7 +271,18 @@
// Add prefix to avoid name conflicts.
return factory.createMethod(getCompanionClassType(method.holder),
factory.createProto(method.proto.returnType, newParams),
- factory.createString(DEFAULT_METHOD_PREFIX + method.name.toString()));
+ factory.createString(prefix + method.name.toString()));
+ }
+
+ // Represent a default interface method as a method of companion class.
+ final DexMethod defaultAsMethodOfCompanionClass(DexMethod method) {
+ return instanceAsMethodOfCompanionClass(method, DEFAULT_METHOD_PREFIX);
+ }
+
+ // Represent a private instance interface method as a method of companion class.
+ final DexMethod privateAsMethodOfCompanionClass(DexMethod method) {
+ // Add an implicit argument to represent the receiver.
+ return instanceAsMethodOfCompanionClass(method, PRIVATE_METHOD_PREFIX);
}
/**
@@ -337,8 +394,16 @@
.append("it is required for default or static interface methods desugaring of `")
.append(referencedFrom.toSourceString())
.append("`");
- DexClass referencedFromClass = converter.appInfo.definitionFor(referencedFrom.getHolder());
options.reporter.warning(
- new StringDiagnostic(builder.toString(), referencedFromClass.getOrigin()));
+ new StringDiagnostic(builder.toString(), getMethodOrigin(referencedFrom)));
+ }
+
+ private Origin getMethodOrigin(DexMethod method) {
+ DexType holder = method.getHolder();
+ if (isCompanionClassType(holder)) {
+ holder = getInterfaceClassType(holder);
+ }
+ DexClass clazz = converter.appInfo.definitionFor(holder);
+ return clazz == null ? Origin.unknown() : clazz.getOrigin();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 3d162c7..8c17d91 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -55,7 +55,7 @@
Code code = virtual.getCode();
if (code == null) {
throw new CompilationError("Code is missing for default "
- + "interface method: " + virtual.method.toSourceString());
+ + "interface method: " + virtual.method.toSourceString(), iface.origin);
}
MethodAccessFlags newFlags = virtual.accessFlags.copy();
@@ -88,24 +88,53 @@
}
remainingMethods.clear();
- // Process static methods, move them into companion class as well.
+ // Process static and private methods, move them into companion class as well,
+ // make private instance methods static.
for (DexEncodedMethod direct : iface.directMethods()) {
- if (direct.accessFlags.isPrivate()) {
- // We only expect to see private methods which are lambda$ methods,
- // and they are supposed to be relaxed to package private static methods
- // by this time by lambda rewriter.
- throw new Unimplemented("Private method are not yet supported.");
+ MethodAccessFlags originalFlags = direct.accessFlags;
+ MethodAccessFlags newFlags = originalFlags.copy();
+ if (originalFlags.isPrivate()) {
+ newFlags.unsetPrivate();
+ newFlags.setPublic();
}
if (isStaticMethod(direct)) {
+ assert originalFlags.isPrivate() || originalFlags.isPublic()
+ : "Static interface method " + direct.toSourceString() + " is expected to "
+ + "either be public or private in " + iface.origin;
companionMethods.add(new DexEncodedMethod(
- rewriter.staticAsMethodOfCompanionClass(direct.method), direct.accessFlags,
+ rewriter.staticAsMethodOfCompanionClass(direct.method), newFlags,
direct.annotations, direct.parameterAnnotations, direct.getCode()));
+
} else {
- // Since there are no interface constructors at this point,
- // this should only be class constructor.
- assert rewriter.factory.isClassConstructor(direct.method);
- remainingMethods.add(direct);
+ if (originalFlags.isPrivate()) {
+ assert !rewriter.factory.isClassConstructor(direct.method)
+ : "Unexpected private constructor " + direct.toSourceString()
+ + " in " + iface.origin;
+ newFlags.setStatic();
+
+ DexMethod companionMethod = rewriter.privateAsMethodOfCompanionClass(direct.method);
+
+ Code code = direct.getCode();
+ if (code == null) {
+ throw new CompilationError("Code is missing for private instance "
+ + "interface method: " + direct.method.toSourceString(), iface.origin);
+ }
+ DexCode dexCode = code.asDexCode();
+ // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
+ dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
+ assert (dexCode.getDebugInfo() == null)
+ || (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
+
+ companionMethods.add(new DexEncodedMethod(companionMethod,
+ newFlags, direct.annotations, direct.parameterAnnotations, code));
+
+ } else {
+ // Since there are no interface constructors at this point,
+ // this should only be class constructor.
+ assert rewriter.factory.isClassConstructor(direct.method);
+ remainingMethods.add(direct);
+ }
}
}
if (remainingMethods.size() < iface.directMethods().length) {
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index 6fb166f..711672c 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -112,7 +113,8 @@
write(writer);
return writer.toString();
} catch (IOException e) {
- return e.toString();
+ // StringWriter is not throwing IOException
+ throw new Unreachable(e);
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
index 5d34ca4..3ae9d06 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
@@ -14,11 +14,6 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.Sets;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -27,6 +22,7 @@
public class MinifiedNameMapPrinter {
+ private static final String NEW_LINE = "\n";
private final DexApplication application;
private final NamingLens namingLens;
private final Set<DexType> seenTypes = Sets.newIdentityHashSet();
@@ -42,13 +38,13 @@
return copy;
}
- private void writeClass(DexProgramClass clazz, PrintStream out) {
+ private void writeClass(DexProgramClass clazz, StringBuilder out) {
seenTypes.add(clazz.type);
DexString descriptor = namingLens.lookupDescriptor(clazz.type);
- out.print(DescriptorUtils.descriptorToJavaType(clazz.type.descriptor.toSourceString()));
- out.print(" -> ");
- out.print(DescriptorUtils.descriptorToJavaType(descriptor.toSourceString()));
- out.println(":");
+ out.append(DescriptorUtils.descriptorToJavaType(clazz.type.descriptor.toSourceString()));
+ out.append(" -> ");
+ out.append(DescriptorUtils.descriptorToJavaType(descriptor.toSourceString()));
+ out.append(":").append(NEW_LINE);
writeFields(sortedCopy(
clazz.instanceFields(), Comparator.comparing(DexEncodedField::toSourceString)), out);
writeFields(sortedCopy(
@@ -59,39 +55,39 @@
clazz.virtualMethods(), Comparator.comparing(DexEncodedMethod::toSourceString)), out);
}
- private void writeType(DexType type, PrintStream out) {
+ private void writeType(DexType type, StringBuilder out) {
if (type.isClassType() && seenTypes.add(type)) {
DexString descriptor = namingLens.lookupDescriptor(type);
- out.print(DescriptorUtils.descriptorToJavaType(type.descriptor.toSourceString()));
- out.print(" -> ");
- out.print(DescriptorUtils.descriptorToJavaType(descriptor.toSourceString()));
- out.println(":");
+ out.append(DescriptorUtils.descriptorToJavaType(type.descriptor.toSourceString()));
+ out.append(" -> ");
+ out.append(DescriptorUtils.descriptorToJavaType(descriptor.toSourceString()));
+ out.append(":").append(NEW_LINE);
}
}
- private void writeFields(DexEncodedField[] fields, PrintStream out) {
+ private void writeFields(DexEncodedField[] fields, StringBuilder out) {
for (DexEncodedField encodedField : fields) {
DexField field = encodedField.field;
DexString renamed = namingLens.lookupName(field);
if (renamed != field.name) {
- out.print(" ");
- out.print(field.type.toSourceString());
- out.print(" ");
- out.print(field.name.toSourceString());
- out.print(" -> ");
- out.println(renamed.toSourceString());
+ out.append(" ");
+ out.append(field.type.toSourceString());
+ out.append(" ");
+ out.append(field.name.toSourceString());
+ out.append(" -> ");
+ out.append(renamed.toSourceString()).append(NEW_LINE);
}
}
}
- private void writeMethod(MethodSignature signature, String renamed, PrintStream out) {
- out.print(" ");
- out.print(signature);
- out.print(" -> ");
- out.println(renamed);
+ private void writeMethod(MethodSignature signature, String renamed, StringBuilder out) {
+ out.append(" ");
+ out.append(signature.toString());
+ out.append(" -> ");
+ out.append(renamed).append(NEW_LINE);
}
- private void writeMethods(DexEncodedMethod[] methods, PrintStream out) {
+ private void writeMethods(DexEncodedMethod[] methods, StringBuilder out) {
for (DexEncodedMethod encodedMethod : methods) {
DexMethod method = encodedMethod.method;
DexString renamed = namingLens.lookupName(method);
@@ -103,7 +99,7 @@
}
}
- public void write(PrintStream out) {
+ public void write(StringBuilder out) {
// First write out all classes that have been renamed.
List<DexProgramClass> classes = new ArrayList<>(application.classes());
classes.sort(Comparator.comparing(DexProgramClass::toSourceString));
@@ -111,11 +107,4 @@
// Now write out all types only mentioned in descriptors that have been renamed.
namingLens.forAllRenamedTypes(type -> writeType(type, out));
}
-
- public void write(Path destination) throws IOException {
- PrintStream out = new PrintStream(Files.newOutputStream(destination), true,
- StandardCharsets.UTF_8.name());
- write(out);
- }
-
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index f394e76..3863c14 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.graph.DexApplication;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
@@ -47,11 +46,9 @@
assert namingLens != null && application != null;
// TODO(herhut): Should writing of the proguard-map file be split like this?
if (!namingLens.isIdentityLens()) {
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
- PrintStream stream = new PrintStream(bytes);
- new MinifiedNameMapPrinter(application, namingLens).write(stream);
- stream.flush();
- return bytes.toString();
+ StringBuilder map = new StringBuilder();
+ new MinifiedNameMapPrinter(application, namingLens).write(map);
+ return map.toString();
}
if (application.getProguardMap() != null) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
diff --git a/src/test/examplesJava9/privateinterfacemethods/C.class b/src/test/examplesJava9/privateinterfacemethods/C.class
new file mode 100644
index 0000000..430b6c1
--- /dev/null
+++ b/src/test/examplesJava9/privateinterfacemethods/C.class
Binary files differ
diff --git a/src/test/examplesJava9/privateinterfacemethods/I$1.class b/src/test/examplesJava9/privateinterfacemethods/I$1.class
new file mode 100644
index 0000000..29644e6
--- /dev/null
+++ b/src/test/examplesJava9/privateinterfacemethods/I$1.class
Binary files differ
diff --git a/src/test/examplesJava9/privateinterfacemethods/I$2.class b/src/test/examplesJava9/privateinterfacemethods/I$2.class
new file mode 100644
index 0000000..243b0af
--- /dev/null
+++ b/src/test/examplesJava9/privateinterfacemethods/I$2.class
Binary files differ
diff --git a/src/test/examplesJava9/privateinterfacemethods/I.class b/src/test/examplesJava9/privateinterfacemethods/I.class
new file mode 100644
index 0000000..893ed9e
--- /dev/null
+++ b/src/test/examplesJava9/privateinterfacemethods/I.class
Binary files differ
diff --git a/src/test/examplesJava9/privateinterfacemethods/IB.class b/src/test/examplesJava9/privateinterfacemethods/IB.class
new file mode 100644
index 0000000..e670d08
--- /dev/null
+++ b/src/test/examplesJava9/privateinterfacemethods/IB.class
Binary files differ
diff --git a/src/test/examplesJava9/privateinterfacemethods/PrivateInterfaceMethods.class b/src/test/examplesJava9/privateinterfacemethods/PrivateInterfaceMethods.class
new file mode 100644
index 0000000..dd3615b
--- /dev/null
+++ b/src/test/examplesJava9/privateinterfacemethods/PrivateInterfaceMethods.class
Binary files differ
diff --git a/src/test/examplesJava9/privateinterfacemethods/PrivateInterfaceMethods.java b/src/test/examplesJava9/privateinterfacemethods/PrivateInterfaceMethods.java
new file mode 100644
index 0000000..a133a7b
--- /dev/null
+++ b/src/test/examplesJava9/privateinterfacemethods/PrivateInterfaceMethods.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package privateinterfacemethods;
+
+public class PrivateInterfaceMethods {
+
+ public static void main(String[] args) {
+ System.out.println("1: " + I.DEFAULT.dFoo());
+ System.out.println("2: " + I.DEFAULT.lFoo());
+ System.out.println("3: " + I.xFoo());
+ System.out.println("4: " + new C().dFoo());
+ }
+}
+
+class C implements I {
+
+ public String dFoo() {
+ return "c>" + I.super.dFoo();
+ }
+}
+
+interface IB {
+
+ String dFoo();
+}
+
+interface I {
+
+ I DEFAULT = new I() {{
+ System.out.println("0: " + sFoo(false, this));
+ }};
+
+ static String xFoo() {
+ return "x>" + sFoo(true, null);
+ }
+
+ private static String sFoo(boolean simple, I it) {
+ return simple ? "s"
+ : ("s>" + it.iFoo(true) + ">" + new I() {
+ public String dFoo() {
+ return "a";
+ }
+ }.dFoo());
+ }
+
+ private String iFoo(boolean skip) {
+ return skip ? "i" : ("i>" + sFoo(false, this));
+ }
+
+ default String dFoo() {
+ return "d>" + iFoo(false);
+ }
+
+ default String lFoo() {
+ IB ib = () -> "l>" + iFoo(false);
+ return ib.dFoo();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index afa0f67..9d41d4e 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -173,14 +173,17 @@
private static Map<DexVm.Version, List<String>> failsOn =
ImmutableMap.of(
DexVm.Version.V4_4_4, ImmutableList.of(
+ "native-private-interface-methods",
// Dex version not supported
"varhandle"
),
DexVm.Version.V5_1_1, ImmutableList.of(
+ "native-private-interface-methods",
// Dex version not supported
"varhandle"
),
DexVm.Version.V6_0_1, ImmutableList.of(
+ "native-private-interface-methods",
// Dex version not supported
"varhandle"
),
@@ -194,6 +197,21 @@
)
);
+ // Defines methods failing on JVM, specifies the output to be used for comparison.
+ private static Map<String, String> expectedJvmResult =
+ ImmutableMap.of(
+ "native-private-interface-methods", "0: s>i>a\n"
+ + "1: d>i>s>i>a\n"
+ + "2: l>i>s>i>a\n"
+ + "3: x>s\n"
+ + "4: c>d>i>s>i>a\n",
+ "desugared-private-interface-methods", "0: s>i>a\n"
+ + "1: d>i>s>i>a\n"
+ + "2: l>i>s>i>a\n"
+ + "3: x>s\n"
+ + "4: c>d>i>s>i>a\n"
+ );
+
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@@ -215,6 +233,21 @@
}
@Test
+ public void nativePrivateInterfaceMethods() throws Throwable {
+ test("native-private-interface-methods", "privateinterfacemethods", "PrivateInterfaceMethods")
+ .withMinApiLevel(AndroidApiLevel.N.getLevel())
+ .run();
+ }
+
+ @Test
+ public void desugaredPrivateInterfaceMethods() throws Throwable {
+ test("desugared-private-interface-methods", "privateinterfacemethods",
+ "PrivateInterfaceMethods")
+ .withMinApiLevel(AndroidApiLevel.M.getLevel())
+ .run();
+ }
+
+ @Test
public void varHAndle() throws Throwable {
test("varhandle", "varhandle", "VarHandleTests")
.withMinApiLevel(AndroidApiLevel.P.getLevel())
@@ -244,16 +277,23 @@
Arrays.stream(dexes).map(path -> path.toString()).collect(Collectors.toList()),
qualifiedMainClass,
null);
- if (!expectedToFail) {
+ String jvmResult = null;
+ if (expectedJvmResult.containsKey(testName)) {
+ jvmResult = expectedJvmResult.get(testName);
+ } else if (!expectedToFail) {
ToolHelper.ProcessResult javaResult =
ToolHelper.runJava(ImmutableList.copyOf(jars), qualifiedMainClass);
assertEquals("JVM run failed", javaResult.exitCode, 0);
+ jvmResult = javaResult.stdout;
+ }
+
+ if (jvmResult != null) {
assertTrue(
"JVM output does not match art output.\n\tjvm: "
- + javaResult.stdout
+ + jvmResult
+ "\n\tart: "
+ output.replace("\r", ""),
- output.equals(javaResult.stdout.replace("\r", "")));
+ output.equals(jvmResult.replace("\r", "")));
}
}