
soot.toDex.DexPrinter Maven / Gradle / Ivy
Show all versions of soot Show documentation
package soot.toDex;
/*-
* #%L
* Soot - a J*va Optimization Framework
* %%
* Copyright (C) 1997 - 2018 Raja Vallée-Rai and others
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 2.1 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import org.jf.dexlib2.AnnotationVisibility;
import org.jf.dexlib2.Opcode;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.builder.BuilderInstruction;
import org.jf.dexlib2.builder.BuilderOffsetInstruction;
import org.jf.dexlib2.builder.Label;
import org.jf.dexlib2.builder.MethodImplementationBuilder;
import org.jf.dexlib2.iface.Annotation;
import org.jf.dexlib2.iface.AnnotationElement;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.ExceptionHandler;
import org.jf.dexlib2.iface.Field;
import org.jf.dexlib2.iface.Method;
import org.jf.dexlib2.iface.MethodImplementation;
import org.jf.dexlib2.iface.MethodParameter;
import org.jf.dexlib2.iface.instruction.Instruction;
import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.dexlib2.iface.reference.MethodReference;
import org.jf.dexlib2.iface.reference.StringReference;
import org.jf.dexlib2.iface.reference.TypeReference;
import org.jf.dexlib2.iface.value.EncodedValue;
import org.jf.dexlib2.immutable.ImmutableAnnotation;
import org.jf.dexlib2.immutable.ImmutableAnnotationElement;
import org.jf.dexlib2.immutable.ImmutableClassDef;
import org.jf.dexlib2.immutable.ImmutableExceptionHandler;
import org.jf.dexlib2.immutable.ImmutableField;
import org.jf.dexlib2.immutable.ImmutableMethod;
import org.jf.dexlib2.immutable.ImmutableMethodParameter;
import org.jf.dexlib2.immutable.reference.ImmutableFieldReference;
import org.jf.dexlib2.immutable.reference.ImmutableMethodReference;
import org.jf.dexlib2.immutable.reference.ImmutableStringReference;
import org.jf.dexlib2.immutable.reference.ImmutableTypeReference;
import org.jf.dexlib2.immutable.value.ImmutableAnnotationEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableArrayEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableBooleanEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableByteEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableCharEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableDoubleEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableEnumEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableFieldEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableFloatEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableIntEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableLongEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableMethodEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableNullEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableShortEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableStringEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableTypeEncodedValue;
import org.jf.dexlib2.writer.builder.BuilderEncodedValues;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import soot.Body;
import soot.BooleanType;
import soot.ByteType;
import soot.CharType;
import soot.CompilationDeathException;
import soot.IntType;
import soot.Local;
import soot.PackManager;
import soot.RefType;
import soot.Scene;
import soot.ShortType;
import soot.SootClass;
import soot.SootField;
import soot.SootFieldRef;
import soot.SootMethod;
import soot.SootMethodRef;
import soot.SourceLocator;
import soot.Trap;
import soot.Type;
import soot.Unit;
import soot.dexpler.DexInnerClassParser;
import soot.dexpler.DexType;
import soot.dexpler.Util;
import soot.jimple.ClassConstant;
import soot.jimple.IdentityStmt;
import soot.jimple.Jimple;
import soot.jimple.MonitorStmt;
import soot.jimple.NopStmt;
import soot.jimple.Stmt;
import soot.jimple.toolkits.scalar.EmptySwitchEliminator;
import soot.options.Options;
import soot.tagkit.AbstractHost;
import soot.tagkit.AnnotationAnnotationElem;
import soot.tagkit.AnnotationArrayElem;
import soot.tagkit.AnnotationBooleanElem;
import soot.tagkit.AnnotationClassElem;
import soot.tagkit.AnnotationConstants;
import soot.tagkit.AnnotationDefaultTag;
import soot.tagkit.AnnotationDoubleElem;
import soot.tagkit.AnnotationElem;
import soot.tagkit.AnnotationEnumElem;
import soot.tagkit.AnnotationFloatElem;
import soot.tagkit.AnnotationIntElem;
import soot.tagkit.AnnotationLongElem;
import soot.tagkit.AnnotationStringElem;
import soot.tagkit.AnnotationTag;
import soot.tagkit.ConstantValueTag;
import soot.tagkit.DoubleConstantValueTag;
import soot.tagkit.EnclosingMethodTag;
import soot.tagkit.FloatConstantValueTag;
import soot.tagkit.InnerClassAttribute;
import soot.tagkit.InnerClassTag;
import soot.tagkit.IntegerConstantValueTag;
import soot.tagkit.LineNumberTag;
import soot.tagkit.LongConstantValueTag;
import soot.tagkit.ParamNamesTag;
import soot.tagkit.SignatureTag;
import soot.tagkit.SourceFileTag;
import soot.tagkit.StringConstantValueTag;
import soot.tagkit.Tag;
import soot.tagkit.VisibilityAnnotationTag;
import soot.tagkit.VisibilityParameterAnnotationTag;
import soot.toDex.instructions.Insn;
import soot.toDex.instructions.Insn10t;
import soot.toDex.instructions.Insn30t;
import soot.toDex.instructions.InsnWithOffset;
import soot.util.Chain;
/**
*
* Creates {@code apk} or {@code jar} file with compiled {@code dex} classes. Main entry point for the "dex" output format.
*
*
* Use {@link #add(SootClass)} to add classes that should be printed as dex output and {@link #print()} to finally print the
* classes.
*
*
* If the printer has found the original {@code APK} of an added class (via {@link SourceLocator#dexClassIndex()}), the files
* in the {@code APK} are copied to a new one, replacing it's {@code classes.dex} and excluding the signature files. Note
* that you have to sign and align the APK yourself, with jarsigner and zipalign, respectively.
*
*
* If {@link Options#output_jar} flag is set, the printer produces {@code JAR} file.
*
*
* If there is no original {@code APK} and {@link Options#output_jar} flag is not set the printer just emits a
* {@code classes.dex}.
*
*
* @see jarsigner documentation
* @see zipalign documentation
*/
public class DexPrinter {
private static final Logger LOGGER = LoggerFactory.getLogger(DexPrinter.class);
public static final Pattern SIGNATURE_FILE_PATTERN = Pattern.compile("META-INF/[^/]+(\\.SF|\\.DSA|\\.RSA|\\.EC)$");
protected MultiDexBuilder dexBuilder;
protected File originalApk;
public DexPrinter() {
dexBuilder = createDexBuilder();
}
/**
* Creates the {@link MultiDexBuilder} that shall be used for creating potentially multiple dex files. This method makes
* sure that users of Soot can overwrite the {@link MultiDexBuilder} with custom strategies.
*
* @return The new {@link MultiDexBuilder}
*/
protected MultiDexBuilder createDexBuilder() {
// we have to create a dex file with the minimum sdk level set. If we build with the target sdk version,
// we break the backwards compatibility of the app.
Scene.AndroidVersionInfo androidSDKVersionInfo = Scene.v().getAndroidSDKVersionInfo();
int apiLevel;
if (androidSDKVersionInfo == null) {
apiLevel = Scene.v().getAndroidAPIVersion();
} else {
apiLevel = Math.min(androidSDKVersionInfo.minSdkVersion, androidSDKVersionInfo.sdkTargetVersion);
}
return new MultiDexBuilder(Opcodes.forApi(apiLevel));
}
private static boolean isSignatureFile(String fileName) {
return SIGNATURE_FILE_PATTERN.matcher(fileName).matches();
}
/**
* Converts Jimple visibility to Dexlib visibility
*
* @param visibility
* Jimple visibility
* @return Dexlib visibility
*/
private static int getVisibility(int visibility) {
if (visibility == AnnotationConstants.RUNTIME_VISIBLE) {
return AnnotationVisibility.RUNTIME;
}
if (visibility == AnnotationConstants.RUNTIME_INVISIBLE) {
return AnnotationVisibility.SYSTEM;
}
if (visibility == AnnotationConstants.SOURCE_VISIBLE) {
return AnnotationVisibility.BUILD;
}
throw new RuntimeException("Unknown annotation visibility: '" + visibility + "'");
}
protected static FieldReference toFieldReference(SootField f) {
FieldReference fieldRef = new ImmutableFieldReference(SootToDexUtils.getDexClassName(f.getDeclaringClass().getName()),
f.getName(), SootToDexUtils.getDexTypeDescriptor(f.getType()));
return fieldRef;
}
protected static FieldReference toFieldReference(SootFieldRef ref) {
FieldReference fieldRef = new ImmutableFieldReference(SootToDexUtils.getDexClassName(ref.declaringClass().getName()),
ref.name(), SootToDexUtils.getDexTypeDescriptor(ref.type()));
return fieldRef;
}
protected static MethodReference toMethodReference(SootMethodRef m) {
List parameters = new ArrayList();
for (Type t : m.getParameterTypes()) {
parameters.add(SootToDexUtils.getDexTypeDescriptor(t));
}
MethodReference methodRef = new ImmutableMethodReference(SootToDexUtils.getDexClassName(m.getDeclaringClass().getName()),
m.getName(), parameters, SootToDexUtils.getDexTypeDescriptor(m.getReturnType()));
return methodRef;
}
public static TypeReference toTypeReference(Type t) {
ImmutableTypeReference tRef = new ImmutableTypeReference(SootToDexUtils.getDexTypeDescriptor(t));
return tRef;
}
private void printZip() throws IOException {
try (final ZipOutputStream outputZip = getZipOutputStream()) {
LOGGER.info("Do not forget to sign the .apk file with jarsigner and to align it with zipalign");
if (originalApk != null) {
// Copy over additional resources from original APK
try (ZipFile original = new ZipFile(originalApk)) {
copyAllButClassesDexAndSigFiles(original, outputZip);
}
}
// put our dex files into the zip archive
final Path tempPath = Files.createTempDirectory(Long.toString(System.nanoTime()));
final List files = dexBuilder.writeTo(tempPath.toString());
for (File file : files) {
try (InputStream is = Files.newInputStream(file.toPath())) {
outputZip.putNextEntry(new ZipEntry(file.getName()));
final byte[] buffer = new byte[16_384];
int read = 0;
while ((read = is.read(buffer)) > 0) {
outputZip.write(buffer, 0, read);
}
outputZip.closeEntry();
}
}
if (Options.v().output_jar()) {
// if we create JAR file, MANIFEST.MF is preferred
addManifest(outputZip, files);
}
// remove tmp dir and contents
Files.walkFileTree(tempPath, new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
}
private ZipOutputStream getZipOutputStream() throws IOException {
if (Options.v().output_jar()) {
LOGGER.info("Writing JAR to \"{}\"", Options.v().output_dir());
return PackManager.v().getJarFile();
}
final String name = originalApk == null ? "out.apk" : originalApk.getName();
if (originalApk == null) {
LOGGER.warn("Setting output file name to \"{}\" as original APK has not been found.", name);
}
final Path outputFile = Paths.get(SourceLocator.v().getOutputDir(), name);
if (Files.exists(outputFile, LinkOption.NOFOLLOW_LINKS)) {
if (!Options.v().force_overwrite()) {
throw new CompilationDeathException("Output file \"" + outputFile + "\" exists. Not overwriting.");
}
try {
Files.delete(outputFile);
} catch (IOException exception) {
throw new IllegalStateException("Removing \"" + outputFile + "\" failed. Not writing out anything.", exception);
}
}
LOGGER.info("Writing APK to \"{}\".", outputFile);
return new ZipOutputStream(Files.newOutputStream(outputFile, StandardOpenOption.CREATE_NEW));
}
private void copyAllButClassesDexAndSigFiles(ZipFile source, ZipOutputStream destination) throws IOException {
Enumeration extends ZipEntry> sourceEntries = source.entries();
while (sourceEntries.hasMoreElements()) {
ZipEntry sourceEntry = sourceEntries.nextElement();
String sourceEntryName = sourceEntry.getName();
if (sourceEntryName.endsWith(".dex") || isSignatureFile(sourceEntryName)) {
continue;
}
// separate ZipEntry avoids compression problems due to encodings
ZipEntry destinationEntry = new ZipEntry(sourceEntryName);
// use the same compression method as the original (certain files
// are stored, not compressed)
destinationEntry.setMethod(sourceEntry.getMethod());
// copy other necessary fields for STORE method
destinationEntry.setSize(sourceEntry.getSize());
destinationEntry.setCrc(sourceEntry.getCrc());
// finally craft new entry
destination.putNextEntry(destinationEntry);
InputStream zipEntryInput = source.getInputStream(sourceEntry);
byte[] buffer = new byte[2048];
int bytesRead = zipEntryInput.read(buffer);
while (bytesRead > 0) {
destination.write(buffer, 0, bytesRead);
bytesRead = zipEntryInput.read(buffer);
}
zipEntryInput.close();
}
}
private void addManifest(ZipOutputStream destination, Collection dexFiles) throws IOException {
final Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
manifest.getMainAttributes().put(new Attributes.Name("Created-By"), "Soot Dex Printer");
if (dexFiles != null && !dexFiles.isEmpty()) {
manifest.getMainAttributes().put(new Attributes.Name("Dex-Location"),
dexFiles.stream().map(File::getName).collect(Collectors.joining(" ")));
}
final ZipEntry manifestEntry = new ZipEntry(JarFile.MANIFEST_NAME);
destination.putNextEntry(manifestEntry);
manifest.write(new BufferedOutputStream(destination));
destination.closeEntry();
}
/**
* Encodes Annotations Elements from Jimple to Dexlib
*
* @param elem
* Jimple Element
* @return Dexlib encoded element
*/
private EncodedValue buildEncodedValueForAnnotation(AnnotationElem elem) {
switch (elem.getKind()) {
case 'Z': {
if (elem instanceof AnnotationIntElem) {
AnnotationIntElem e = (AnnotationIntElem) elem;
if (e.getValue() == 0) {
return ImmutableBooleanEncodedValue.FALSE_VALUE;
} else if (e.getValue() == 1) {
return ImmutableBooleanEncodedValue.TRUE_VALUE;
} else {
throw new RuntimeException("error: boolean value from int with value != 0 or 1.");
}
} else if (elem instanceof AnnotationBooleanElem) {
AnnotationBooleanElem e = (AnnotationBooleanElem) elem;
if (e.getValue()) {
return ImmutableBooleanEncodedValue.TRUE_VALUE;
} else {
return ImmutableBooleanEncodedValue.FALSE_VALUE;
}
} else {
throw new RuntimeException("Annotation type incompatible with target type boolean");
}
}
case 'S': {
AnnotationIntElem e = (AnnotationIntElem) elem;
return new ImmutableShortEncodedValue((short) e.getValue());
}
case 'B': {
AnnotationIntElem e = (AnnotationIntElem) elem;
return new ImmutableByteEncodedValue((byte) e.getValue());
}
case 'C': {
AnnotationIntElem e = (AnnotationIntElem) elem;
return new ImmutableCharEncodedValue((char) e.getValue());
}
case 'I': {
AnnotationIntElem e = (AnnotationIntElem) elem;
return new ImmutableIntEncodedValue(e.getValue());
}
case 'J': {
AnnotationLongElem e = (AnnotationLongElem) elem;
return new ImmutableLongEncodedValue(e.getValue());
}
case 'F': {
AnnotationFloatElem e = (AnnotationFloatElem) elem;
return new ImmutableFloatEncodedValue(e.getValue());
}
case 'D': {
AnnotationDoubleElem e = (AnnotationDoubleElem) elem;
return new ImmutableDoubleEncodedValue(e.getValue());
}
case 's': {
AnnotationStringElem e = (AnnotationStringElem) elem;
return new ImmutableStringEncodedValue(e.getValue());
}
case 'e': {
AnnotationEnumElem e = (AnnotationEnumElem) elem;
String classT = SootToDexUtils.getDexClassName(e.getTypeName());
String fieldT = classT;
return new ImmutableEnumEncodedValue(new ImmutableFieldReference(classT, e.getConstantName(), fieldT));
}
case 'c': {
AnnotationClassElem e = (AnnotationClassElem) elem;
return new ImmutableTypeEncodedValue(e.getDesc());
}
case '[': {
AnnotationArrayElem e = (AnnotationArrayElem) elem;
List values = new ArrayList();
for (int i = 0; i < e.getNumValues(); i++) {
EncodedValue val = buildEncodedValueForAnnotation(e.getValueAt(i));
values.add(val);
}
return new ImmutableArrayEncodedValue(values);
}
case '@': {
AnnotationAnnotationElem e = (AnnotationAnnotationElem) elem;
Set alreadyWritten = new HashSet();
List elements = null;
if (!e.getValue().getElems().isEmpty()) {
elements = new ArrayList();
for (AnnotationElem ae : e.getValue().getElems()) {
if (!alreadyWritten.add(ae.getName())) {
throw new RuntimeException("Duplicate annotation attribute: " + ae.getName());
}
AnnotationElement element = new ImmutableAnnotationElement(ae.getName(), buildEncodedValueForAnnotation(ae));
elements.add(element);
}
}
return new ImmutableAnnotationEncodedValue(SootToDexUtils.getDexClassName(e.getValue().getType()), elements);
}
case 'f': { // field (Dalvik specific?)
AnnotationStringElem e = (AnnotationStringElem) elem;
String fSig = e.getValue();
String[] sp = fSig.split(" ");
String classString = SootToDexUtils.getDexClassName(sp[0].split(":")[0]);
if (classString.isEmpty()) {
throw new RuntimeException("Empty class name in annotation");
}
String typeString = sp[1];
if (typeString.isEmpty()) {
throw new RuntimeException("Empty type string in annotation");
}
String fieldName = sp[2];
return new ImmutableFieldEncodedValue(new ImmutableFieldReference(classString, fieldName, typeString));
}
case 'M': { // method (Dalvik specific?)
AnnotationStringElem e = (AnnotationStringElem) elem;
String[] sp = e.getValue().split(" ");
String classString = SootToDexUtils.getDexClassName(sp[0].split(":")[0]);
if (classString.isEmpty()) {
throw new RuntimeException("Empty class name in annotation");
}
String returnType = sp[1];
String[] sp2 = sp[2].split("\\(");
String methodNameString = sp2[0];
String parameters = sp2[1].replaceAll("\\)", "");
List paramTypeList = parameters.isEmpty() ? null : Arrays.asList(parameters.split(","));
return new ImmutableMethodEncodedValue(
new ImmutableMethodReference(classString, methodNameString, paramTypeList, returnType));
}
case 'N': { // null (Dalvik specific?)
return ImmutableNullEncodedValue.INSTANCE;
}
default:
throw new RuntimeException("Unknown Elem Attr Kind: " + elem.getKind());
}
}
private EncodedValue makeConstantItem(SootField sf, Tag t) {
if (!(t instanceof ConstantValueTag)) {
throw new RuntimeException("error: t not ConstantValueTag.");
}
if (t instanceof IntegerConstantValueTag) {
Type sft = sf.getType();
IntegerConstantValueTag i = (IntegerConstantValueTag) t;
if (sft instanceof BooleanType) {
int v = i.getIntValue();
if (v == 0) {
return ImmutableBooleanEncodedValue.FALSE_VALUE;
} else if (v == 1) {
return ImmutableBooleanEncodedValue.TRUE_VALUE;
} else {
throw new RuntimeException("error: boolean value from int with value != 0 or 1.");
}
} else if (sft instanceof CharType) {
return new ImmutableCharEncodedValue((char) i.getIntValue());
} else if (sft instanceof ByteType) {
return new ImmutableByteEncodedValue((byte) i.getIntValue());
} else if (sft instanceof IntType) {
return new ImmutableIntEncodedValue(i.getIntValue());
} else if (sft instanceof ShortType) {
return new ImmutableShortEncodedValue((short) i.getIntValue());
} else {
throw new RuntimeException("error: unexpected constant tag type: " + t + " for field " + sf);
}
} else if (t instanceof LongConstantValueTag) {
LongConstantValueTag l = (LongConstantValueTag) t;
return new ImmutableLongEncodedValue(l.getLongValue());
} else if (t instanceof DoubleConstantValueTag) {
DoubleConstantValueTag d = (DoubleConstantValueTag) t;
return new ImmutableDoubleEncodedValue(d.getDoubleValue());
} else if (t instanceof FloatConstantValueTag) {
FloatConstantValueTag f = (FloatConstantValueTag) t;
return new ImmutableFloatEncodedValue(f.getFloatValue());
} else if (t instanceof StringConstantValueTag) {
StringConstantValueTag s = (StringConstantValueTag) t;
if (sf.getType().equals(RefType.v("java.lang.String"))) {
return new ImmutableStringEncodedValue(s.getStringValue());
} else {
// Not supported in Dalvik
// See
// https://android.googlesource.com/platform/dalvik.git/+/android-4.3_r3/vm/oo/Class.cpp
// Results in "Bogus static initialization"
return null;
}
} else {
throw new RuntimeException("Unexpected constant type");
}
}
private void addAsClassDefItem(SootClass c) {
// add source file tag if any
String sourceFile = null;
if (c.hasTag("SourceFileTag")) {
SourceFileTag sft = (SourceFileTag) c.getTag("SourceFileTag");
sourceFile = sft.getSourceFile();
}
String classType = SootToDexUtils.getDexTypeDescriptor(c.getType());
int accessFlags = c.getModifiers();
String superClass = c.hasSuperclass() ? SootToDexUtils.getDexTypeDescriptor(c.getSuperclass().getType()) : null;
List interfaces = null;
if (!c.getInterfaces().isEmpty()) {
interfaces = new ArrayList();
for (SootClass ifc : c.getInterfaces()) {
interfaces.add(SootToDexUtils.getDexTypeDescriptor(ifc.getType()));
}
}
List fields = null;
if (!c.getFields().isEmpty()) {
fields = new ArrayList();
for (SootField f : c.getFields()) {
// We do not want to write out phantom fields
if (f.isPhantom()) {
continue;
}
// Look for a static initializer
EncodedValue staticInit = null;
for (Tag t : f.getTags()) {
if (t instanceof ConstantValueTag) {
if (staticInit != null) {
LOGGER.warn("More than one constant tag for field \"{}\": \"{}\"", f, t);
} else {
staticInit = makeConstantItem(f, t);
}
}
}
if (staticInit == null) {
staticInit = BuilderEncodedValues.defaultValueForType(SootToDexUtils.getDexTypeDescriptor(f.getType()));
}
// Build field annotations
Set fieldAnnotations = buildFieldAnnotations(f);
ImmutableField field = new ImmutableField(classType, f.getName(), SootToDexUtils.getDexTypeDescriptor(f.getType()),
f.getModifiers(), staticInit, fieldAnnotations, null);
fields.add(field);
}
}
Collection methods = toMethods(c);
ClassDef classDef = new ImmutableClassDef(classType, accessFlags, superClass, interfaces, sourceFile,
buildClassAnnotations(c), fields, methods);
dexBuilder.internClass(classDef);
}
private Set buildClassAnnotations(SootClass c) {
Set skipList = new HashSet();
Set annotations = buildCommonAnnotations(c, skipList);
// Classes can have either EnclosingMethod or EnclosingClass tags. Soot
// sets the outer class for both "normal" and anonymous inner classes,
// so we test for enclosing methods first.
if (c.hasTag("EnclosingMethodTag")) {
EnclosingMethodTag eMethTag = (EnclosingMethodTag) c.getTag("EnclosingMethodTag");
Annotation enclosingMethodItem = buildEnclosingMethodTag(eMethTag, skipList);
if (enclosingMethodItem != null) {
annotations.add(enclosingMethodItem);
}
} else if (c.hasOuterClass()) {
if (skipList.add("Ldalvik/annotation/EnclosingClass;")) {
// EnclosingClass annotation
ImmutableAnnotationElement enclosingElement = new ImmutableAnnotationElement("value",
new ImmutableTypeEncodedValue(SootToDexUtils.getDexClassName(c.getOuterClass().getName())));
annotations.add(new ImmutableAnnotation(AnnotationVisibility.SYSTEM, "Ldalvik/annotation/EnclosingClass;",
Collections.singleton(enclosingElement)));
}
}
// If we have an outer class, we also pick up the InnerClass annotations
// from there. Note that Java and Soot associate InnerClass annotations
// with the respective outer classes, while Dalvik puts them on the
// respective inner classes.
if (c.hasOuterClass()) {
InnerClassAttribute icTag = (InnerClassAttribute) c.getOuterClass().getTag("InnerClassAttribute");
if (icTag != null) {
List innerClassItem = buildInnerClassAttribute(c, icTag, skipList);
if (innerClassItem != null) {
annotations.addAll(innerClassItem);
}
}
}
writeMemberClasses(c, skipList, annotations);
for (Tag t : c.getTags()) {
if (t.getName().equals("VisibilityAnnotationTag")) {
List visibilityItems = buildVisibilityAnnotationTag((VisibilityAnnotationTag) t, skipList);
annotations.addAll(visibilityItems);
}
}
// Write default-annotation tags
List defaults = new ArrayList();
for (SootMethod method : c.getMethods()) {
AnnotationDefaultTag tag = (AnnotationDefaultTag) method.getTag("AnnotationDefaultTag");
if (tag != null) {
tag.getDefaultVal().setName(method.getName());
defaults.add(tag.getDefaultVal());
}
}
if (defaults.size() > 0) {
VisibilityAnnotationTag defaultAnnotationTag = new VisibilityAnnotationTag(AnnotationConstants.RUNTIME_INVISIBLE);
AnnotationTag a = new AnnotationTag("Ldalvik/annotation/AnnotationDefault;");
defaultAnnotationTag.addAnnotation(a);
AnnotationTag at = new AnnotationTag(SootToDexUtils.getDexClassName(c.getName()));
AnnotationAnnotationElem ae = new AnnotationAnnotationElem(at, '@', "value");
a.addElem(ae);
for (AnnotationElem aelem : defaults) {
at.addElem(aelem);
}
List visibilityItems = buildVisibilityAnnotationTag(defaultAnnotationTag, skipList);
annotations.addAll(visibilityItems);
}
return annotations;
}
protected void writeMemberClasses(SootClass c, Set skipList, Set annotations) {
// Write the MemberClasses tag
InnerClassAttribute icTag = (InnerClassAttribute) c.getTag("InnerClassAttribute");
if (icTag != null) {
List memberClassesItem = buildMemberClassesAttribute(c, icTag, skipList);
if (memberClassesItem != null) {
annotations.addAll(memberClassesItem);
}
}
}
private Set buildFieldAnnotations(SootField f) {
Set skipList = new HashSet();
Set annotations = buildCommonAnnotations(f, skipList);
for (Tag t : f.getTags()) {
if (t.getName().equals("VisibilityAnnotationTag")) {
List visibilityItems = buildVisibilityAnnotationTag((VisibilityAnnotationTag) t, skipList);
annotations.addAll(visibilityItems);
}
}
return annotations;
}
private Set buildMethodAnnotations(SootMethod m) {
Set skipList = new HashSet();
Set annotations = buildCommonAnnotations(m, skipList);
for (Tag t : m.getTags()) {
if (t.getName().equals("VisibilityAnnotationTag")) {
List visibilityItems = buildVisibilityAnnotationTag((VisibilityAnnotationTag) t, skipList);
annotations.addAll(visibilityItems);
}
}
List exceptionList = m.getExceptionsUnsafe();
if (exceptionList != null && !exceptionList.isEmpty()) {
List valueList = new ArrayList(exceptionList.size());
for (SootClass exceptionClass : exceptionList) {
valueList.add(new ImmutableTypeEncodedValue(DexType.toDalvikICAT(exceptionClass.getName()).replace(".", "/")));
}
ImmutableArrayEncodedValue valueValue = new ImmutableArrayEncodedValue(valueList);
ImmutableAnnotationElement valueElement = new ImmutableAnnotationElement("value", valueValue);
Set elements = Collections.singleton(valueElement);
ImmutableAnnotation ann = new ImmutableAnnotation(AnnotationVisibility.SYSTEM, "Ldalvik/annotation/Throws;", elements);
annotations.add(ann);
}
return annotations;
}
/**
* Returns all method parameter annotations (or null) for a specific parameter
*
* @param m
* the method
* @param paramIdx
* the parameter index
* @return the annotations (or null)
*/
private Set buildMethodParameterAnnotations(SootMethod m, final int paramIdx) {
Set skipList = null;
Set annotations = null;
for (Tag t : m.getTags()) {
if (t.getName().equals("VisibilityParameterAnnotationTag")) {
VisibilityParameterAnnotationTag vat = (VisibilityParameterAnnotationTag) t;
if (skipList == null) {
skipList = new HashSet();
annotations = new HashSet();
}
List visibilityItems = buildVisibilityParameterAnnotationTag(vat, skipList, paramIdx);
annotations.addAll(visibilityItems);
}
}
return annotations;
}
private Set buildCommonAnnotations(AbstractHost host, Set skipList) {
Set annotations = new HashSet();
// handle deprecated tag
if (host.hasTag("DeprecatedTag") && !skipList.contains("Ljava/lang/Deprecated;")) {
ImmutableAnnotation ann = new ImmutableAnnotation(AnnotationVisibility.RUNTIME, "Ljava/lang/Deprecated;",
Collections.emptySet());
annotations.add(ann);
skipList.add("Ljava/lang/Deprecated;");
}
// handle signature tag
if (host.hasTag("SignatureTag") && !skipList.contains("Ldalvik/annotation/Signature;")) {
SignatureTag tag = (SignatureTag) host.getTag("SignatureTag");
List splitSignature = SootToDexUtils.splitSignature(tag.getSignature());
Set elements = null;
if (splitSignature != null && splitSignature.size() > 0) {
List valueList = new ArrayList();
for (String s : splitSignature) {
ImmutableStringEncodedValue val = new ImmutableStringEncodedValue(s);
valueList.add(val);
}
ImmutableArrayEncodedValue valueValue = new ImmutableArrayEncodedValue(valueList);
ImmutableAnnotationElement valueElement = new ImmutableAnnotationElement("value", valueValue);
elements = Collections.singleton(valueElement);
} else {
LOGGER.info("Signature annotation without value detected");
}
ImmutableAnnotation ann
= new ImmutableAnnotation(AnnotationVisibility.SYSTEM, "Ldalvik/annotation/Signature;", elements);
annotations.add(ann);
skipList.add("Ldalvik/annotation/Signature;");
}
return annotations;
}
private List buildVisibilityAnnotationTag(VisibilityAnnotationTag t, Set skipList) {
if (t.getAnnotations() == null) {
return Collections.emptyList();
}
List annotations = new ArrayList();
for (AnnotationTag at : t.getAnnotations()) {
String type = at.getType();
if (!skipList.add(type)) {
continue;
}
Set alreadyWritten = new HashSet();
List elements = null;
if (!at.getElems().isEmpty()) {
elements = new ArrayList();
for (AnnotationElem ae : at.getElems()) {
if (ae.getName() == null || ae.getName().isEmpty()) {
throw new RuntimeException("Null or empty annotation name encountered");
}
if (!alreadyWritten.add(ae.getName())) {
throw new RuntimeException("Duplicate annotation attribute: " + ae.getName());
}
EncodedValue value = buildEncodedValueForAnnotation(ae);
ImmutableAnnotationElement element = new ImmutableAnnotationElement(ae.getName(), value);
elements.add(element);
}
}
String typeName = SootToDexUtils.getDexClassName(at.getType());
ImmutableAnnotation ann = new ImmutableAnnotation(getVisibility(t.getVisibility()), typeName, elements);
annotations.add(ann);
}
return annotations;
}
private List buildVisibilityParameterAnnotationTag(VisibilityParameterAnnotationTag t,
Set skipList, int paramIdx) {
if (t.getVisibilityAnnotations() == null) {
return Collections.emptyList();
}
int paramTagIdx = 0;
List annotations = new ArrayList();
for (VisibilityAnnotationTag vat : t.getVisibilityAnnotations()) {
if (paramTagIdx == paramIdx && vat != null && vat.getAnnotations() != null) {
for (AnnotationTag at : vat.getAnnotations()) {
String type = at.getType();
if (!skipList.add(type)) {
continue;
}
Set alreadyWritten = new HashSet();
List elements = null;
if (!at.getElems().isEmpty()) {
elements = new ArrayList();
for (AnnotationElem ae : at.getElems()) {
if (ae.getName() == null || ae.getName().isEmpty()) {
throw new RuntimeException("Null or empty annotation name encountered");
}
if (!alreadyWritten.add(ae.getName())) {
throw new RuntimeException("Duplicate annotation attribute: " + ae.getName());
}
EncodedValue value = buildEncodedValueForAnnotation(ae);
ImmutableAnnotationElement element = new ImmutableAnnotationElement(ae.getName(), value);
elements.add(element);
}
}
ImmutableAnnotation ann = new ImmutableAnnotation(getVisibility(vat.getVisibility()),
SootToDexUtils.getDexClassName(at.getType()), elements);
annotations.add(ann);
}
}
paramTagIdx++;
}
return annotations;
}
private Annotation buildEnclosingMethodTag(EnclosingMethodTag t, Set skipList) {
if (!skipList.add("Ldalvik/annotation/EnclosingMethod;")) {
return null;
}
if (t.getEnclosingMethod() == null) {
return null;
}
String[] split1 = t.getEnclosingMethodSig().split("\\)");
String parametersS = split1[0].replaceAll("\\(", "");
String returnTypeS = split1[1];
List typeList = new ArrayList();
if (!parametersS.equals("")) {
for (String p : Util.splitParameters(parametersS)) {
if (!p.isEmpty()) {
typeList.add(p);
}
}
}
ImmutableMethodReference mRef = new ImmutableMethodReference(SootToDexUtils.getDexClassName(t.getEnclosingClass()),
t.getEnclosingMethod(), typeList, returnTypeS);
ImmutableMethodEncodedValue methodRef = new ImmutableMethodEncodedValue(mRef);
AnnotationElement methodElement = new ImmutableAnnotationElement("value", methodRef);
return new ImmutableAnnotation(AnnotationVisibility.SYSTEM, "Ldalvik/annotation/EnclosingMethod;",
Collections.singleton(methodElement));
}
private List buildInnerClassAttribute(SootClass parentClass, InnerClassAttribute t, Set skipList) {
if (t.getSpecs() == null) {
return null;
}
List anns = null;
for (Tag t2 : t.getSpecs()) {
InnerClassTag icTag = (InnerClassTag) t2;
// In Dalvik, both the EnclosingMethod/EnclosingClass tag and the
// InnerClass tag are written to the inner class which is different
// to Java. We thus check whether this tag actually points to our
// outer class.
String outerClass = DexInnerClassParser.getOuterClassNameFromTag(icTag);
String innerClass = icTag.getInnerClass().replaceAll("/", ".");
// Only write the InnerClass tag to the inner class itself, not
// the other one. If the outer class points to our parent, but
// this is simply the wrong inner class, we also continue with the
// next tag.
if (!parentClass.hasOuterClass() || !innerClass.equals(parentClass.getName())) {
continue;
}
// If the outer class points to the very same class, we null it
if (parentClass.getName().equals(outerClass) && icTag.getOuterClass() == null) {
outerClass = null;
}
// Do not write garbage. Never.
if (parentClass.getName().equals(outerClass)) {
continue;
}
// This is an actual inner class. Write the annotation
if (skipList.add("Ldalvik/annotation/InnerClass;")) {
// InnerClass annotation
List elements = new ArrayList();
ImmutableAnnotationElement flagsElement
= new ImmutableAnnotationElement("accessFlags", new ImmutableIntEncodedValue(icTag.getAccessFlags()));
elements.add(flagsElement);
ImmutableEncodedValue nameValue;
if (icTag.getShortName() != null && !icTag.getShortName().isEmpty()) {
nameValue = new ImmutableStringEncodedValue(icTag.getShortName());
} else {
nameValue = ImmutableNullEncodedValue.INSTANCE;
}
ImmutableAnnotationElement nameElement = new ImmutableAnnotationElement("name", nameValue);
elements.add(nameElement);
if (anns == null) {
anns = new ArrayList();
}
anns.add(new ImmutableAnnotation(AnnotationVisibility.SYSTEM, "Ldalvik/annotation/InnerClass;", elements));
}
}
return anns;
}
private List buildMemberClassesAttribute(SootClass parentClass, InnerClassAttribute t, Set skipList) {
List anns = null;
Set memberClasses = null;
// Collect the inner classes
for (Tag t2 : t.getSpecs()) {
InnerClassTag icTag = (InnerClassTag) t2;
String outerClass = DexInnerClassParser.getOuterClassNameFromTag(icTag);
// Only classes with names are member classes
if (icTag.getOuterClass() != null && parentClass.getName().equals(outerClass)) {
if (memberClasses == null) {
memberClasses = new HashSet();
}
memberClasses.add(SootToDexUtils.getDexClassName(icTag.getInnerClass()));
}
}
// Write the member classes
if (memberClasses != null && !memberClasses.isEmpty() && skipList.add("Ldalvik/annotation/MemberClasses;")) {
List classes = new ArrayList();
for (String memberClass : memberClasses) {
ImmutableTypeEncodedValue classValue = new ImmutableTypeEncodedValue(memberClass);
classes.add(classValue);
}
ImmutableArrayEncodedValue classesValue = new ImmutableArrayEncodedValue(classes);
ImmutableAnnotationElement element = new ImmutableAnnotationElement("value", classesValue);
ImmutableAnnotation memberAnnotation = new ImmutableAnnotation(AnnotationVisibility.SYSTEM,
"Ldalvik/annotation/MemberClasses;", Collections.singletonList(element));
if (anns == null) {
anns = new ArrayList();
}
anns.add(memberAnnotation);
}
return anns;
}
private Collection toMethods(SootClass clazz) {
if (clazz.getMethods().isEmpty()) {
return null;
}
String classType = SootToDexUtils.getDexTypeDescriptor(clazz.getType());
List methods = new ArrayList();
for (SootMethod sm : clazz.getMethods()) {
if (sm.isPhantom()) {
// Do not print method bodies for inherited methods
continue;
}
MethodImplementation impl = toMethodImplementation(sm);
List parameterNames = null;
if (sm.hasTag("ParamNamesTag")) {
parameterNames = ((ParamNamesTag) sm.getTag("ParamNamesTag")).getNames();
}
int paramIdx = 0;
List parameters = null;
if (sm.getParameterCount() > 0) {
parameters = new ArrayList();
for (Type tp : sm.getParameterTypes()) {
String paramType = SootToDexUtils.getDexTypeDescriptor(tp);
parameters.add(new ImmutableMethodParameter(paramType, buildMethodParameterAnnotations(sm, paramIdx),
sm.isConcrete() && parameterNames != null ? parameterNames.get(paramIdx) : null));
paramIdx++;
}
}
String returnType = SootToDexUtils.getDexTypeDescriptor(sm.getReturnType());
int accessFlags = SootToDexUtils.getDexAccessFlags(sm);
ImmutableMethod meth = new ImmutableMethod(classType, sm.getName(), parameters, returnType, accessFlags,
buildMethodAnnotations(sm), null, impl);
methods.add(meth);
}
return methods;
}
private MethodImplementation toMethodImplementation(SootMethod m) {
if (m.isAbstract() || m.isNative()) {
return null;
}
Body activeBody = m.retrieveActiveBody();
// check the method name to make sure that dexopt won't get into trouble
// when installing the app
if (m.getName().contains("<") || m.getName().equals(">")) {
if (!m.getName().equals("") && !m.getName().equals("")) {
throw new RuntimeException("Invalid method name: " + m.getName());
}
}
// Switch statements may not be empty in dex, so we have to fix this
// first
EmptySwitchEliminator.v().transform(activeBody);
// Dalvik requires synchronized methods to have explicit monitor calls,
// so we insert them here. See
// http://milk.com/kodebase/dalvik-docs-mirror/docs/debugger.html
// We cannot place this upon the developer since it is only required
// for Dalvik, but not for other targets.
SynchronizedMethodTransformer.v().transform(activeBody);
// Tries may not start or end at units which have no corresponding
// Dalvik
// instructions such as IdentityStmts. We reduce the traps to start at
// the
// first "real" instruction. We could also use a TrapTigthener, but that
// would be too expensive for what we need here.
FastDexTrapTightener.v().transform(activeBody);
// Look for sequences of array element assignments that we can collapse
// into bulk initializations
DexArrayInitDetector initDetector = new DexArrayInitDetector();
initDetector.constructArrayInitializations(activeBody);
initDetector.fixTraps(activeBody);
// Split the tries since Dalvik does not supported nested try/catch
// blocks
TrapSplitter.v().transform(activeBody);
// word count of incoming parameters
int inWords = SootToDexUtils.getDexWords(m.getParameterTypes());
if (!m.isStatic()) {
inWords++; // extra word for "this"
}
// word count of max outgoing parameters
Collection units = activeBody.getUnits();
// register count = parameters + additional registers, depending on the
// dex instructions generated (e.g. locals used and constants loaded)
StmtVisitor stmtV = buildStmtVisitor(m, initDetector);
Chain traps = activeBody.getTraps();
Set trapReferences = new HashSet(traps.size() * 3);
for (Trap t : activeBody.getTraps()) {
trapReferences.add(t.getBeginUnit());
trapReferences.add(t.getEndUnit());
trapReferences.add(t.getHandlerUnit());
}
toInstructions(units, stmtV, trapReferences);
int registerCount = stmtV.getRegisterCount();
if (inWords > registerCount) {
/*
* as the Dalvik VM moves the parameters into the last registers, the "in" word count must be at least equal to the
* register count. a smaller register count could occur if soot generated the method body, see e.g. the handling of
* phantom refs in SootMethodRefImpl.resolve(StringBuffer): the body has no locals for the ParameterRefs, it just
* throws an error.
*
* we satisfy the verifier by just increasing the register count, since calling phantom refs will lead to an error
* anyway.
*/
registerCount = inWords;
}
MethodImplementationBuilder builder = new MethodImplementationBuilder(registerCount);
LabelAssigner labelAssinger = new LabelAssigner(builder);
List instructions = stmtV.getRealInsns(labelAssinger);
fixLongJumps(instructions, labelAssinger, stmtV);
Map seenRegisters = new HashMap();
Map instructionRegisterMap = stmtV.getInstructionRegisterMap();
if (Options.v().write_local_annotations()) {
for (LocalRegisterAssignmentInformation assignment : stmtV.getParameterInstructionsList()) {
// The "this" local gets added automatically, so we do not need
// to add it explicitly
// (at least not if it exists with exactly this name)
if (assignment.getLocal().getName().equals("this")) {
continue;
}
addRegisterAssignmentDebugInfo(assignment, seenRegisters, builder);
}
}
for (BuilderInstruction ins : instructions) {
Stmt origStmt = stmtV.getStmtForInstruction(ins);
// If this is a switch payload, we need to place the label
if (stmtV.getInstructionPayloadMap().containsKey(ins)) {
builder.addLabel(labelAssinger.getLabelName(stmtV.getInstructionPayloadMap().get(ins)));
}
if (origStmt != null) {
// Do we need a label here because this a trap handler?
if (trapReferences.contains(origStmt)) {
labelAssinger.getOrCreateLabel(origStmt);
}
// Add the label if the statement has one
String labelName = labelAssinger.getLabelName(origStmt);
if (labelName != null && !builder.getLabel(labelName).isPlaced()) {
builder.addLabel(labelName);
}
// Add the tags
if (stmtV.getStmtForInstruction(ins) != null) {
writeTagsForStatement(builder, origStmt);
}
}
builder.addInstruction(ins);
LocalRegisterAssignmentInformation registerAssignmentTag = instructionRegisterMap.get(ins);
if (registerAssignmentTag != null) {
// Add start local debugging information: Register -> Local
// assignment
addRegisterAssignmentDebugInfo(registerAssignmentTag, seenRegisters, builder);
}
}
for (int registersLeft : seenRegisters.values()) {
builder.addEndLocal(registersLeft);
}
toTries(activeBody.getTraps(), stmtV, builder, labelAssinger);
// Make sure that all labels have been placed by now
for (Label lbl : labelAssinger.getAllLabels()) {
if (!lbl.isPlaced()) {
throw new RuntimeException("Label not placed: " + lbl);
}
}
return builder.getMethodImplementation();
}
/**
* Creates a statement visitor to build code for each statement.
*
* Allows subclasses to use own implementations
*
* @param belongingMethod
* the method
* @param arrayInitDetector
* auxilliary class for detecting array initializations
* @return the statement visitor
*/
protected StmtVisitor buildStmtVisitor(SootMethod belongingMethod, DexArrayInitDetector arrayInitDetector) {
return new StmtVisitor(belongingMethod, arrayInitDetector);
}
/**
* Writes out the information stored in the tags associated with the given statement
*
* @param builder
* The builder used to generate the Dalvik method implementation
* @param stmt
* The statement for which to write out the tags
*/
protected void writeTagsForStatement(MethodImplementationBuilder builder, Stmt stmt) {
List tags = stmt.getTags();
for (Tag t : tags) {
if (t instanceof LineNumberTag) {
LineNumberTag lnt = (LineNumberTag) t;
builder.addLineNumber(lnt.getLineNumber());
} else if (t instanceof SourceFileTag) {
SourceFileTag sft = (SourceFileTag) t;
builder.addSetSourceFile(new ImmutableStringReference(sft.getSourceFile()));
}
}
}
/**
* Fixes long jumps that exceed the maximum distance for the respective jump type
*
* @param instructions
* The list of generated dalvik instructions
* @param labelAssigner
* The label assigner that maps statements to labels
* @param stmtV
* The statement visitor used to produce the dalvik instructions
*/
private void fixLongJumps(List instructions, LabelAssigner labelAssigner, StmtVisitor stmtV) {
// Only construct the maps once and update them afterwards
Map instructionsToIndex = new HashMap();
List instructionsToOffsets = new ArrayList();
Map