com.android.dx.command.dexer.Main Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of builder Show documentation
Show all versions of builder Show documentation
Library to build Android applications.
/*
* Copyright (C) 2007 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 com.android.dx.command.dexer;
import static java.lang.System.err;
import static java.lang.System.out;
import com.android.dex.Dex;
import com.android.dex.DexException;
import com.android.dex.DexFormat;
import com.android.dex.util.FileUtils;
import com.android.dx.Version;
import com.android.dx.cf.code.SimException;
import com.android.dx.cf.direct.ClassPathOpener;
import com.android.dx.cf.direct.ClassPathOpener.FileNameFilter;
import com.android.dx.cf.direct.DirectClassFile;
import com.android.dx.cf.direct.StdAttributeFactory;
import com.android.dx.cf.iface.ParseException;
import com.android.dx.command.UsageException;
import com.android.dx.dex.DexOptions;
import com.android.dx.dex.cf.CfOptions;
import com.android.dx.dex.cf.CfTranslator;
import com.android.dx.dex.code.PositionList;
import com.android.dx.dex.file.ClassDefItem;
import com.android.dx.dex.file.DexFile;
import com.android.dx.dex.file.EncodedMethod;
import com.android.dx.merge.CollisionPolicy;
import com.android.dx.merge.DexMerger;
import com.android.dx.rop.annotation.Annotation;
import com.android.dx.rop.annotation.Annotations;
import com.android.dx.rop.annotation.AnnotationsList;
import com.android.dx.rop.code.RegisterSpec;
import com.android.dx.rop.cst.CstNat;
import com.android.dx.rop.cst.CstString;
import com.android.dx.rop.type.Prototype;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
/**
* Main class for the class file translator.
*/
public class Main {
public static final int CONCURRENCY_LEVEL = 8;
/**
* File extension of a {@code .dex} file.
*/
private static final String DEX_EXTENSION = ".dex";
/**
* File name prefix of a {@code .dex} file automatically loaded in an
* archive.
*/
private static final String DEX_PREFIX = "classes";
/**
* {@code non-null;} the lengthy message that tries to discourage
* people from defining core classes in applications
*/
private static final String IN_RE_CORE_CLASSES =
"Ill-advised or mistaken usage of a core class (java.* or javax.*)\n" +
"when not building a core library.\n\n" +
"This is often due to inadvertently including a core library file\n" +
"in your application's project, when using an IDE (such as\n" +
"Eclipse). If you are sure you're not intentionally defining a\n" +
"core class, then this is the most likely explanation of what's\n" +
"going on.\n\n" +
"However, you might actually be trying to define a class in a core\n" +
"namespace, the source of which you may have taken, for example,\n" +
"from a non-Android virtual machine project. This will most\n" +
"assuredly not work. At a minimum, it jeopardizes the\n" +
"compatibility of your app with future versions of the platform.\n" +
"It is also often of questionable legality.\n\n" +
"If you really intend to build a core library -- which is only\n" +
"appropriate as part of creating a full virtual machine\n" +
"distribution, as opposed to compiling an application -- then use\n" +
"the \"--core-library\" option to suppress this error message.\n\n" +
"If you go ahead and use \"--core-library\" but are in fact\n" +
"building an application, then be forewarned that your application\n" +
"will still fail to build or run, at some point. Please be\n" +
"prepared for angry customers who find, for example, that your\n" +
"application ceases to function once they upgrade their operating\n" +
"system. You will be to blame for this problem.\n\n" +
"If you are legitimately using some code that happens to be in a\n" +
"core package, then the easiest safe alternative you have is to\n" +
"repackage that code. That is, move the classes in question into\n" +
"your own package namespace. This means that they will never be in\n" +
"conflict with core system classes. JarJar is a tool that may help\n" +
"you in this endeavor. If you find that you cannot do this, then\n" +
"that is an indication that the path you are on will ultimately\n" +
"lead to pain, suffering, grief, and lamentation.\n";
/**
* {@code non-null;} name of the standard manifest file in {@code .jar}
* files
*/
private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
/**
* {@code non-null;} attribute name for the (quasi-standard?)
* {@code Created-By} attribute
*/
private static final Attributes.Name CREATED_BY =
new Attributes.Name("Created-By");
/**
* {@code non-null;} list of {@code javax} subpackages that are considered
* to be "core". Note:: This list must be sorted, since it
* is binary-searched.
*/
private static final String[] JAVAX_CORE = {
"accessibility", "crypto", "imageio", "management", "naming", "net",
"print", "rmi", "security", "sip", "sound", "sql", "swing",
"transaction", "xml"
};
/* Array.newInstance may be added by RopperMachine,
* ArrayIndexOutOfBoundsException. may be added by EscapeAnalysis */
private static final int MAX_METHOD_ADDED_DURING_DEX_CREATION = 2;
/* .TYPE */
private static final int MAX_FIELD_ADDED_DURING_DEX_CREATION = 9;
/** number of errors during processing */
private AtomicInteger errors = new AtomicInteger(0);
/** {@code non-null;} parsed command-line arguments */
private Arguments args;
/** {@code non-null;} output file in-progress */
private DexFile outputDex;
/**
* {@code null-ok;} map of resources to include in the output, or
* {@code null} if resources are being ignored
*/
private TreeMap outputResources;
/** Library .dex files to merge into the output .dex. */
private final List libraryDexBuffers = new ArrayList();
/** Thread pool object used for multi-thread class translation. */
private ExecutorService classTranslatorPool;
/** Single thread executor, for collecting results of parallel translation,
* and adding classes to dex file in original input file order. */
private ExecutorService classDefItemConsumer;
/** Futures for {@code classDefItemConsumer} tasks. */
private List> addToDexFutures =
new ArrayList>();
/** Thread pool object used for multi-thread dex conversion (to byte array).
* Used in combination with multi-dex support, to allow outputing
* a completed dex file, in parallel with continuing processing. */
private ExecutorService dexOutPool;
/** Futures for {@code dexOutPool} task. */
private List> dexOutputFutures = new ArrayList>();
/** Lock object used to to coordinate dex file rotation, and
* multi-threaded translation. */
private Object dexRotationLock = new Object();
/** Record the number if method indices "reserved" for files
* committed to translation in the context of the current dex
* file, but not yet added. */
private int maxMethodIdsInProcess = 0;
/** Record the number if field indices "reserved" for files
* committed to translation in the context of the current dex
* file, but not yet added. */
private int maxFieldIdsInProcess = 0;
/** true if any files are successfully processed */
private volatile boolean anyFilesProcessed;
/** class files older than this must be defined in the target dex file. */
private long minimumFileAge = 0;
private Set classesInMainDex = null;
private List dexOutputArrays = new ArrayList();
private OutputStreamWriter humanOutWriter = null;
private final DxContext context;
public Main(DxContext context) {
this.context = context;
}
/**
* Run and exit if something unexpected happened.
* @param argArray the command line arguments
*/
public static void main(String[] argArray) throws IOException {
DxContext context = new DxContext();
Arguments arguments = new Arguments();
arguments.parse(argArray, context);
int result = new Main(context).run(arguments);
if (result != 0) {
System.exit(result);
}
}
public static void clearInternTables() {
Prototype.clearInternTable();
RegisterSpec.clearInternTable();
}
/**
* Run and return a result code.
* @param arguments the data + parameters for the conversion
* @return 0 if success > 0 otherwise.
*/
public int run(Arguments arguments) throws IOException {
// Reset the error count to start fresh.
errors.set(0);
// empty the list, so that tools that load dx and keep it around
// for multiple runs don't reuse older buffers.
libraryDexBuffers.clear();
args = arguments;
args.makeOptionsObjects(context);
OutputStream humanOutRaw = null;
if (args.humanOutName != null) {
humanOutRaw = openOutput(args.humanOutName);
humanOutWriter = new OutputStreamWriter(humanOutRaw);
}
try {
if (args.multiDex) {
return runMultiDex();
} else {
return runMonoDex();
}
} finally {
closeOutput(humanOutRaw);
}
}
/**
* {@code non-null;} Error message for too many method/field/type ids.
*/
public static String getTooManyIdsErrorMessage() {
return "Too many classes to fit in one dex file.";
}
private int runMonoDex() throws IOException {
File incrementalOutFile = null;
if (args.incremental) {
if (args.outName == null) {
err.println(
"error: no incremental output name specified");
return -1;
}
incrementalOutFile = new File(args.outName);
if (incrementalOutFile.exists()) {
minimumFileAge = incrementalOutFile.lastModified();
}
}
if (!processAllFiles()) {
return 1;
}
if (args.incremental && !anyFilesProcessed) {
return 0; // this was a no-op incremental build
}
// this array is null if no classes were defined
byte[] outArray = null;
if (!outputDex.isEmpty() || (args.humanOutName != null)) {
outArray = writeDex(outputDex);
if (outArray == null) {
return 2;
}
}
if (args.incremental) {
outArray = mergeIncremental(outArray, incrementalOutFile);
}
outArray = mergeLibraryDexBuffers(outArray);
if (args.jarOutput) {
// Effectively free up the (often massive) DexFile memory.
outputDex = null;
if (outArray != null) {
outputResources.put(DexFormat.DEX_IN_JAR_NAME, outArray);
}
if (!createJar(args.outName)) {
return 3;
}
} else if (outArray != null && args.outName != null) {
OutputStream out = openOutput(args.outName);
out.write(outArray);
closeOutput(out);
}
return 0;
}
private int runMultiDex() throws IOException {
assert !args.incremental;
if (args.mainDexListFile != null) {
classesInMainDex = new HashSet();
readPathsFromFile(args.mainDexListFile, classesInMainDex);
}
dexOutPool = Executors.newFixedThreadPool(args.numThreads);
if (!processAllFiles()) {
return 1;
}
if (!libraryDexBuffers.isEmpty()) {
throw new DexException("Library dex files are not supported in multi-dex mode");
}
if (outputDex != null) {
// this array is null if no classes were defined
dexOutputFutures.add(dexOutPool.submit(new DexWriter(outputDex)));
// Effectively free up the (often massive) DexFile memory.
outputDex = null;
}
try {
dexOutPool.shutdown();
if (!dexOutPool.awaitTermination(600L, TimeUnit.SECONDS)) {
throw new RuntimeException("Timed out waiting for dex writer threads.");
}
for (Future f : dexOutputFutures) {
dexOutputArrays.add(f.get());
}
} catch (InterruptedException ex) {
dexOutPool.shutdownNow();
throw new RuntimeException("A dex writer thread has been interrupted.");
} catch (Exception e) {
dexOutPool.shutdownNow();
throw new RuntimeException("Unexpected exception in dex writer thread");
}
if (args.jarOutput) {
for (int i = 0; i < dexOutputArrays.size(); i++) {
outputResources.put(getDexFileName(i),
dexOutputArrays.get(i));
}
if (!createJar(args.outName)) {
return 3;
}
} else if (args.outName != null) {
File outDir = new File(args.outName);
assert outDir.isDirectory();
for (int i = 0; i < dexOutputArrays.size(); i++) {
OutputStream out = new FileOutputStream(new File(outDir, getDexFileName(i)));
try {
out.write(dexOutputArrays.get(i));
} finally {
closeOutput(out);
}
}
}
return 0;
}
private static String getDexFileName(int i) {
if (i == 0) {
return DexFormat.DEX_IN_JAR_NAME;
} else {
return DEX_PREFIX + (i + 1) + DEX_EXTENSION;
}
}
private static void readPathsFromFile(String fileName, Collection paths) throws IOException {
BufferedReader bfr = null;
try {
FileReader fr = new FileReader(fileName);
bfr = new BufferedReader(fr);
String line;
while (null != (line = bfr.readLine())) {
paths.add(fixPath(line));
}
} finally {
if (bfr != null) {
bfr.close();
}
}
}
/**
* Merges the dex files {@code update} and {@code base}, preferring
* {@code update}'s definition for types defined in both dex files.
*
* @param base a file to find the previous dex file. May be a .dex file, a
* jar file possibly containing a .dex file, or null.
* @return the bytes of the merged dex file, or null if both the update
* and the base dex do not exist.
*/
private static byte[] mergeIncremental(byte[] update, File base) throws IOException {
Dex dexA = null;
Dex dexB = null;
if (update != null) {
dexA = new Dex(update);
}
if (base.exists()) {
dexB = new Dex(base);
}
Dex result;
if (dexA == null && dexB == null) {
return null;
} else if (dexA == null) {
result = dexB;
} else if (dexB == null) {
result = dexA;
} else {
result = new DexMerger(new Dex[] {dexA, dexB}, CollisionPolicy.KEEP_FIRST).merge();
}
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
result.writeTo(bytesOut);
return bytesOut.toByteArray();
}
/**
* Merges the dex files in library jars. If multiple dex files define the
* same type, this fails with an exception.
*/
private byte[] mergeLibraryDexBuffers(byte[] outArray) throws IOException {
ArrayList dexes = new ArrayList();
if (outArray != null) {
dexes.add(new Dex(outArray));
}
for (byte[] libraryDex : libraryDexBuffers) {
dexes.add(new Dex(libraryDex));
}
if (dexes.isEmpty()) {
return null;
}
Dex merged = new DexMerger(dexes.toArray(new Dex[dexes.size()]), CollisionPolicy.FAIL).merge();
return merged.getBytes();
}
/**
* Constructs the output {@link DexFile}, fill it in with all the
* specified classes, and populate the resources map if required.
*
* @return whether processing was successful
*/
private boolean processAllFiles() {
createDexFile();
if (args.jarOutput) {
outputResources = new TreeMap();
}
anyFilesProcessed = false;
String[] fileNames = args.fileNames;
Arrays.sort(fileNames);
// translate classes in parallel
classTranslatorPool = new ThreadPoolExecutor(args.numThreads,
args.numThreads, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue(2 * args.numThreads, true),
new ThreadPoolExecutor.CallerRunsPolicy());
// collect translated and write to dex in order
classDefItemConsumer = Executors.newSingleThreadExecutor();
try {
if (args.mainDexListFile != null) {
// with --main-dex-list
FileNameFilter mainPassFilter = args.strictNameCheck ? new MainDexListFilter() :
new BestEffortMainDexListFilter();
// forced in main dex
for (int i = 0; i < fileNames.length; i++) {
processOne(fileNames[i], mainPassFilter);
}
if (dexOutputFutures.size() > 0) {
throw new DexException("Too many classes in " + Arguments.MAIN_DEX_LIST_OPTION
+ ", main dex capacity exceeded");
}
if (args.minimalMainDex) {
// start second pass directly in a secondary dex file.
// Wait for classes in progress to complete
synchronized(dexRotationLock) {
while(maxMethodIdsInProcess > 0 || maxFieldIdsInProcess > 0) {
try {
dexRotationLock.wait();
} catch(InterruptedException ex) {
/* ignore */
}
}
}
rotateDexFile();
}
// remaining files
for (int i = 0; i < fileNames.length; i++) {
processOne(fileNames[i], new NotFilter(mainPassFilter));
}
} else {
// without --main-dex-list
for (int i = 0; i < fileNames.length; i++) {
processOne(fileNames[i], ClassPathOpener.acceptAll);
}
}
} catch (StopProcessing ex) {
/*
* Ignore it and just let the error reporting do
* their things.
*/
}
try {
classTranslatorPool.shutdown();
classTranslatorPool.awaitTermination(600L, TimeUnit.SECONDS);
classDefItemConsumer.shutdown();
classDefItemConsumer.awaitTermination(600L, TimeUnit.SECONDS);
for (Future f : addToDexFutures) {
try {
f.get();
} catch(ExecutionException ex) {
// Catch any previously uncaught exceptions from
// class translation and adding to dex.
int count = errors.incrementAndGet();
if (count < 10) {
if (args.debug) {
context.err.println("Uncaught translation error:");
ex.getCause().printStackTrace(context.err);
} else {
context.err.println("Uncaught translation error: " + ex.getCause());
}
} else {
throw new InterruptedException("Too many errors");
}
}
}
} catch (InterruptedException ie) {
classTranslatorPool.shutdownNow();
classDefItemConsumer.shutdownNow();
throw new RuntimeException("Translation has been interrupted", ie);
} catch (Exception e) {
classTranslatorPool.shutdownNow();
classDefItemConsumer.shutdownNow();
e.printStackTrace(out);
throw new RuntimeException("Unexpected exception in translator thread.", e);
}
int errorNum = errors.get();
if (errorNum != 0) {
context.err.println(errorNum + " error" +
((errorNum == 1) ? "" : "s") + "; aborting");
return false;
}
if (args.incremental && !anyFilesProcessed) {
return true;
}
if (!(anyFilesProcessed || args.emptyOk)) {
context.err.println("no classfiles specified");
return false;
}
if (args.optimize && args.statistics) {
context.codeStatistics.dumpStatistics(context.out);
}
return true;
}
private void createDexFile() {
outputDex = new DexFile(args.dexOptions);
if (args.dumpWidth != 0) {
outputDex.setDumpWidth(args.dumpWidth);
}
}
private void rotateDexFile() {
if (outputDex != null) {
if (dexOutPool != null) {
dexOutputFutures.add(dexOutPool.submit(new DexWriter(outputDex)));
} else {
dexOutputArrays.add(writeDex(outputDex));
}
}
createDexFile();
}
/**
* Processes one pathname element.
*
* @param pathname {@code non-null;} the pathname to process. May
* be the path of a class file, a jar file, or a directory
* containing class files.
* @param filter {@code non-null;} A filter for excluding files.
*/
private void processOne(String pathname, FileNameFilter filter) {
ClassPathOpener opener;
opener = new ClassPathOpener(pathname, true, filter, new FileBytesConsumer());
if (opener.process()) {
updateStatus(true);
}
}
private void updateStatus(boolean res) {
anyFilesProcessed |= res;
}
/**
* Processes one file, which may be either a class or a resource.
*
* @param name {@code non-null;} name of the file
* @param bytes {@code non-null;} contents of the file
* @return whether processing was successful
*/
private boolean processFileBytes(String name, long lastModified, byte[] bytes) {
boolean isClass = name.endsWith(".class");
boolean isClassesDex = name.equals(DexFormat.DEX_IN_JAR_NAME);
boolean keepResources = (outputResources != null);
if (!isClass && !isClassesDex && !keepResources) {
if (args.verbose) {
context.out.println("ignored resource " + name);
}
return false;
}
if (args.verbose) {
context.out.println("processing " + name + "...");
}
String fixedName = fixPath(name);
if (isClass) {
if (keepResources && args.keepClassesInJar) {
synchronized (outputResources) {
outputResources.put(fixedName, bytes);
}
}
if (lastModified < minimumFileAge) {
return true;
}
processClass(fixedName, bytes);
// Assume that an exception may occur. Status will be updated
// asynchronously, if the class compiles without error.
return false;
} else if (isClassesDex) {
synchronized (libraryDexBuffers) {
libraryDexBuffers.add(bytes);
}
return true;
} else {
synchronized (outputResources) {
outputResources.put(fixedName, bytes);
}
return true;
}
}
/**
* Processes one classfile.
*
* @param name {@code non-null;} name of the file, clipped such that it
* should correspond to the name of the class it contains
* @param bytes {@code non-null;} contents of the file
* @return whether processing was successful
*/
private boolean processClass(String name, byte[] bytes) {
if (! args.coreLibrary) {
checkClassName(name);
}
try {
new DirectClassFileConsumer(name, bytes, null).call(
new ClassParserTask(name, bytes).call());
} catch(Exception ex) {
throw new RuntimeException("Exception parsing classes", ex);
}
return true;
}
private DirectClassFile parseClass(String name, byte[] bytes) {
DirectClassFile cf = new DirectClassFile(bytes, name,
args.cfOptions.strictNameCheck);
cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
cf.getMagic(); // triggers the actual parsing
return cf;
}
private ClassDefItem translateClass(byte[] bytes, DirectClassFile cf) {
try {
return CfTranslator.translate(context, cf, bytes, args.cfOptions,
args.dexOptions, outputDex);
} catch (ParseException ex) {
context.err.println("\ntrouble processing:");
if (args.debug) {
ex.printStackTrace(context.err);
} else {
ex.printContext(context.err);
}
}
errors.incrementAndGet();
return null;
}
private boolean addClassToDex(ClassDefItem clazz) {
synchronized (outputDex) {
outputDex.add(clazz);
}
return true;
}
/**
* Check the class name to make sure it's not a "core library"
* class. If there is a problem, this updates the error count and
* throws an exception to stop processing.
*
* @param name {@code non-null;} the fully-qualified internal-form
* class name
*/
private void checkClassName(String name) {
boolean bogus = false;
if (name.startsWith("java/")) {
bogus = true;
} else if (name.startsWith("javax/")) {
int slashAt = name.indexOf('/', 6);
if (slashAt == -1) {
// Top-level javax classes are verboten.
bogus = true;
} else {
String pkg = name.substring(6, slashAt);
bogus = (Arrays.binarySearch(JAVAX_CORE, pkg) >= 0);
}
}
if (! bogus) {
return;
}
/*
* The user is probably trying to include an entire desktop
* core library in a misguided attempt to get their application
* working. Try to help them understand what's happening.
*/
context.err.println("\ntrouble processing \"" + name + "\":\n\n" +
IN_RE_CORE_CLASSES);
errors.incrementAndGet();
throw new StopProcessing();
}
/**
* Converts {@link #outputDex} into a {@code byte[]} and do whatever
* human-oriented dumping is required.
*
* @return {@code null-ok;} the converted {@code byte[]} or {@code null}
* if there was a problem
*/
private byte[] writeDex(DexFile outputDex) {
byte[] outArray = null;
try {
try {
if (args.methodToDump != null) {
/*
* Simply dump the requested method. Note: The call
* to toDex() is required just to get the underlying
* structures ready.
*/
outputDex.toDex(null, false);
dumpMethod(outputDex, args.methodToDump, humanOutWriter);
} else {
/*
* This is the usual case: Create an output .dex file,
* and write it, dump it, etc.
*/
outArray = outputDex.toDex(humanOutWriter, args.verboseDump);
}
if (args.statistics) {
context.out.println(outputDex.getStatistics().toHuman());
}
} finally {
if (humanOutWriter != null) {
humanOutWriter.flush();
}
}
} catch (Exception ex) {
if (args.debug) {
context.err.println("\ntrouble writing output:");
ex.printStackTrace(context.err);
} else {
context.err.println("\ntrouble writing output: " +
ex.getMessage());
}
return null;
}
return outArray;
}
/**
* Creates a jar file from the resources (including dex file arrays).
*
* @param fileName {@code non-null;} name of the file
* @return whether the creation was successful
*/
private boolean createJar(String fileName) {
/*
* Make or modify the manifest (as appropriate), put the dex
* array into the resources map, and then process the entire
* resources map in a uniform manner.
*/
try {
Manifest manifest = makeManifest();
OutputStream out = openOutput(fileName);
JarOutputStream jarOut = new JarOutputStream(out, manifest);
try {
for (Map.Entry e :
outputResources.entrySet()) {
String name = e.getKey();
byte[] contents = e.getValue();
JarEntry entry = new JarEntry(name);
int length = contents.length;
if (args.verbose) {
context.out.println("writing " + name + "; size " + length + "...");
}
entry.setSize(length);
jarOut.putNextEntry(entry);
jarOut.write(contents);
jarOut.closeEntry();
}
} finally {
jarOut.finish();
jarOut.flush();
closeOutput(out);
}
} catch (Exception ex) {
if (args.debug) {
context.err.println("\ntrouble writing output:");
ex.printStackTrace(context.err);
} else {
context.err.println("\ntrouble writing output: " +
ex.getMessage());
}
return false;
}
return true;
}
/**
* Creates and returns the manifest to use for the output. This may
* modify {@link #outputResources} (removing the pre-existing manifest).
*
* @return {@code non-null;} the manifest
*/
private Manifest makeManifest() throws IOException {
byte[] manifestBytes = outputResources.get(MANIFEST_NAME);
Manifest manifest;
Attributes attribs;
if (manifestBytes == null) {
// We need to construct an entirely new manifest.
manifest = new Manifest();
attribs = manifest.getMainAttributes();
attribs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
} else {
manifest = new Manifest(new ByteArrayInputStream(manifestBytes));
attribs = manifest.getMainAttributes();
outputResources.remove(MANIFEST_NAME);
}
String createdBy = attribs.getValue(CREATED_BY);
if (createdBy == null) {
createdBy = "";
} else {
createdBy += " + ";
}
createdBy += "dx " + Version.VERSION;
attribs.put(CREATED_BY, createdBy);
attribs.putValue("Dex-Location", DexFormat.DEX_IN_JAR_NAME);
return manifest;
}
/**
* Opens and returns the named file for writing, treating "-" specially.
*
* @param name {@code non-null;} the file name
* @return {@code non-null;} the opened file
*/
private static OutputStream openOutput(String name) throws IOException {
if (name.equals("-") ||
name.startsWith("-.")) {
return out;
}
return new FileOutputStream(name);
}
/**
* Flushes and closes the given output stream, except if it happens to be
* {@link System#out} in which case this method does the flush but not
* the close. This method will also silently do nothing if given a
* {@code null} argument.
*
* @param stream {@code null-ok;} what to close
*/
private static void closeOutput(OutputStream stream) throws IOException {
if (stream == null) {
return;
}
stream.flush();
if (stream != out) {
stream.close();
}
}
/**
* Returns the "fixed" version of a given file path, suitable for
* use as a path within a {@code .jar} file and for checking
* against a classfile-internal "this class" name. This looks for
* the last instance of the substring {@code "/./"} within
* the path, and if it finds it, it takes the portion after to be
* the fixed path. If that isn't found but the path starts with
* {@code "./"}, then that prefix is removed and the rest is
* return. If neither of these is the case, this method returns
* its argument.
*
* @param path {@code non-null;} the path to "fix"
* @return {@code non-null;} the fixed version (which might be the same as
* the given {@code path})
*/
private static String fixPath(String path) {
/*
* If the path separator is \ (like on windows), we convert the
* path to a standard '/' separated path.
*/
if (File.separatorChar == '\\') {
path = path.replace('\\', '/');
}
int index = path.lastIndexOf("/./");
if (index != -1) {
return path.substring(index + 3);
}
if (path.startsWith("./")) {
return path.substring(2);
}
return path;
}
/**
* Dumps any method with the given name in the given file.
*
* @param dex {@code non-null;} the dex file
* @param fqName {@code non-null;} the fully-qualified name of the
* method(s)
* @param out {@code non-null;} where to dump to
*/
private void dumpMethod(DexFile dex, String fqName,
OutputStreamWriter out) {
boolean wildcard = fqName.endsWith("*");
int lastDot = fqName.lastIndexOf('.');
if ((lastDot <= 0) || (lastDot == (fqName.length() - 1))) {
context.err.println("bogus fully-qualified method name: " +
fqName);
return;
}
String className = fqName.substring(0, lastDot).replace('.', '/');
String methodName = fqName.substring(lastDot + 1);
ClassDefItem clazz = dex.getClassOrNull(className);
if (clazz == null) {
context.err.println("no such class: " + className);
return;
}
if (wildcard) {
methodName = methodName.substring(0, methodName.length() - 1);
}
ArrayList allMeths = clazz.getMethods();
TreeMap meths =
new TreeMap();
/*
* Figure out which methods to include in the output, and get them
* all sorted, so that the printout code is robust with respect to
* changes in the underlying order.
*/
for (EncodedMethod meth : allMeths) {
String methName = meth.getName().getString();
if ((wildcard && methName.startsWith(methodName)) ||
(!wildcard && methName.equals(methodName))) {
meths.put(meth.getRef().getNat(), meth);
}
}
if (meths.size() == 0) {
context.err.println("no such method: " + fqName);
return;
}
PrintWriter pw = new PrintWriter(out);
for (EncodedMethod meth : meths.values()) {
// TODO: Better stuff goes here, perhaps.
meth.debugPrint(pw, args.verboseDump);
/*
* The (default) source file is an attribute of the class, but
* it's useful to see it in method dumps.
*/
CstString sourceFile = clazz.getSourceFile();
if (sourceFile != null) {
pw.println(" source file: " + sourceFile.toQuoted());
}
Annotations methodAnnotations =
clazz.getMethodAnnotations(meth.getRef());
AnnotationsList parameterAnnotations =
clazz.getParameterAnnotations(meth.getRef());
if (methodAnnotations != null) {
pw.println(" method annotations:");
for (Annotation a : methodAnnotations.getAnnotations()) {
pw.println(" " + a);
}
}
if (parameterAnnotations != null) {
pw.println(" parameter annotations:");
int sz = parameterAnnotations.size();
for (int i = 0; i < sz; i++) {
pw.println(" parameter " + i);
Annotations annotations = parameterAnnotations.get(i);
for (Annotation a : annotations.getAnnotations()) {
pw.println(" " + a);
}
}
}
}
pw.flush();
}
private static class NotFilter implements FileNameFilter {
private final FileNameFilter filter;
private NotFilter(FileNameFilter filter) {
this.filter = filter;
}
@Override
public boolean accept(String path) {
return !filter.accept(path);
}
}
/**
* A quick and accurate filter for when file path can be trusted.
*/
private class MainDexListFilter implements FileNameFilter {
@Override
public boolean accept(String fullPath) {
if (fullPath.endsWith(".class")) {
String path = fixPath(fullPath);
return classesInMainDex.contains(path);
} else {
return true;
}
}
}
/**
* A best effort conservative filter for when file path can not be trusted.
*/
private class BestEffortMainDexListFilter implements FileNameFilter {
Map> map = new HashMap>();
public BestEffortMainDexListFilter() {
for (String pathOfClass : classesInMainDex) {
String normalized = fixPath(pathOfClass);
String simple = getSimpleName(normalized);
List fullPath = map.get(simple);
if (fullPath == null) {
fullPath = new ArrayList(1);
map.put(simple, fullPath);
}
fullPath.add(normalized);
}
}
@Override
public boolean accept(String path) {
if (path.endsWith(".class")) {
String normalized = fixPath(path);
String simple = getSimpleName(normalized);
List fullPaths = map.get(simple);
if (fullPaths != null) {
for (String fullPath : fullPaths) {
if (normalized.endsWith(fullPath)) {
return true;
}
}
}
return false;
} else {
return true;
}
}
private String getSimpleName(String path) {
int index = path.lastIndexOf('/');
if (index >= 0) {
return path.substring(index + 1);
} else {
return path;
}
}
}
/**
* Exception class used to halt processing prematurely.
*/
private static class StopProcessing extends RuntimeException {
// This space intentionally left blank.
}
/**
* Command-line argument parser and access.
*/
public static class Arguments {
private static final String MINIMAL_MAIN_DEX_OPTION = "--minimal-main-dex";
private static final String MAIN_DEX_LIST_OPTION = "--main-dex-list";
private static final String MULTI_DEX_OPTION = "--multi-dex";
private static final String NUM_THREADS_OPTION = "--num-threads";
private static final String INCREMENTAL_OPTION = "--incremental";
private static final String INPUT_LIST_OPTION = "--input-list";
/** whether to run in debug mode */
public boolean debug = false;
/** whether to emit warning messages */
public boolean warnings = true;
/** whether to emit high-level verbose human-oriented output */
public boolean verbose = false;
/** whether to emit verbose human-oriented output in the dump file */
public boolean verboseDump = false;
/** whether we are constructing a core library */
public boolean coreLibrary = false;
/** {@code null-ok;} particular method to dump */
public String methodToDump = null;
/** max width for columnar output */
public int dumpWidth = 0;
/** {@code null-ok;} output file name for binary file */
public String outName = null;
/** {@code null-ok;} output file name for human-oriented dump */
public String humanOutName = null;
/** whether strict file-name-vs-class-name checking should be done */
public boolean strictNameCheck = true;
/**
* whether it is okay for there to be no {@code .class} files
* to process
*/
public boolean emptyOk = false;
/**
* whether the binary output is to be a {@code .jar} file
* instead of a plain {@code .dex}
*/
public boolean jarOutput = false;
/**
* when writing a {@code .jar} file, whether to still
* keep the {@code .class} files
*/
public boolean keepClassesInJar = false;
/** how much source position info to preserve */
public int positionInfo = PositionList.LINES;
/** whether to keep local variable information */
public boolean localInfo = true;
/** whether to merge with the output dex file if it exists. */
public boolean incremental = false;
/** whether to force generation of const-string/jumbo for all indexes,
* to allow merges between dex files with many strings. */
public boolean forceJumbo = false;
/** {@code non-null} after {@link #parse}; file name arguments */
public String[] fileNames;
/** whether to do SSA/register optimization */
public boolean optimize = true;
/** Filename containg list of methods to optimize */
public String optimizeListFile = null;
/** Filename containing list of methods to NOT optimize */
public String dontOptimizeListFile = null;
/** Whether to print statistics to stdout at end of compile cycle */
public boolean statistics;
/** Options for class file transformation */
public CfOptions cfOptions;
/** Options for dex file output */
public DexOptions dexOptions;
/** number of threads to run with */
public int numThreads = 1;
/** generation of multiple dex is allowed */
public boolean multiDex = false;
/** Optional file containing a list of class files containing classes to be forced in main
* dex */
public String mainDexListFile = null;
/** Produce the smallest possible main dex. Ignored unless multiDex is true and
* mainDexListFile is specified and non empty. */
public boolean minimalMainDex = false;
/** Optional list containing inputs read in from a file. */
private List inputList = null;
private int maxNumberOfIdxPerDex = DexFormat.MAX_MEMBER_IDX + 1;
private static class ArgumentsParser {
/** The arguments to process. */
private final String[] arguments;
/** The index of the next argument to process. */
private int index;
/** The current argument being processed after a {@link #getNext()} call. */
private String current;
/** The last value of an argument processed by {@link #isArg(String)}. */
private String lastValue;
public ArgumentsParser(String[] arguments) {
this.arguments = arguments;
index = 0;
}
public String getCurrent() {
return current;
}
public String getLastValue() {
return lastValue;
}
/**
* Moves on to the next argument.
* Returns false when we ran out of arguments that start with --.
*/
public boolean getNext() {
if (index >= arguments.length) {
return false;
}
current = arguments[index];
if (current.equals("--") || !current.startsWith("--")) {
return false;
}
index++;
return true;
}
/**
* Similar to {@link #getNext()}, this moves on the to next argument.
* It does not check however whether the argument starts with --
* and thus can be used to retrieve values.
*/
private boolean getNextValue() {
if (index >= arguments.length) {
return false;
}
current = arguments[index];
index++;
return true;
}
/**
* Returns all the arguments that have not been processed yet.
*/
public String[] getRemaining() {
int n = arguments.length - index;
String[] remaining = new String[n];
if (n > 0) {
System.arraycopy(arguments, index, remaining, 0, n);
}
return remaining;
}
/**
* Checks the current argument against the given prefix.
* If prefix is in the form '--name=', an extra value is expected.
* The argument can then be in the form '--name=value' or as a 2-argument
* form '--name value'.
*/
public boolean isArg(String prefix) {
int n = prefix.length();
if (n > 0 && prefix.charAt(n-1) == '=') {
// Argument accepts a value. Capture it.
if (current.startsWith(prefix)) {
// Argument is in the form --name=value, split the value out
lastValue = current.substring(n);
return true;
} else {
// Check whether we have "--name value" as 2 arguments
prefix = prefix.substring(0, n-1);
if (current.equals(prefix)) {
if (getNextValue()) {
lastValue = current;
return true;
} else {
err.println("Missing value after parameter " + prefix);
throw new UsageException();
}
}
return false;
}
} else {
// Argument does not accept a value.
return current.equals(prefix);
}
}
}
/**
* Parses the given command-line arguments.
*
* @param args {@code non-null;} the arguments
* @param context
*/
public void parse(String[] args, DxContext context) {
ArgumentsParser parser = new ArgumentsParser(args);
boolean outputIsDirectory = false;
boolean outputIsDirectDex = false;
while(parser.getNext()) {
if (parser.isArg("--debug")) {
debug = true;
} else if (parser.isArg("--no-warning")) {
warnings = false;
} else if (parser.isArg("--verbose")) {
verbose = true;
} else if (parser.isArg("--verbose-dump")) {
verboseDump = true;
} else if (parser.isArg("--no-files")) {
emptyOk = true;
} else if (parser.isArg("--no-optimize")) {
optimize = false;
} else if (parser.isArg("--no-strict")) {
strictNameCheck = false;
} else if (parser.isArg("--core-library")) {
coreLibrary = true;
} else if (parser.isArg("--statistics")) {
statistics = true;
} else if (parser.isArg("--optimize-list=")) {
if (dontOptimizeListFile != null) {
err.println("--optimize-list and "
+ "--no-optimize-list are incompatible.");
throw new UsageException();
}
optimize = true;
optimizeListFile = parser.getLastValue();
} else if (parser.isArg("--no-optimize-list=")) {
if (dontOptimizeListFile != null) {
err.println("--optimize-list and "
+ "--no-optimize-list are incompatible.");
throw new UsageException();
}
optimize = true;
dontOptimizeListFile = parser.getLastValue();
} else if (parser.isArg("--keep-classes")) {
keepClassesInJar = true;
} else if (parser.isArg("--output=")) {
outName = parser.getLastValue();
if (new File(outName).isDirectory()) {
jarOutput = false;
outputIsDirectory = true;
} else if (FileUtils.hasArchiveSuffix(outName)) {
jarOutput = true;
} else if (outName.endsWith(".dex") ||
outName.equals("-")) {
jarOutput = false;
outputIsDirectDex = true;
} else {
err.println("unknown output extension: " +
outName);
throw new UsageException();
}
} else if (parser.isArg("--dump-to=")) {
humanOutName = parser.getLastValue();
} else if (parser.isArg("--dump-width=")) {
dumpWidth = Integer.parseInt(parser.getLastValue());
} else if (parser.isArg("--dump-method=")) {
methodToDump = parser.getLastValue();
jarOutput = false;
} else if (parser.isArg("--positions=")) {
String pstr = parser.getLastValue().intern();
if (pstr == "none") {
positionInfo = PositionList.NONE;
} else if (pstr == "important") {
positionInfo = PositionList.IMPORTANT;
} else if (pstr == "lines") {
positionInfo = PositionList.LINES;
} else {
err.println("unknown positions option: " +
pstr);
throw new UsageException();
}
} else if (parser.isArg("--no-locals")) {
localInfo = false;
} else if (parser.isArg(NUM_THREADS_OPTION + "=")) {
numThreads = Integer.parseInt(parser.getLastValue());
} else if (parser.isArg(INCREMENTAL_OPTION)) {
incremental = true;
} else if (parser.isArg("--force-jumbo")) {
forceJumbo = true;
} else if (parser.isArg(MULTI_DEX_OPTION)) {
multiDex = true;
} else if (parser.isArg(MAIN_DEX_LIST_OPTION + "=")) {
mainDexListFile = parser.getLastValue();
} else if (parser.isArg(MINIMAL_MAIN_DEX_OPTION)) {
minimalMainDex = true;
} else if (parser.isArg("--set-max-idx-number=")) { // undocumented test option
maxNumberOfIdxPerDex = Integer.parseInt(parser.getLastValue());
} else if(parser.isArg(INPUT_LIST_OPTION + "=")) {
File inputListFile = new File(parser.getLastValue());
try{
inputList = new ArrayList();
readPathsFromFile(inputListFile.getAbsolutePath(), inputList);
} catch(IOException e) {
err.println(
"Unable to read input list file: " + inputListFile.getName());
// problem reading the file so we should halt execution
throw new UsageException();
}
} else {
err.println("unknown option: " + parser.getCurrent());
throw new UsageException();
}
}
fileNames = parser.getRemaining();
if(inputList != null && !inputList.isEmpty()) {
// append the file names to the end of the input list
inputList.addAll(Arrays.asList(fileNames));
fileNames = inputList.toArray(new String[inputList.size()]);
}
if (fileNames.length == 0) {
if (!emptyOk) {
err.println("no input files specified");
throw new UsageException();
}
} else if (emptyOk) {
out.println("ignoring input files");
}
if ((humanOutName == null) && (methodToDump != null)) {
humanOutName = "-";
}
if (mainDexListFile != null && !multiDex) {
err.println(MAIN_DEX_LIST_OPTION + " is only supported in combination with "
+ MULTI_DEX_OPTION);
throw new UsageException();
}
if (minimalMainDex && (mainDexListFile == null || !multiDex)) {
err.println(MINIMAL_MAIN_DEX_OPTION + " is only supported in combination with "
+ MULTI_DEX_OPTION + " and " + MAIN_DEX_LIST_OPTION);
throw new UsageException();
}
if (multiDex && incremental) {
err.println(INCREMENTAL_OPTION + " is not supported with "
+ MULTI_DEX_OPTION);
throw new UsageException();
}
if (multiDex && outputIsDirectDex) {
err.println("Unsupported output \"" + outName +"\". " + MULTI_DEX_OPTION +
" supports only archive or directory output");
throw new UsageException();
}
if (outputIsDirectory && !multiDex) {
outName = new File(outName, DexFormat.DEX_IN_JAR_NAME).getPath();
}
makeOptionsObjects(context);
}
/**
* Copies relevent arguments over into CfOptions and
* DexOptions instances.
*/
private void makeOptionsObjects(DxContext context) {
cfOptions = new CfOptions();
cfOptions.positionInfo = positionInfo;
cfOptions.localInfo = localInfo;
cfOptions.strictNameCheck = strictNameCheck;
cfOptions.optimize = optimize;
cfOptions.optimizeListFile = optimizeListFile;
cfOptions.dontOptimizeListFile = dontOptimizeListFile;
cfOptions.statistics = statistics;
if (warnings) {
cfOptions.warn = context.err;
} else {
cfOptions.warn = context.noop;
}
dexOptions = new DexOptions();
dexOptions.forceJumbo = forceJumbo;
}
}
/**
* Callback class for processing input file bytes, produced by the
* ClassPathOpener.
*/
private class FileBytesConsumer implements ClassPathOpener.Consumer {
@Override
public boolean processFileBytes(String name, long lastModified,
byte[] bytes) {
return Main.this.processFileBytes(name, lastModified, bytes);
}
@Override
public void onException(Exception ex) {
if (ex instanceof StopProcessing) {
throw (StopProcessing) ex;
} else if (ex instanceof SimException) {
context.err.println("\nEXCEPTION FROM SIMULATION:");
context.err.println(ex.getMessage() + "\n");
context.err.println(((SimException) ex).getContext());
} else {
context.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
ex.printStackTrace(context.err);
}
errors.incrementAndGet();
}
@Override
public void onProcessArchiveStart(File file) {
if (args.verbose) {
context.out.println("processing archive " + file + "...");
}
}
}
/** Callable helper class to parse class bytes. */
private class ClassParserTask implements Callable {
String name;
byte[] bytes;
private ClassParserTask(String name, byte[] bytes) {
this.name = name;
this.bytes = bytes;
}
@Override
public DirectClassFile call() throws Exception {
DirectClassFile cf = parseClass(name, bytes);
return cf;
}
}
/**
* Callable helper class used to sequentially collect the results of
* the (optionally parallel) translation phase, in correct input file order.
* This class is also responsible for coordinating dex file rotation
* with the ClassDefItemConsumer class.
* We maintain invariant that the number of indices used in the current
* dex file plus the max number of indices required by classes passed to
* the translation phase and not yet added to the dex file, is less than
* or equal to the dex file limit.
* For each parsed file, we estimate the maximum number of indices it may
* require. If passing the file to the translation phase would invalidate
* the invariant, we wait, until the next class is added to the dex file,
* and then reevaluate the invariant. If there are no further classes in
* the translation phase, we rotate the dex file.
*/
private class DirectClassFileConsumer implements Callable {
String name;
byte[] bytes;
Future dcff;
private DirectClassFileConsumer(String name, byte[] bytes,
Future dcff) {
this.name = name;
this.bytes = bytes;
this.dcff = dcff;
}
@Override
public Boolean call() throws Exception {
DirectClassFile cf = dcff.get();
return call(cf);
}
private Boolean call(DirectClassFile cf) {
int maxMethodIdsInClass = 0;
int maxFieldIdsInClass = 0;
if (args.multiDex) {
// Calculate max number of indices this class will add to the
// dex file.
// The possibility of overloading means that we can't easily
// know how many constant are needed for declared methods and
// fields. We therefore make the simplifying assumption that
// all constants are external method or field references.
int constantPoolSize = cf.getConstantPool().size();
maxMethodIdsInClass = constantPoolSize + cf.getMethods().size()
+ MAX_METHOD_ADDED_DURING_DEX_CREATION;
maxFieldIdsInClass = constantPoolSize + cf.getFields().size()
+ MAX_FIELD_ADDED_DURING_DEX_CREATION;
synchronized(dexRotationLock) {
int numMethodIds;
int numFieldIds;
// Number of indices used in current dex file.
synchronized(outputDex) {
numMethodIds = outputDex.getMethodIds().items().size();
numFieldIds = outputDex.getFieldIds().items().size();
}
// Wait until we're sure this class will fit in the current
// dex file.
while(((numMethodIds + maxMethodIdsInClass + maxMethodIdsInProcess
> args.maxNumberOfIdxPerDex) ||
(numFieldIds + maxFieldIdsInClass + maxFieldIdsInProcess
> args.maxNumberOfIdxPerDex))) {
if (maxMethodIdsInProcess > 0 || maxFieldIdsInProcess > 0) {
// There are classes in the translation phase that
// have not yet been added to the dex file, so we
// wait for the next class to complete.
try {
dexRotationLock.wait();
} catch(InterruptedException ex) {
/* ignore */
}
} else if (outputDex.getClassDefs().items().size() > 0) {
// There are no further classes in the translation
// phase, and we have a full dex file. Rotate!
rotateDexFile();
} else {
// The estimated number of indices is too large for
// an empty dex file. We proceed hoping the actual
// number of indices needed will fit.
break;
}
synchronized(outputDex) {
numMethodIds = outputDex.getMethodIds().items().size();
numFieldIds = outputDex.getFieldIds().items().size();
}
}
// Add our estimate to the total estimate for
// classes under translation.
maxMethodIdsInProcess += maxMethodIdsInClass;
maxFieldIdsInProcess += maxFieldIdsInClass;
}
}
// Submit class to translation phase.
Future cdif = classTranslatorPool.submit(
new ClassTranslatorTask(name, bytes, cf));
Future res = classDefItemConsumer.submit(new ClassDefItemConsumer(
name, cdif, maxMethodIdsInClass, maxFieldIdsInClass));
addToDexFutures.add(res);
return true;
}
}
/** Callable helper class to translate classes in parallel */
private class ClassTranslatorTask implements Callable {
String name;
byte[] bytes;
DirectClassFile classFile;
private ClassTranslatorTask(String name, byte[] bytes,
DirectClassFile classFile) {
this.name = name;
this.bytes = bytes;
this.classFile = classFile;
}
@Override
public ClassDefItem call() {
ClassDefItem clazz = translateClass(bytes, classFile);
return clazz;
}
}
/**
* Callable helper class used to collect the results of
* the parallel translation phase, adding the translated classes to
* the current dex file in correct (deterministic) file order.
* This class is also responsible for coordinating dex file rotation
* with the DirectClassFileConsumer class.
*/
private class ClassDefItemConsumer implements Callable {
String name;
Future futureClazz;
int maxMethodIdsInClass;
int maxFieldIdsInClass;
private ClassDefItemConsumer(String name, Future futureClazz,
int maxMethodIdsInClass, int maxFieldIdsInClass) {
this.name = name;
this.futureClazz = futureClazz;
this.maxMethodIdsInClass = maxMethodIdsInClass;
this.maxFieldIdsInClass = maxFieldIdsInClass;
}
@Override
public Boolean call() throws Exception {
try {
ClassDefItem clazz = futureClazz.get();
if (clazz != null) {
addClassToDex(clazz);
updateStatus(true);
}
return true;
} catch(ExecutionException ex) {
// Rethrow previously uncaught translation exceptions.
// These, as well as any exceptions from addClassToDex,
// are handled and reported in processAllFiles().
Throwable t = ex.getCause();
throw (t instanceof Exception) ? (Exception) t : ex;
} finally {
if (args.multiDex) {
// Having added our actual indicies to the dex file,
// we subtract our original estimate from the total estimate,
// and signal the translation phase, which may be paused
// waiting to determine if more classes can be added to the
// current dex file, or if a new dex file must be created.
synchronized(dexRotationLock) {
maxMethodIdsInProcess -= maxMethodIdsInClass;
maxFieldIdsInProcess -= maxFieldIdsInClass;
dexRotationLock.notifyAll();
}
}
}
}
}
/** Callable helper class to convert dex files in worker threads */
private class DexWriter implements Callable {
private DexFile dexFile;
private DexWriter(DexFile dexFile) {
this.dexFile = dexFile;
}
@Override
public byte[] call() throws IOException {
return writeDex(dexFile);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy