content: /*
   * Copyright (C) 2014 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package dexfuzz.executors;
  
  import dexfuzz.ExecutionResult;
  import dexfuzz.Options;
  import dexfuzz.StreamConsumer;
  import dexfuzz.listeners.BaseListener;
  
  /**
   * Base class containing the common methods for executing a particular backend of ART.
   */
  public abstract class Executor {
    private StreamConsumer outputConsumer;
    private StreamConsumer errorConsumer;
  
    protected ExecutionResult executionResult;
    protected String executeClass;
  
    // Set by subclasses.
    protected String name;
    protected int timeout;
    protected BaseListener listener;
    protected String testLocation;
    protected Architecture architecture;
    protected Device device;
    private boolean needsCleanCodeCache;
  
    protected Executor(String name, int timeout, BaseListener listener, Architecture architecture,
        Device device, boolean needsCleanCodeCache) {
      executeClass = Options.executeClass;
  
      if (Options.shortTimeouts) {
        this.timeout = 2;
      } else {
        this.timeout = timeout;
      }
  
      this.name = name;
      this.listener = listener;
      this.architecture = architecture;
      this.device = device;
      this.needsCleanCodeCache = needsCleanCodeCache;
  
      if (Options.executeOnHost) {
        this.testLocation = System.getProperty("user.dir");
      } else {
        this.testLocation = Options.executeDirectory;
      }
  
      outputConsumer = new StreamConsumer();
      outputConsumer.start();
      errorConsumer = new StreamConsumer();
      errorConsumer.start();
    }
  
    /**
     * Called by subclass Executors in their execute() implementations.
     */
    protected ExecutionResult executeCommandWithTimeout(String command, boolean captureOutput) {
      String timeoutString = "timeout " + timeout + " ";
      return device.executeCommand(timeoutString + device.getExecutionShellPrefix() + command,
          captureOutput, outputConsumer, errorConsumer);
    }
  
    /**
     * Call this to make sure the StreamConsumer threads are stopped.
     */
    public void shutdown() {
      outputConsumer.shutdown();
      errorConsumer.shutdown();
    }
  
    /**
     * Called by the Fuzzer after each execution has finished, to clear the results.
     */
    public void reset() {
      executionResult = null;
    }
  
    /**
     * Called by the Fuzzer to verify the mutated program using the host-side dex2oat.
     */
    public boolean verifyOnHost(String programName) {
      StringBuilder commandBuilder = new StringBuilder();
      commandBuilder.append("dex2oat ");
  
      commandBuilder.append("--instruction-set=").append(architecture.asString());
      commandBuilder.append(" --instruction-set-features=default ");
  
      // Select the correct boot image.
      commandBuilder.append("--boot-image=").append(device.getAndroidProductOut());
      if (device.noBootImageAvailable()) {
        commandBuilder.append("/data/art-test/core.art ");
      } else {
        commandBuilder.append("/system/framework/boot.art ");
      }
  
      commandBuilder.append("--oat-file=output.oat ");
      commandBuilder.append("--android-root=").append(device.getAndroidHostOut()).append(" ");
      commandBuilder.append("--runtime-arg -classpath ");
      commandBuilder.append("--runtime-arg ").append(programName).append(" ");
      commandBuilder.append("--dex-file=").append(programName).append(" ");
      commandBuilder.append("--compiler-filter=interpret-only --runtime-arg -Xnorelocate ");
  
      ExecutionResult verificationResult = device.executeCommand(commandBuilder.toString(), true,
          outputConsumer, errorConsumer);
  
      boolean success = true;
  
      if (verificationResult.isSigabort()) {
        listener.handleHostVerificationSigabort(verificationResult);
        success = false;
      }
  
      if (success) {
        // Search for a keyword that indicates verification was not successful.
        // TODO: Determine if dex2oat crashed?
        for (String line : verificationResult.error) {
          if (line.contains("Verification error")
              || line.contains("Failure to verify dex file")) {
            success = false;
          }
          if (Options.dumpVerify) {
            // Strip out the start of the log lines.
            listener.handleDumpVerify(line.replaceFirst(".*(cc|h):\\d+] ",  ""));
          }
        }
      }
  
      if (!success) {
        listener.handleFailedHostVerification(verificationResult);
      }
  
      device.executeCommand("rm output.oat", false);
  
      return success;
    }
  
    /**
     * Called by the Fuzzer to upload the program to the target device.
     */
    public void prepareProgramForExecution(String programName) {
      if (!Options.executeOnHost) {
        device.pushProgramToDevice(programName, testLocation);
      }
  
      if (needsCleanCodeCache) {
        // Get the device to clean the code cache
        device.cleanCodeCache(architecture, testLocation, programName);
      }
    }
  
    /**
     * Executor subclasses need to override this, to construct their arguments for dalvikvm
     * invocation correctly.
     */
    public abstract void execute(String programName);
  
    /**
     * Fuzzer.checkForArchitectureSplit() will use this determine the architecture of the Executor.
     */
    public Architecture getArchitecture() {
      return architecture;
    }
  
    /**
     * Used by the Fuzzer to get result of execution.
     */
    public ExecutionResult getResult() {
      return executionResult;
    }
  
    /**
     * Because dex2oat can accept a program with soft errors on the host, and then fail after
     * performing hard verification on the target, we need to check if the Executor detected
     * a target verification failure, before doing anything else with the resulting output.
     * Used by the Fuzzer.
     */
    public boolean didTargetVerify() {
      // TODO: Remove this once host-verification can be forced to always fail?
      String output = executionResult.getFlattenedAll();
      if (output.contains("VerifyError") || output.contains("Verification failed on class")) {
        return false;
      }
      return true;
    }
  
    public String getName() {
      return name;
    }
  
    public void finishedWithProgramOnDevice() {
      device.resetProgramPushed();
    }
  }
  