package soot.toDex; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; 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.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.ExceptionHandler; 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.value.EncodedValue; import org.jf.dexlib2.immutable.ImmutableAnnotation; import org.jf.dexlib2.immutable.ImmutableAnnotationElement; import org.jf.dexlib2.immutable.ImmutableExceptionHandler; 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.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.jf.dexlib2.writer.builder.BuilderField; import org.jf.dexlib2.writer.builder.BuilderFieldReference; import org.jf.dexlib2.writer.builder.BuilderMethod; import org.jf.dexlib2.writer.builder.BuilderMethodReference; import org.jf.dexlib2.writer.builder.BuilderTypeReference; import org.jf.dexlib2.writer.builder.DexBuilder; import org.jf.dexlib2.writer.io.FileDataStore; import soot.Body; import soot.BooleanType; import soot.ByteType; import soot.CharType; import soot.CompilationDeathException; import soot.G; import soot.IntType; import soot.Local; import soot.PackManager; import soot.ShortType; import soot.SootClass; import soot.SootField; import soot.SootMethod; import soot.SootMethodRef; import soot.SourceLocator; import soot.Trap; import soot.Type; import soot.Unit; import soot.dexpler.Util; import soot.jimple.Jimple; 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; /** * Main entry point for the "dex" output format.<br> * <br> * Use {@link #add(SootClass)} to add classes that should be printed as dex output and {@link #print()} to finally print the classes.<br> * If the printer has found the original APK of an added class (via {@link SourceLocator#dexClassIndex()}), * the files in the APK are copied to a new one, replacing it's classes.dex and excluding the signature files. * Note that you have to sign and align the APK yourself, with jarsigner and zipalign, respectively.<br> * If there is no original APK, the printer just emits a classes.dex. * * @see <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/windows/jarsigner.html">jarsigner documentation</a> * @see <a href="http://developer.android.com/tools/help/zipalign.html">zipalign documentation</a> */ public class DexPrinter { private static final String CLASSES_DEX = "classes.dex"; private static DexBuilder dexFile; private File originalApk; public DexPrinter() { dexFile = DexBuilder.makeDexBuilder(19); //dexAnnotation = new DexAnnotation(dexFile); } private void printApk(String outputDir, File originalApk) throws IOException { ZipOutputStream outputApk; if(Options.v().output_jar()) { outputApk = PackManager.v().getJarFile(); G.v().out.println("Writing APK to: " + Options.v().output_dir()); } else { String outputFileName = outputDir + File.separatorChar + originalApk.getName(); File outputFile = new File(outputFileName); if(outputFile.exists() && !Options.v().force_overwrite()) { throw new CompilationDeathException("Output file "+outputFile+" exists. Not overwriting."); } outputApk = new ZipOutputStream(new FileOutputStream(outputFile)); G.v().out.println("Writing APK to: " + outputFileName); } G.v().out.println("do not forget to sign the .apk file with jarsigner and to align it with zipalign"); ZipFile original = new ZipFile(originalApk); copyAllButClassesDexAndSigFiles(original, outputApk); original.close(); // put our classes.dex into the zip archive File tmpFile = File.createTempFile("toDex", null); FileInputStream fis = new FileInputStream(tmpFile); try { outputApk.putNextEntry(new ZipEntry(CLASSES_DEX)); writeTo(tmpFile.getAbsolutePath()); while (fis.available() > 0) { byte[] data = new byte[fis.available()]; fis.read(data); outputApk.write(data); } outputApk.closeEntry(); outputApk.close(); } finally { fis.close(); tmpFile.delete(); } } 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.equals(CLASSES_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 static boolean isSignatureFile(String fileName) { StringBuilder sigFileRegex = new StringBuilder(); // file name must start with META-INF... sigFileRegex.append("META\\-INF"); // ...followed by a zip file separator... sigFileRegex.append('/'); // ...followed by anything but a zip file separator... sigFileRegex.append("[^/]+"); // ...ending with .SF, .DSA, .RSA or .EC sigFileRegex.append("(\\.SF|\\.DSA|\\.RSA|\\.EC)$"); return fileName.matches(sigFileRegex.toString()); } private void writeTo(String fileName) throws IOException { FileDataStore fds = new FileDataStore(new File(fileName)); dexFile.writeTo(fds); fds.close(); } /** * 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; FieldReference fref = dexFile.internFieldReference(new ImmutableFieldReference (classT, e.getConstantName(), fieldT)); return new ImmutableEnumEncodedValue(fref); } case 'c': { AnnotationClassElem e = (AnnotationClassElem)elem; return new ImmutableTypeEncodedValue(SootToDexUtils.getDexClassName(e.getDesc())); } case '[': { AnnotationArrayElem e = (AnnotationArrayElem)elem; Set<EncodedValue> values = new HashSet<EncodedValue>(); 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<String> alreadyWritten = new HashSet<String>(); List<AnnotationElement> elements = null; if (!e.getValue().getElems().isEmpty()) { elements = new ArrayList<AnnotationElement>(); 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]; FieldReference fref = dexFile.internFieldReference(new ImmutableFieldReference (classString, fieldName, typeString)); return new ImmutableFieldEncodedValue(fref); } 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<String> paramTypeList = null; if (!parameters.isEmpty()) { paramTypeList = new ArrayList<String>(); if (parameters.length() > 0) for (String p: parameters.split(",")) { paramTypeList.add(p); } } MethodReference mref = dexFile.internMethodReference(new ImmutableMethodReference (classString, methodNameString, paramTypeList, returnType)); return new ImmutableMethodEncodedValue(mref); } 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; return new ImmutableStringEncodedValue(s.getStringValue()); } 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<String> interfaces = null; if (!c.getInterfaces().isEmpty()) { interfaces = new ArrayList<String>(); for (SootClass ifc : c.getInterfaces()) interfaces.add(SootToDexUtils.getDexTypeDescriptor(ifc.getType())); } List<BuilderField> fields = null; if (!c.getFields().isEmpty()) { fields = new ArrayList<BuilderField>(); for (SootField f : c.getFields()) { // Look for a static initializer EncodedValue staticInit = null; for (Tag t : f.getTags()) { if (t instanceof ConstantValueTag) { if (staticInit != null) { G.v().out.println("warning: 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<Annotation> fieldAnnotations = buildFieldAnnotations(f); BuilderField field = dexFile.internField(classType, f.getName(), SootToDexUtils.getDexTypeDescriptor(f.getType()), f.getModifiers(), staticInit, fieldAnnotations); fields.add(field); } } dexFile.internClassDef(classType, accessFlags, superClass, interfaces, sourceFile, buildClassAnnotations(c), fields, toMethods(c)); } private Set<Annotation> buildClassAnnotations(SootClass c) { Set<String> skipList = new HashSet<String>(); Set<Annotation> 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<Annotation> innerClassItem = buildInnerClassAttribute(c, icTag, skipList); if (innerClassItem != null) annotations.addAll(innerClassItem); } } // Write the MemberClasses tag InnerClassAttribute icTag = (InnerClassAttribute) c.getTag("InnerClassAttribute"); if (icTag != null) { List<Annotation> memberClassesItem = buildMemberClassesAttribute(c, icTag, skipList); if (memberClassesItem != null) annotations.addAll(memberClassesItem); } for (Tag t : c.getTags()) { if (t.getName().equals("VisibilityAnnotationTag")){ List<ImmutableAnnotation> visibilityItems = buildVisibilityAnnotationTag ((VisibilityAnnotationTag) t, skipList); annotations.addAll(visibilityItems); } } //Write default-annotation tags List<AnnotationElem> defaults = new ArrayList<AnnotationElem>(); 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<ImmutableAnnotation> visibilityItems = buildVisibilityAnnotationTag (defaultAnnotationTag, skipList); annotations.addAll(visibilityItems); } return annotations; } private Set<Annotation> buildFieldAnnotations(SootField f) { Set<String> skipList = new HashSet<String>(); Set<Annotation> annotations = buildCommonAnnotations(f, skipList); for (Tag t : f.getTags()) { if (t.getName().equals("VisibilityAnnotationTag")){ List<ImmutableAnnotation> visibilityItems = buildVisibilityAnnotationTag ((VisibilityAnnotationTag) t, skipList); annotations.addAll(visibilityItems); } } return annotations; } private Set<Annotation> buildMethodAnnotations(SootMethod m) { Set<String> skipList = new HashSet<String>(); Set<Annotation> annotations = buildCommonAnnotations(m, skipList); for (Tag t : m.getTags()) { if (t.getName().equals("VisibilityAnnotationTag")){ List<ImmutableAnnotation> visibilityItems = buildVisibilityAnnotationTag ((VisibilityAnnotationTag) t, skipList); annotations.addAll(visibilityItems); } } return annotations; } private Set<Annotation> buildMethodParameterAnnotations(SootMethod m, final int paramIdx) { Set<String> skipList = new HashSet<String>(); Set<Annotation> annotations = buildCommonAnnotations(m, skipList); for (Tag t : m.getTags()) { if (t.getName().equals("VisibilityParameterAnnotationTag")) { VisibilityParameterAnnotationTag vat = (VisibilityParameterAnnotationTag)t; List<ImmutableAnnotation> visibilityItems = buildVisibilityParameterAnnotationTag (vat, skipList, paramIdx); annotations.addAll(visibilityItems); } } return annotations; } private Set<Annotation> buildCommonAnnotations(AbstractHost host, Set<String> skipList) { Set<Annotation> annotations = new HashSet<Annotation>(); // handle deprecated tag if (host.hasTag("DeprecatedTag") && !skipList.contains("Ljava/lang/Deprecated;")) { ImmutableAnnotation ann = new ImmutableAnnotation (AnnotationVisibility.RUNTIME, "Ljava/lang/Deprecated;", Collections.<AnnotationElement>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<String> splitSignature = SootToDexUtils.splitSignature(tag.getSignature()); Set<ImmutableAnnotationElement> elements = null; if (splitSignature != null && splitSignature.size() > 0) { elements = new HashSet<ImmutableAnnotationElement>(); List<ImmutableEncodedValue> valueList = new ArrayList<ImmutableEncodedValue>(); for (String s : splitSignature) { ImmutableStringEncodedValue val = new ImmutableStringEncodedValue(s); valueList.add(val); } ImmutableArrayEncodedValue valueValue = new ImmutableArrayEncodedValue(valueList); ImmutableAnnotationElement valueElement = new ImmutableAnnotationElement ("value", valueValue); elements.add(valueElement); } else G.v().out.println("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<ImmutableAnnotation> buildVisibilityAnnotationTag (VisibilityAnnotationTag t, Set<String> skipList) { if (t.getAnnotations() == null) return Collections.emptyList(); List<ImmutableAnnotation> annotations = new ArrayList<ImmutableAnnotation>(); for (AnnotationTag at: t.getAnnotations()) { String type = at.getType(); if (!skipList.add(type)) continue; Set<String> alreadyWritten = new HashSet<String>(); List<AnnotationElement> elements = null; if (!at.getElems().isEmpty()) { elements = new ArrayList<AnnotationElement>(); 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<ImmutableAnnotation> buildVisibilityParameterAnnotationTag (VisibilityParameterAnnotationTag t, Set<String> skipList, int paramIdx) { if (t.getVisibilityAnnotations() == null) return Collections.emptyList(); int paramTagIdx = 0; List<ImmutableAnnotation> annotations = new ArrayList<ImmutableAnnotation>(); 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<String> alreadyWritten = new HashSet<String>(); List<AnnotationElement> elements = null; if (!at.getElems().isEmpty()) { elements = new ArrayList<AnnotationElement>(); 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<String> 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<String> typeList = new ArrayList<String>(); 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 (dexFile.internMethodReference(mRef)); AnnotationElement methodElement = new ImmutableAnnotationElement("value", methodRef); return new ImmutableAnnotation(AnnotationVisibility.SYSTEM, "Ldalvik/annotation/EnclosingMethod;", Collections.singleton(methodElement)); } private List<Annotation> buildInnerClassAttribute(SootClass parentClass, InnerClassAttribute t, Set<String> skipList) { if (t.getSpecs() == null) return null; List<Annotation> 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 = 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<AnnotationElement> elements = new ArrayList<AnnotationElement>(); 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<Annotation>(); anns.add(new ImmutableAnnotation(AnnotationVisibility.SYSTEM, "Ldalvik/annotation/InnerClass;", elements)); } } return anns; } private String getOuterClassNameFromTag(InnerClassTag icTag) { String outerClass; if (icTag.getOuterClass() == null) { // anonymous inner classes outerClass = icTag.getInnerClass().replaceAll("\\$[0-9,a-z,A-Z]*$", "").replaceAll("/", "."); } else { outerClass = icTag.getOuterClass().replaceAll("/", "."); } return outerClass; } private List<Annotation> buildMemberClassesAttribute(SootClass parentClass, InnerClassAttribute t, Set<String> skipList) { List<Annotation> anns = null; Set<String> memberClasses = null; // Collect the inner classes for (Tag t2 : t.getSpecs()) { InnerClassTag icTag = (InnerClassTag) t2; String outerClass = getOuterClassNameFromTag(icTag); // Only classes with names are member classes if (icTag.getOuterClass() != null && parentClass.getName().equals(outerClass)) { if (memberClasses == null) memberClasses = new HashSet<String>(); memberClasses.add(SootToDexUtils.getDexClassName(icTag.getInnerClass())); } } // Write the member classes if (memberClasses != null && !memberClasses.isEmpty() && skipList.add("Ldalvik/annotation/MemberClasses;")) { List<EncodedValue> classes = new ArrayList<EncodedValue>(); 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<Annotation>(); anns.add(memberAnnotation); } return anns; } /** * 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 + "'"); } private Collection<BuilderMethod> toMethods(SootClass clazz) { if (clazz.getMethods().isEmpty()) return null; String classType = SootToDexUtils.getDexTypeDescriptor(clazz.getType()); List<BuilderMethod> methods = new ArrayList<BuilderMethod>(); for (SootMethod sm : clazz.getMethods()) { if (sm.isPhantom()) { // Do not print method bodies for inherited methods continue; } MethodImplementation impl = toMethodImplementation(sm); List<String> parameterNames = null; if (sm.hasTag("ParamNamesTag")) parameterNames = ((ParamNamesTag) sm.getTag("ParamNamesTag")).getNames(); int paramIdx = 0; List<MethodParameter> parameters = null; if (sm.getParameterCount() > 0) { parameters = new ArrayList<MethodParameter>(); 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); BuilderMethod meth = dexFile.internMethod(classType, sm.getName(), parameters, returnType, accessFlags, buildMethodAnnotations(sm), impl); methods.add(meth); } return methods; } protected static BuilderFieldReference toFieldReference (SootField f, DexBuilder belongingDexFile) { FieldReference fieldRef = new ImmutableFieldReference (SootToDexUtils.getDexClassName(f.getDeclaringClass().getName()), f.getName(), SootToDexUtils.getDexTypeDescriptor(f.getType())); return belongingDexFile.internFieldReference(fieldRef); } protected static BuilderMethodReference toMethodReference (SootMethodRef m, DexBuilder belongingDexFile) { List<String> parameters = new ArrayList<String>(); for (Type t : m.parameterTypes()) parameters.add(SootToDexUtils.getDexTypeDescriptor(t)); MethodReference methodRef = new ImmutableMethodReference (SootToDexUtils.getDexClassName(m.declaringClass().getName()), m.name(), parameters, SootToDexUtils.getDexTypeDescriptor(m.returnType())); return belongingDexFile.internMethodReference(methodRef); } protected static BuilderTypeReference toTypeReference (Type t, DexBuilder belongingDexFile) { return belongingDexFile.internTypeReference (SootToDexUtils.getDexTypeDescriptor(t)); } 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("<init>") && !m.getName().equals("<clinit>")) 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); // 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<Unit> units = activeBody.getUnits(); // register count = parameters + additional registers, depending on the dex instructions generated (e.g. locals used and constants loaded) StmtVisitor stmtV = new StmtVisitor(m, dexFile); toInstructions(units, stmtV); 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<BuilderInstruction> instructions = stmtV.getRealInsns(labelAssinger); fixLongJumps(instructions, labelAssinger, stmtV); Map<Local, Integer> seenRegisters = new HashMap<Local, Integer>(); Map<Instruction, LocalRegisterAssignmentInformation> instructionRegisterMap = stmtV.getInstructionRegisterMap(); 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? for (Trap t : m.getActiveBody().getTraps()) { if (t.getBeginUnit() == origStmt || t.getEndUnit() == origStmt || t.getHandlerUnit() == origStmt) { labelAssinger.getOrCreateLabel(origStmt); break; } } // 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) { List<Tag> tags = origStmt.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(dexFile.internStringReference (sft.getSourceFile())); } } } } 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(); } private void fixLongJumps(List<BuilderInstruction> instructions, LabelAssigner labelAssigner, StmtVisitor stmtV) { boolean hasChanged = true; l0 : while (hasChanged) { hasChanged = false; // Build a mapping between labels and offsets Map<Label, Integer> labelToInsOffset = new HashMap<Label, Integer>(); for (int i = 0; i < instructions.size(); i++) { BuilderInstruction bi = instructions.get(i); Stmt origStmt = stmtV.getStmtForInstruction(bi); if (origStmt != null) { Label lbl = labelAssigner.getLabelUnsafe(origStmt); if (lbl != null) { labelToInsOffset.put(lbl, i); } } } // Look for references to labels for (int j = 0; j < instructions.size(); j++) { BuilderInstruction bj = instructions.get(j); if (bj instanceof BuilderOffsetInstruction) { BuilderOffsetInstruction boj = (BuilderOffsetInstruction) bj; Label targetLbl = boj.getTarget(); Integer offset = labelToInsOffset.get(targetLbl); if (offset == null) continue; // Compute the distance between the instructions Insn jumpInsn = stmtV.getInsnForInstruction(boj); if (jumpInsn instanceof InsnWithOffset) { InsnWithOffset offsetInsn = (InsnWithOffset) jumpInsn; int distance = getDistanceBetween(instructions, j, offset); if (Math.abs(distance) > offsetInsn.getMaxJumpOffset()) { // We need intermediate jumps insertIntermediateJump(offset, j, stmtV, instructions, labelAssigner); hasChanged = true; continue l0; } } } } } } /** * Creates an intermediate jump instruction between the original jump * instruction and its target * @param targetInsPos The jump target index * @param jumpInsPos The position of the jump instruction * @param stmtV The statement visitor used for constructing the instructions * @param instructions The list of Dalvik instructions * @param labelAssigner The label assigner to be used for creating new labels */ private void insertIntermediateJump(int targetInsPos, int jumpInsPos, StmtVisitor stmtV, List<BuilderInstruction> instructions, LabelAssigner labelAssigner) { // Get the original jump instruction BuilderInstruction originalJumpInstruction = instructions.get(jumpInsPos); Insn originalJumpInsn = stmtV.getInsnForInstruction(originalJumpInstruction); if (originalJumpInsn == null) return; if (!(originalJumpInsn instanceof InsnWithOffset)) throw new RuntimeException("Unexpected jump instruction target"); InsnWithOffset offsetInsn = (InsnWithOffset) originalJumpInsn; // Find a position where we can jump to int distance = Math.max(targetInsPos, jumpInsPos) - Math.min(targetInsPos, jumpInsPos); if (distance == 0) return; int newJumpIdx = Math.min(targetInsPos, jumpInsPos) + (distance / 2); int sign = (int) Math.signum(targetInsPos - jumpInsPos); if (distance > offsetInsn.getMaxJumpOffset()) newJumpIdx = jumpInsPos + sign; // There must be a statement at the instruction after the jump target while (stmtV.getStmtForInstruction(instructions.get(newJumpIdx)) == null) { newJumpIdx += sign; if (newJumpIdx < 0 || newJumpIdx >= instructions.size()) throw new RuntimeException("No position for inserting intermediate " + "jump instruction found"); } // Create a jump instruction from the middle to the end NopStmt nop = Jimple.v().newNopStmt(); Insn30t newJump = new Insn30t(Opcode.GOTO_32); newJump.setTarget(stmtV.getStmtForInstruction(instructions.get(targetInsPos))); BuilderInstruction newJumpInstruction = newJump.getRealInsn(labelAssigner); instructions.add(newJumpIdx, newJumpInstruction); stmtV.fakeNewInsn(nop, newJump, newJumpInstruction); // We have added something, so we need to fix indices if (newJumpIdx < jumpInsPos) jumpInsPos++; if (newJumpIdx < targetInsPos) targetInsPos++; // Jump from the original instruction to the new one in the middle offsetInsn.setTarget(nop); BuilderInstruction replacementJumpInstruction = offsetInsn.getRealInsn(labelAssigner); instructions.add(jumpInsPos, replacementJumpInstruction); instructions.remove(originalJumpInstruction); stmtV.fakeNewInsn(stmtV.getStmtForInstruction(originalJumpInstruction), originalJumpInsn, replacementJumpInstruction); // Our indices are still fine, because we just replaced something Stmt afterNewJump = stmtV.getStmtForInstruction(instructions.get(newJumpIdx + 1)); // Make the original control flow jump around the new artificial jump instruction Insn10t jumpAround = new Insn10t(Opcode.GOTO); jumpAround.setTarget(afterNewJump); BuilderInstruction jumpAroundInstruction = jumpAround.getRealInsn(labelAssigner); instructions.add(newJumpIdx, jumpAroundInstruction); } private int getDistanceBetween(List<BuilderInstruction> instructions, int i, int j) { if (i == j) return 0; int dist = 0; for (int idx = Math.min(i, j); idx < Math.max(i, j); idx++) { BuilderInstruction bi = instructions.get(idx); dist += (bi.getFormat().size / 2); } return dist; } private void addRegisterAssignmentDebugInfo( LocalRegisterAssignmentInformation registerAssignment, Map<Local, Integer> seenRegisters, MethodImplementationBuilder builder) { Local local = registerAssignment.getLocal(); String dexLocalType = SootToDexUtils.getDexTypeDescriptor(local.getType()); StringReference localName = dexFile.internStringReference(local.getName()); Register reg = registerAssignment.getRegister(); int register = reg.getNumber(); Integer beforeRegister = seenRegisters.get(local); if (beforeRegister != null) { if (beforeRegister == register) //No change return; builder.addEndLocal(beforeRegister); } builder.addStartLocal(register, localName, dexFile.internTypeReference(dexLocalType), dexFile.internStringReference("")); seenRegisters.put(local, register); } private void toInstructions(Collection<Unit> units, StmtVisitor stmtV) { for (Unit u : units) { stmtV.beginNewStmt((Stmt) u); u.apply(stmtV); } stmtV.finalizeInstructions(); } private static class CodeRange { int startAddress; int endAddress; public CodeRange(int startAddress, int endAddress) { this.startAddress = startAddress; this.endAddress = endAddress; } /** * Checks whether the given code range r is fully enclosed by this code * range. * @param r The other code range * @return True if the given code range r is fully enclosed by this code * range, otherwise false. */ public boolean containsRange(CodeRange r) { return (r.startAddress >= this.startAddress && r.endAddress <= this.endAddress); } @Override public String toString() { return this.startAddress + "-" + this.endAddress; } @Override public boolean equals(Object other) { if (other == this) return true; if (other == null && !(other instanceof CodeRange)) return false; CodeRange cr = (CodeRange) other; return (this.startAddress == cr.startAddress && this.endAddress == cr.endAddress); } @Override public int hashCode() { return 17 * startAddress + 13 * endAddress; } } private void toTries(Collection<Trap> traps, StmtVisitor stmtV, MethodImplementationBuilder builder, LabelAssigner labelAssigner) { // Original code: assume that the mapping startCodeAddress -> TryItem is enough for // a "code range", ignore different end Units / try lengths // That's definitely not enough since we can have two handlers H1, H2 with // H1:240-322, H2:242-322. There is no valid ordering for such overlapping traps // in dex. Current solution: If there is already a trap T' for a subrange of the // current trap T, merge T and T' on the fully range of T. This is not a 100% // correct since we extend traps over the requested range, but it's better than // the previous code that produced APKs which failed Dalvik's bytecode verification. // (Steven Arzt, 09.08.2013) // There are cases in which we need to split traps, e.g. in cases like // ( (t1) ... (t2) )<big catch all around it> where the all three handlers do // something different. That's why we run the TrapSplitter before we get here. // (Steven Arzt, 25.09.2013) Map<CodeRange, List<ExceptionHandler>> codeRangesToTryItem = new HashMap<CodeRange, List<ExceptionHandler>>(); for (Trap t : traps) { // see if there is old handler info at this code range Stmt beginStmt = (Stmt) t.getBeginUnit(); Stmt endStmt = (Stmt) t.getEndUnit(); int startCodeAddress = labelAssigner.getLabel(beginStmt).getCodeAddress(); int endCodeAddress = labelAssigner.getLabel(endStmt).getCodeAddress(); String exceptionType = SootToDexUtils.getDexTypeDescriptor(t.getException().getType()); ImmutableExceptionHandler exceptionHandler = new ImmutableExceptionHandler (exceptionType, labelAssigner.getLabel((Stmt) t.getHandlerUnit()).getCodeAddress()); List<ExceptionHandler> newHandlers = new ArrayList<ExceptionHandler>(); CodeRange range = new CodeRange(startCodeAddress, endCodeAddress); for (CodeRange r : codeRangesToTryItem.keySet()) { // Check whether this range is contained in some other range. We then extend our // trap over the bigger range containing this range if (r.containsRange(range)) { range.startAddress = r.startAddress; range.endAddress = r.endAddress; // copy the old handlers to a bigger array (the old one cannot be modified...) List<ExceptionHandler> oldHandlers = codeRangesToTryItem.get(r); if (oldHandlers != null) newHandlers.addAll(oldHandlers); break; } // Check whether the other range is contained in this range. In this case, // a smaller range is already in the list. We merge the two over the larger // range. else if (range.containsRange(r)) { range.startAddress = r.startAddress; range.endAddress = r.endAddress; // just use the newly found handler info List<ExceptionHandler> oldHandlers = codeRangesToTryItem.get(range); if (oldHandlers != null) newHandlers.addAll(oldHandlers); // remove the old range, the new one will be added anyway and contain // the merged handlers codeRangesToTryItem.remove(r); break; } } if (!newHandlers.contains(exceptionHandler)) newHandlers.add(exceptionHandler); codeRangesToTryItem.put(range, newHandlers); } for (CodeRange range : codeRangesToTryItem.keySet()) for (ExceptionHandler handler : codeRangesToTryItem.get(range)) { builder.addCatch(dexFile.internTypeReference(handler.getExceptionType()), labelAssigner.getLabelAtAddress(range.startAddress), labelAssigner.getLabelAtAddress(range.endAddress), labelAssigner.getLabelAtAddress(handler.getHandlerCodeAddress())); } } public void add(SootClass c) { if (c.isPhantom()) return; addAsClassDefItem(c); // save original APK for this class, needed to copy all the other files inside Map<String, File> dexClassIndex = SourceLocator.v().dexClassIndex(); if (dexClassIndex == null) { return; // no dex classes were loaded } File sourceForClass = dexClassIndex.get(c.getName()); if (sourceForClass == null || sourceForClass.getName().endsWith(".dex")) { return; // a class was written that was not a dex class or the class originates from a .dex file, not an APK } if (originalApk != null && !originalApk.equals(sourceForClass)) { throw new CompilationDeathException("multiple APKs as source of an application are not supported"); } originalApk = sourceForClass; } public void print() { String outputDir = SourceLocator.v().getOutputDir(); try { if (originalApk != null && Options.v().output_format() != Options.output_format_force_dex) { printApk(outputDir, originalApk); } else { String fileName = outputDir + File.separatorChar + CLASSES_DEX; G.v().out.println("Writing dex to: " + fileName); writeTo(fileName); } } catch (IOException e) { throw new CompilationDeathException("I/O exception while printing dex", e); } } }