1 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file 2 // for details. All rights reserved. Use of this source code is governed by a 3 // BSD-style license that can be found in the LICENSE file. 4 package com.android.tools.r8; 5 6 import static org.junit.Assert.assertEquals; 7 import static org.junit.Assert.assertTrue; 8 import static org.junit.Assert.fail; 9 10 import com.android.tools.r8.dex.ApplicationReader; 11 import com.android.tools.r8.dex.Constants; 12 import com.android.tools.r8.graph.DexApplication; 13 import com.android.tools.r8.shaking.ProguardRuleParserException; 14 import com.android.tools.r8.utils.AndroidApp; 15 import com.android.tools.r8.utils.InternalOptions; 16 import com.android.tools.r8.utils.ListUtils; 17 import com.android.tools.r8.utils.Timing; 18 import com.google.common.collect.ImmutableList; 19 import com.google.common.collect.ImmutableMap; 20 import com.google.common.collect.ImmutableSet; 21 import com.google.common.collect.Lists; 22 import com.google.common.io.ByteStreams; 23 import com.google.common.io.CharStreams; 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.io.InputStreamReader; 27 import java.nio.charset.StandardCharsets; 28 import java.nio.file.Files; 29 import java.nio.file.Path; 30 import java.nio.file.Paths; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.Collection; 34 import java.util.Collections; 35 import java.util.LinkedHashMap; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Set; 39 import java.util.concurrent.ExecutionException; 40 import java.util.function.Consumer; 41 import java.util.stream.Collectors; 42 import joptsimple.internal.Strings; 43 import org.junit.Assume; 44 import org.junit.rules.TemporaryFolder; 45 46 public class ToolHelper { 47 48 public static final String BUILD_DIR = "build/"; 49 public static final String EXAMPLES_DIR = "src/test/examples/"; 50 public static final String EXAMPLES_ANDROID_O_DIR = "src/test/examplesAndroidO/"; 51 public static final String EXAMPLES_BUILD_DIR = BUILD_DIR + "test/examples/"; 52 public static final String EXAMPLES_ANDROID_N_BUILD_DIR = BUILD_DIR + "test/examplesAndroidN/"; 53 public static final String EXAMPLES_ANDROID_O_BUILD_DIR = BUILD_DIR + "test/examplesAndroidO/"; 54 public static final String SMALI_BUILD_DIR = BUILD_DIR + "test/smali/"; 55 56 public static final String LINE_SEPARATOR = System.getProperty("line.separator"); 57 58 private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar"; 59 private static final int DEFAULT_MIN_SDK = 14; 60 61 public enum DexVm { 62 ART_4_4_4("4.4.4"), 63 ART_5_1_1("5.1.1"), 64 ART_6_0_1("6.0.1"), 65 ART_7_0_0("7.0.0"), 66 ART_DEFAULT("default"); 67 68 private static final ImmutableMap<String, DexVm> SHORT_NAME_MAP = 69 new ImmutableMap.Builder<String, DexVm>() 70 .putAll( 71 Arrays.stream(DexVm.values()).collect( 72 Collectors.toMap(a -> a.toString(), a -> a))) 73 .build(); 74 toString()75 public String toString() { 76 return shortName; 77 } 78 fromShortName(String shortName)79 public static DexVm fromShortName(String shortName) { 80 return SHORT_NAME_MAP.get(shortName); 81 } 82 isNewerThan(DexVm other)83 public boolean isNewerThan(DexVm other) { 84 return compareTo(other) > 0; 85 } 86 DexVm(String shortName)87 private DexVm(String shortName) { 88 this.shortName = shortName; 89 } 90 91 private final String shortName; 92 } 93 94 95 public abstract static class CommandBuilder { 96 97 private List<String> options = new ArrayList<>(); 98 private Map<String, String> systemProperties = new LinkedHashMap<>(); 99 private List<String> classpaths = new ArrayList<>(); 100 private String mainClass; 101 private List<String> programArguments = new ArrayList<>(); 102 private List<String> bootClassPaths = new ArrayList<>(); 103 appendArtOption(String option)104 public CommandBuilder appendArtOption(String option) { 105 options.add(option); 106 return this; 107 } 108 appendArtSystemProperty(String key, String value)109 public CommandBuilder appendArtSystemProperty(String key, String value) { 110 systemProperties.put(key, value); 111 return this; 112 } 113 appendClasspath(String classpath)114 public CommandBuilder appendClasspath(String classpath) { 115 classpaths.add(classpath); 116 return this; 117 } 118 setMainClass(String className)119 public CommandBuilder setMainClass(String className) { 120 this.mainClass = className; 121 return this; 122 } 123 appendProgramArgument(String option)124 public CommandBuilder appendProgramArgument(String option) { 125 programArguments.add(option); 126 return this; 127 } 128 appendBootClassPath(String lib)129 public CommandBuilder appendBootClassPath(String lib) { 130 bootClassPaths.add(lib); 131 return this; 132 } 133 command()134 private List<String> command() { 135 List<String> result = new ArrayList<>(); 136 // The art script _must_ be run with bash, bots default to /bin/dash for /bin/sh, so 137 // explicitly set it; 138 if (isLinux()) { 139 result.add("/bin/bash"); 140 } else { 141 result.add("tools/docker/run.sh"); 142 } 143 result.add(getExecutable()); 144 for (String option : options) { 145 result.add(option); 146 } 147 for (Map.Entry<String, String> entry : systemProperties.entrySet()) { 148 StringBuilder builder = new StringBuilder("-D"); 149 builder.append(entry.getKey()); 150 builder.append("="); 151 builder.append(entry.getValue()); 152 result.add(builder.toString()); 153 } 154 if (!classpaths.isEmpty()) { 155 result.add("-cp"); 156 result.add(Strings.join(classpaths, ":")); 157 } 158 if (!bootClassPaths.isEmpty()) { 159 result.add("-Xbootclasspath:" + String.join(":", bootClassPaths)); 160 } 161 if (mainClass != null) { 162 result.add(mainClass); 163 } 164 for (String argument : programArguments) { 165 result.add(argument); 166 } 167 return result; 168 } 169 asProcessBuilder()170 public ProcessBuilder asProcessBuilder() { 171 return new ProcessBuilder(command()); 172 } 173 build()174 public String build() { 175 return String.join(" ", command()); 176 } 177 getExecutable()178 protected abstract String getExecutable(); 179 } 180 181 public static class ArtCommandBuilder extends CommandBuilder { 182 183 private DexVm version; 184 ArtCommandBuilder()185 public ArtCommandBuilder() { 186 } 187 ArtCommandBuilder(DexVm version)188 public ArtCommandBuilder(DexVm version) { 189 assert ART_BINARY_VERSIONS.containsKey(version); 190 this.version = version; 191 } 192 193 @Override getExecutable()194 protected String getExecutable() { 195 return version != null ? getArtBinary(version) : getArtBinary(); 196 } 197 } 198 199 public static class DXCommandBuilder extends CommandBuilder { 200 DXCommandBuilder()201 public DXCommandBuilder() { 202 appendProgramArgument("--dex"); 203 } 204 205 @Override getExecutable()206 protected String getExecutable() { 207 return DX; 208 } 209 } 210 211 private static class StreamReader implements Runnable { 212 213 private InputStream stream; 214 private String result; 215 StreamReader(InputStream stream)216 public StreamReader(InputStream stream) { 217 this.stream = stream; 218 } 219 getResult()220 public String getResult() { 221 return result; 222 } 223 224 @Override run()225 public void run() { 226 try { 227 result = CharStreams.toString(new InputStreamReader(stream, StandardCharsets.UTF_8)); 228 stream.close(); 229 } catch (IOException e) { 230 result = "Failed reading result for stream " + stream; 231 } 232 } 233 } 234 235 private static final String TOOLS = "tools"; 236 private static final Map<DexVm, String> ART_DIRS = 237 ImmutableMap.of( 238 DexVm.ART_DEFAULT, "art", 239 DexVm.ART_7_0_0, "art-7.0.0", 240 DexVm.ART_6_0_1, "art-6.0.1", 241 DexVm.ART_5_1_1, "art-5.1.1", 242 DexVm.ART_4_4_4, "dalvik"); 243 private static final Map<DexVm, String> ART_BINARY_VERSIONS = 244 ImmutableMap.of( 245 DexVm.ART_DEFAULT, "bin/art", 246 DexVm.ART_7_0_0, "bin/art", 247 DexVm.ART_6_0_1, "bin/art", 248 DexVm.ART_5_1_1, "bin/art", 249 DexVm.ART_4_4_4, "bin/dalvik"); 250 251 private static final Map<DexVm, String> ART_BINARY_VERSIONS_X64 = 252 ImmutableMap.of( 253 DexVm.ART_DEFAULT, "bin/art", 254 DexVm.ART_7_0_0, "bin/art", 255 DexVm.ART_6_0_1, "bin/art"); 256 private static final List<String> ART_BOOT_LIBS = 257 ImmutableList.of( 258 "core-libart-hostdex.jar", 259 "core-oj-hostdex.jar", 260 "apache-xml-hostdex.jar" 261 ); 262 263 private static final String LIB_PATH = TOOLS + "/linux/art/lib"; 264 private static final String DX = TOOLS + "/linux/dx/bin/dx"; 265 private static final String DEX2OAT = TOOLS + "/linux/art/bin/dex2oat"; 266 private static final String ANGLER_DIR = TOOLS + "/linux/art/product/angler"; 267 private static final String ANGLER_BOOT_IMAGE = ANGLER_DIR + "/system/framework/boot.art"; 268 getArtDir(DexVm version)269 public static String getArtDir(DexVm version) { 270 String dir = ART_DIRS.get(version); 271 if (dir == null) { 272 throw new IllegalStateException("Does not support dex vm: " + version); 273 } 274 if (isLinux() || isMac()) { 275 // The Linux version is used on Mac, where it is run in a Docker container. 276 return TOOLS + "/linux/" + dir; 277 } 278 fail("Unsupported platform, we currently only support mac and linux: " + getPlatform()); 279 return ""; //never here 280 } 281 getArtBinary(DexVm version)282 public static String getArtBinary(DexVm version) { 283 String binary = ART_BINARY_VERSIONS.get(version); 284 if (binary == null) { 285 throw new IllegalStateException("Does not support running with dex vm: " + version); 286 } 287 return getArtDir(version) + "/" + binary; 288 } 289 getDefaultAndroidJar()290 public static String getDefaultAndroidJar() { 291 return getAndroidJar(Constants.DEFAULT_ANDROID_API); 292 } 293 getAndroidJar(int minSdkVersion)294 public static String getAndroidJar(int minSdkVersion) { 295 return String.format( 296 ANDROID_JAR_PATTERN, 297 minSdkVersion == Constants.DEFAULT_ANDROID_API ? DEFAULT_MIN_SDK : minSdkVersion); 298 } 299 getJdwpTestsJarPath(int minSdk)300 public static Path getJdwpTestsJarPath(int minSdk) { 301 if (minSdk >= Constants.ANDROID_N_API) { 302 return Paths.get("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-host.jar"); 303 } else { 304 return Paths.get(ToolHelper.BUILD_DIR, "libs", "jdwp-tests-preN.jar"); 305 } 306 } 307 308 // For non-Linux platforms create the temporary directory in the repository root to simplify 309 // running Art in a docker container getTemporaryFolderForTest()310 public static TemporaryFolder getTemporaryFolderForTest() { 311 return new TemporaryFolder(ToolHelper.isLinux() ? null : Paths.get("build", "tmp").toFile()); 312 } 313 getArtBinary()314 public static String getArtBinary() { 315 return getArtBinary(getDexVm()); 316 } 317 getArtVersions()318 public static Set<DexVm> getArtVersions() { 319 String artVersion = System.getProperty("dex_vm"); 320 if (artVersion != null) { 321 DexVm artVersionEnum = getDexVm(); 322 if (!ART_BINARY_VERSIONS.containsKey(artVersionEnum)) { 323 throw new RuntimeException("Unsupported Art version " + artVersion); 324 } 325 return ImmutableSet.of(artVersionEnum); 326 } else { 327 if (isLinux()) { 328 return ART_BINARY_VERSIONS.keySet(); 329 } else { 330 return ART_BINARY_VERSIONS_X64.keySet(); 331 } 332 } 333 } 334 getArtBootLibs()335 public static List<String> getArtBootLibs() { 336 String prefix = getArtDir(getDexVm()) + "/"; 337 List<String> result = new ArrayList<>(); 338 ART_BOOT_LIBS.stream().forEach(x -> result.add(prefix + "framework/" + x)); 339 return result; 340 } 341 342 // Returns if the passed in vm to use is the default. isDefaultDexVm()343 public static boolean isDefaultDexVm() { 344 return getDexVm() == DexVm.ART_DEFAULT; 345 } 346 getDexVm()347 public static DexVm getDexVm() { 348 String artVersion = System.getProperty("dex_vm"); 349 if (artVersion == null) { 350 return DexVm.ART_DEFAULT; 351 } else { 352 DexVm artVersionEnum = DexVm.fromShortName(artVersion); 353 if (artVersionEnum == null) { 354 throw new RuntimeException("Unsupported Art version " + artVersion); 355 } else { 356 return artVersionEnum; 357 } 358 } 359 } 360 getMinApiLevelForDexVm(DexVm dexVm)361 public static int getMinApiLevelForDexVm(DexVm dexVm) { 362 switch (dexVm) { 363 case ART_DEFAULT: 364 return Constants.ANDROID_O_API; 365 case ART_7_0_0: 366 return Constants.ANDROID_N_API; 367 default: 368 return Constants.DEFAULT_ANDROID_API; 369 } 370 } 371 getPlatform()372 private static String getPlatform() { 373 return System.getProperty("os.name"); 374 } 375 isLinux()376 public static boolean isLinux() { 377 return getPlatform().startsWith("Linux"); 378 } 379 isMac()380 public static boolean isMac() { 381 return getPlatform().startsWith("Mac"); 382 } 383 isWindows()384 public static boolean isWindows() { 385 return getPlatform().startsWith("Windows"); 386 } 387 artSupported()388 public static boolean artSupported() { 389 if (!isLinux() && !isMac()) { 390 System.err.println("Testing on your platform is not fully supported. " + 391 "Art does not work on on your platform."); 392 return false; 393 } 394 return true; 395 } 396 getClassPathForTests()397 public static Path getClassPathForTests() { 398 return Paths.get(BUILD_DIR, "classes", "test"); 399 } 400 getClassFileForTestClass(Class clazz)401 public static Path getClassFileForTestClass(Class clazz) { 402 List<String> parts = Lists.newArrayList(clazz.getCanonicalName().split("\\.")); 403 Class enclosing = clazz; 404 while (enclosing.getEnclosingClass() != null) { 405 parts.set(parts.size() - 2, parts.get(parts.size() - 2) + "$" + parts.get(parts.size() - 1)); 406 parts.remove(parts.size() - 1); 407 enclosing = clazz.getEnclosingClass(); 408 } 409 parts.set(parts.size() - 1, parts.get(parts.size() - 1) + ".class"); 410 return getClassPathForTests().resolve( 411 Paths.get("", parts.toArray(new String[parts.size() - 1]))); 412 } 413 buildApplication(List<String> fileNames)414 public static DexApplication buildApplication(List<String> fileNames) 415 throws IOException, ExecutionException { 416 return new ApplicationReader( 417 AndroidApp.fromProgramFiles(ListUtils.map(fileNames, Paths::get)), 418 new InternalOptions(), 419 new Timing("ToolHelper buildApplication")) 420 .read(); 421 } 422 prepareR8CommandBuilder(AndroidApp app)423 public static R8Command.Builder prepareR8CommandBuilder(AndroidApp app) { 424 return R8Command.builder(app); 425 } 426 runR8(AndroidApp app)427 public static AndroidApp runR8(AndroidApp app) 428 throws ExecutionException, IOException, ProguardRuleParserException, CompilationException { 429 return runR8(R8Command.builder(app).build()); 430 } 431 runR8(AndroidApp app, Path output)432 public static AndroidApp runR8(AndroidApp app, Path output) 433 throws ExecutionException, IOException, ProguardRuleParserException, CompilationException { 434 assert output != null; 435 return runR8(R8Command.builder(app).setOutputPath(output).build()); 436 } 437 runR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)438 public static AndroidApp runR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer) 439 throws ProguardRuleParserException, ExecutionException, IOException, CompilationException { 440 return runR8(R8Command.builder(app).build(), optionsConsumer); 441 } 442 runR8(R8Command command)443 public static AndroidApp runR8(R8Command command) 444 throws ProguardRuleParserException, ExecutionException, IOException { 445 return runR8(command, null); 446 } 447 runR8(R8Command command, Consumer<InternalOptions> optionsConsumer)448 public static AndroidApp runR8(R8Command command, Consumer<InternalOptions> optionsConsumer) 449 throws ProguardRuleParserException, ExecutionException, IOException { 450 return runR8WithFullResult(command, optionsConsumer).androidApp; 451 } 452 runR8WithFullResult( R8Command command, Consumer<InternalOptions> optionsConsumer)453 public static CompilationResult runR8WithFullResult( 454 R8Command command, Consumer<InternalOptions> optionsConsumer) 455 throws ProguardRuleParserException, ExecutionException, IOException { 456 // TODO(zerny): Should we really be adding the android library in ToolHelper? 457 AndroidApp app = command.getInputApp(); 458 if (app.getClassLibraryResources().isEmpty()) { 459 app = 460 AndroidApp.builder(app) 461 .addLibraryFiles(Paths.get(getAndroidJar(command.getMinApiLevel()))) 462 .build(); 463 } 464 InternalOptions options = command.getInternalOptions(); 465 // TODO(zerny): Should we really be setting this in ToolHelper? 466 options.ignoreMissingClasses = true; 467 if (optionsConsumer != null) { 468 optionsConsumer.accept(options); 469 } 470 CompilationResult result = R8.runForTesting(app, options); 471 R8.writeOutputs(command, options, result.androidApp); 472 return result; 473 } 474 runR8(String fileName, String out)475 public static AndroidApp runR8(String fileName, String out) 476 throws IOException, ProguardRuleParserException, ExecutionException, CompilationException { 477 return runR8(Collections.singletonList(fileName), out); 478 } 479 runR8(Collection<String> fileNames, String out)480 public static AndroidApp runR8(Collection<String> fileNames, String out) 481 throws IOException, ProguardRuleParserException, ExecutionException, CompilationException { 482 return R8.run( 483 R8Command.builder() 484 .addProgramFiles(ListUtils.map(fileNames, Paths::get)) 485 .setOutputPath(Paths.get(out)) 486 .setIgnoreMissingClasses(true) 487 .build()); 488 } 489 runD8(AndroidApp app)490 public static AndroidApp runD8(AndroidApp app) throws CompilationException, IOException { 491 return runD8(app, null); 492 } 493 runD8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)494 public static AndroidApp runD8(AndroidApp app, Consumer<InternalOptions> optionsConsumer) 495 throws CompilationException, IOException { 496 return runD8(D8Command.builder(app).build(), optionsConsumer); 497 } 498 runD8(D8Command command)499 public static AndroidApp runD8(D8Command command) throws IOException { 500 return runD8(command, null); 501 } 502 runD8(D8Command command, Consumer<InternalOptions> optionsConsumer)503 public static AndroidApp runD8(D8Command command, Consumer<InternalOptions> optionsConsumer) 504 throws IOException { 505 InternalOptions options = command.getInternalOptions(); 506 if (optionsConsumer != null) { 507 optionsConsumer.accept(options); 508 } 509 AndroidApp result = D8.runForTesting(command.getInputApp(), options).androidApp; 510 if (command.getOutputPath() != null) { 511 result.write(command.getOutputPath(), command.getOutputMode()); 512 } 513 return result; 514 } 515 runDexer(String fileName, String outDir, String... extraArgs)516 public static AndroidApp runDexer(String fileName, String outDir, String... extraArgs) 517 throws IOException { 518 List<String> args = new ArrayList<>(); 519 Collections.addAll(args, extraArgs); 520 Collections.addAll(args, "--output=" + outDir + "/classes.dex", fileName); 521 int result = runDX(args.toArray(new String[args.size()])).exitCode; 522 return result != 0 ? null : AndroidApp.fromProgramDirectory(Paths.get(outDir)); 523 } 524 runDX(String[] args)525 public static ProcessResult runDX(String[] args) throws IOException { 526 Assume.assumeTrue(ToolHelper.artSupported()); 527 DXCommandBuilder builder = new DXCommandBuilder(); 528 for (String arg : args) { 529 builder.appendProgramArgument(arg); 530 } 531 return runProcess(builder.asProcessBuilder()); 532 } 533 runJava(Class clazz)534 public static ProcessResult runJava(Class clazz) throws Exception { 535 String main = clazz.getCanonicalName(); 536 Path path = getClassPathForTests(); 537 return runJava(ImmutableList.of(path.toString()), main); 538 } 539 runJava(List<String> classpath, String mainClass)540 public static ProcessResult runJava(List<String> classpath, String mainClass) throws IOException { 541 ProcessBuilder builder = new ProcessBuilder( 542 getJavaExecutable(), "-cp", String.join(":", classpath), mainClass); 543 return runProcess(builder); 544 } 545 forkD8(Path dir, String... args)546 public static ProcessResult forkD8(Path dir, String... args) 547 throws IOException, InterruptedException { 548 return forkJava(dir, D8.class, args); 549 } 550 forkR8(Path dir, String... args)551 public static ProcessResult forkR8(Path dir, String... args) 552 throws IOException, InterruptedException { 553 return forkJava(dir, R8.class, ImmutableList.builder() 554 .addAll(Arrays.asList(args)) 555 .add("--ignore-missing-classes") 556 .build() 557 .toArray(new String[0])); 558 } 559 forkJava(Path dir, Class clazz, String... args)560 private static ProcessResult forkJava(Path dir, Class clazz, String... args) 561 throws IOException, InterruptedException { 562 List<String> command = new ImmutableList.Builder<String>() 563 .add(getJavaExecutable()) 564 .add("-cp").add(System.getProperty("java.class.path")) 565 .add(clazz.getCanonicalName()) 566 .addAll(Arrays.asList(args)) 567 .build(); 568 return runProcess(new ProcessBuilder(command).directory(dir.toFile())); 569 } 570 getJavaExecutable()571 public static String getJavaExecutable() { 572 return Paths.get(System.getProperty("java.home"), "bin", "java").toString(); 573 } 574 runArtNoVerificationErrors(String file, String mainClass)575 public static String runArtNoVerificationErrors(String file, String mainClass) 576 throws IOException { 577 return runArtNoVerificationErrors(Collections.singletonList(file), mainClass, null); 578 } 579 runArtNoVerificationErrors(List<String> files, String mainClass, Consumer<ArtCommandBuilder> extras)580 public static String runArtNoVerificationErrors(List<String> files, String mainClass, 581 Consumer<ArtCommandBuilder> extras) 582 throws IOException { 583 return runArtNoVerificationErrors(files, mainClass, extras, null); 584 } 585 runArtNoVerificationErrors(List<String> files, String mainClass, Consumer<ArtCommandBuilder> extras, DexVm version)586 public static String runArtNoVerificationErrors(List<String> files, String mainClass, 587 Consumer<ArtCommandBuilder> extras, 588 DexVm version) 589 throws IOException { 590 ArtCommandBuilder builder = 591 version != null ? new ArtCommandBuilder(version) : new ArtCommandBuilder(); 592 files.forEach(builder::appendClasspath); 593 builder.setMainClass(mainClass); 594 if (extras != null) { 595 extras.accept(builder); 596 } 597 return runArtNoVerificationErrors(builder); 598 } 599 runArtNoVerificationErrors(ArtCommandBuilder builder)600 public static String runArtNoVerificationErrors(ArtCommandBuilder builder) throws IOException { 601 ProcessResult result = runArtProcess(builder); 602 if (result.stderr.contains("Verification error")) { 603 fail("Verification error: \n" + result.stderr); 604 } 605 return result.stdout; 606 } 607 runArtProcess(ArtCommandBuilder builder)608 private static ProcessResult runArtProcess(ArtCommandBuilder builder) throws IOException { 609 Assume.assumeTrue(ToolHelper.artSupported()); 610 ProcessResult result = runProcess(builder.asProcessBuilder()); 611 if (result.exitCode != 0) { 612 fail("Unexpected art failure: '" + result.stderr + "'\n" + result.stdout); 613 } 614 return result; 615 } 616 runArt(ArtCommandBuilder builder)617 public static String runArt(ArtCommandBuilder builder) throws IOException { 618 ProcessResult result = runArtProcess(builder); 619 return result.stdout; 620 } 621 checkArtOutputIdentical(String file1, String file2, String mainClass, DexVm version)622 public static String checkArtOutputIdentical(String file1, String file2, String mainClass, 623 DexVm version) 624 throws IOException { 625 return checkArtOutputIdentical(Collections.singletonList(file1), 626 Collections.singletonList(file2), mainClass, null, version); 627 } 628 checkArtOutputIdentical(List<String> files1, List<String> files2, String mainClass, Consumer<ArtCommandBuilder> extras, DexVm version)629 public static String checkArtOutputIdentical(List<String> files1, List<String> files2, 630 String mainClass, 631 Consumer<ArtCommandBuilder> extras, 632 DexVm version) 633 throws IOException { 634 // Run art on original. 635 for (String file : files1) { 636 assertTrue("file1 " + file + " must exists", Files.exists(Paths.get(file))); 637 } 638 String output1 = ToolHelper.runArtNoVerificationErrors(files1, mainClass, extras, version); 639 // Run art on R8 processed version. 640 for (String file : files2) { 641 assertTrue("file2 " + file + " must exists", Files.exists(Paths.get(file))); 642 } 643 String output2 = ToolHelper.runArtNoVerificationErrors(files2, mainClass, extras, version); 644 assertEquals(output1, output2); 645 return output1; 646 } 647 runDex2Oat(Path file, Path outFile)648 public static void runDex2Oat(Path file, Path outFile) throws IOException { 649 Assume.assumeTrue(ToolHelper.artSupported()); 650 assert Files.exists(file); 651 assert ByteStreams.toByteArray(Files.newInputStream(file)).length > 0; 652 List<String> command = new ArrayList<>(); 653 command.add(DEX2OAT); 654 command.add("--android-root=" + ANGLER_DIR); 655 command.add("--runtime-arg"); 656 command.add("-Xnorelocate"); 657 command.add("--boot-image=" + ANGLER_BOOT_IMAGE); 658 command.add("--dex-file=" + file.toAbsolutePath()); 659 command.add("--oat-file=" + outFile.toAbsolutePath()); 660 command.add("--instruction-set=arm64"); 661 command.add("--compiler-filter=interpret-only"); 662 ProcessBuilder builder = new ProcessBuilder(command); 663 builder.environment().put("LD_LIBRARY_PATH", LIB_PATH); 664 ProcessResult result = runProcess(builder); 665 if (result.exitCode != 0) { 666 fail("dex2oat failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr); 667 } 668 if (result.stderr.contains("Verification error")) { 669 fail("Verification error: \n" + result.stderr); 670 } 671 } 672 673 public static class ProcessResult { 674 675 public final int exitCode; 676 public final String stdout; 677 public final String stderr; 678 ProcessResult(int exitCode, String stdout, String stderr)679 ProcessResult(int exitCode, String stdout, String stderr) { 680 this.exitCode = exitCode; 681 this.stdout = stdout; 682 this.stderr = stderr; 683 } 684 685 @Override toString()686 public String toString() { 687 StringBuilder builder = new StringBuilder(); 688 builder.append("EXIT CODE: "); 689 builder.append(exitCode); 690 builder.append("\n"); 691 builder.append("STDOUT: "); 692 builder.append("\n"); 693 builder.append(stdout); 694 builder.append("\n"); 695 builder.append("STDERR: "); 696 builder.append("\n"); 697 builder.append(stderr); 698 builder.append("\n"); 699 return builder.toString(); 700 } 701 } 702 runProcess(ProcessBuilder builder)703 public static ProcessResult runProcess(ProcessBuilder builder) throws IOException { 704 System.out.println(String.join(" ", builder.command())); 705 Process p = builder.start(); 706 // Drain stdout and stderr so that the process does not block. Read stdout and stderr 707 // in parallel to make sure that neither buffer can get filled up which will cause the 708 // C program to block in a call to write. 709 StreamReader stdoutReader = new StreamReader(p.getInputStream()); 710 StreamReader stderrReader = new StreamReader(p.getErrorStream()); 711 Thread stdoutThread = new Thread(stdoutReader); 712 Thread stderrThread = new Thread(stderrReader); 713 stdoutThread.start(); 714 stderrThread.start(); 715 try { 716 p.waitFor(); 717 stdoutThread.join(); 718 stderrThread.join(); 719 } catch (InterruptedException e) { 720 throw new RuntimeException("Execution interrupted", e); 721 } 722 return new ProcessResult(p.exitValue(), stdoutReader.getResult(), stderrReader.getResult()); 723 } 724 getApp(BaseCommand command)725 public static AndroidApp getApp(BaseCommand command) { 726 return command.getInputApp(); 727 } 728 } 729