/* * [The "BSD licence"] * Copyright (c) 2010 Ben Gruver (JesusFreke) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.taobao.android.baksmali.adaptors; import com.google.common.collect.Lists; import com.taobao.android.apatch.ApkPatch; import com.taobao.android.apatch.annotation.MethodReplaceAnnotation; import com.taobao.android.apatch.utils.TypeGenUtil; import com.taobao.android.baksmali.util.ReferenceUtil; import com.taobao.android.object.DexDiffInfo; import org.jf.baksmali.baksmaliOptions; import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.dexbacked.DexBackedClassDef; import org.jf.dexlib2.dexbacked.DexBackedDexFile.InvalidItemIndex; import org.jf.dexlib2.iface.Annotation; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.Field; import org.jf.dexlib2.iface.Method; import org.jf.dexlib2.iface.MethodImplementation; import org.jf.dexlib2.iface.instruction.Instruction; import org.jf.dexlib2.iface.instruction.formats.Instruction21c; import org.jf.dexlib2.iface.reference.FieldReference; import org.jf.util.IndentingWriter; import org.jf.util.StringUtils; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.Nonnull; public class ClassDefinition { @Nonnull public final baksmaliOptions options; @Nonnull public final ClassDef classDef; @Nonnull private final HashSet<String> fieldsSetInStaticConstructor; @Nonnull public final boolean isScan; @Nonnull public final boolean fullMethod; protected boolean validationErrors; public ClassDefinition(@Nonnull baksmaliOptions options, @Nonnull ClassDef classDef, @Nonnull boolean isScan, @Nonnull boolean fullMethod) { this.options = options; this.classDef = classDef; this.isScan = isScan; this.fullMethod = fullMethod; fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor(); } public ClassDefinition(baksmaliOptions options, ClassDef classDef) { this.options = options; this.classDef = classDef; this.isScan = false; this.fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor(); this.fullMethod = true; } public boolean hadValidationErrors() { return validationErrors; } @Nonnull private HashSet<String> findFieldsSetInStaticConstructor() { HashSet<String> fieldsSetInStaticConstructor = new HashSet<String>(); for (Method method : classDef.getDirectMethods()) { if (method.getName().equals("<clinit>")) { MethodImplementation impl = method.getImplementation(); if (impl != null) { for (Instruction instruction : impl.getInstructions()) { switch (instruction.getOpcode()) { case SPUT: case SPUT_BOOLEAN: case SPUT_BYTE: case SPUT_CHAR: case SPUT_OBJECT: case SPUT_SHORT: case SPUT_WIDE: { Instruction21c ins = (Instruction21c) instruction; FieldReference fieldRef = null; try { fieldRef = (FieldReference) ins.getReference(); } catch (InvalidItemIndex ex) { // just ignore it for now. We'll deal with it later, when processing the instructions // themselves } if (fieldRef != null && fieldRef.getDefiningClass().equals((classDef.getType()))) { fieldsSetInStaticConstructor.add(ReferenceUtil.getShortFieldDescriptor(fieldRef)); } break; } } } } } } return fieldsSetInStaticConstructor; } public void writeTo(IndentingWriter writer) throws IOException { writeClass(writer); writeSuper(writer); writeSourceFile(writer); writeInterfaces(writer); writeAnnotations(writer); Set<String> staticFields = writeStaticFields(writer); writeInstanceFields(writer, staticFields); Set<String> directMethods = writeDirectMethods(writer); writeVirtualMethods(writer, directMethods); } private void writeClass(IndentingWriter writer) throws IOException { writer.write(".class "); writeAccessFlags(writer); writer.write(TypeGenUtil.newType(classDef.getType())); writer.write('\n'); } private void writeAccessFlags(IndentingWriter writer) throws IOException { for (AccessFlags accessFlag : AccessFlags.getAccessFlagsForClass(classDef.getAccessFlags())) { writer.write(accessFlag.toString()); writer.write(' '); } } private void writeSuper(IndentingWriter writer) throws IOException { String superClass = classDef.getSuperclass(); if (superClass != null) { writer.write(".super "); writer.write(superClass); writer.write('\n'); if (isScan) { // System.out.println("writeSuper: " + superClass + " " + classDef.getType()); ArrayList<String> derivedClasses = null; if (ApkPatch.superClasses.containsKey(superClass)) { derivedClasses = ApkPatch.superClasses.get(superClass); } else { derivedClasses = new ArrayList<String>(); ApkPatch.superClasses.put(superClass, derivedClasses); } derivedClasses.add(classDef.getType()); } } } private void writeSourceFile(IndentingWriter writer) throws IOException { String sourceFile = classDef.getSourceFile(); if (sourceFile != null) { writer.write(".source \""); StringUtils.writeEscapedString(writer, sourceFile); writer.write("\"\n"); } } private void writeInterfaces(IndentingWriter writer) throws IOException { List<String> interfaces = Lists.newArrayList(classDef.getInterfaces()); Collections.sort(interfaces); if (interfaces.size() != 0) { writer.write('\n'); writer.write("# interfaces\n"); for (String interfaceName : interfaces) { writer.write(".implements "); writer.write(interfaceName); writer.write('\n'); } } } private void writeAnnotations(IndentingWriter writer) throws IOException { Collection<? extends Annotation> classAnnotations = classDef.getAnnotations(); if (classAnnotations.size() != 0) { writer.write("\n\n"); writer.write("# annotations\n"); String containingClass = null; if (options.useImplicitReferences) { containingClass = classDef.getType(); } AnnotationFormatter.writeTo(writer, classAnnotations, containingClass); } } private Set<String> writeStaticFields(IndentingWriter writer) throws IOException { if (!fullMethod && !DexDiffInfo.addedClasses.contains(classDef)) { return null; } boolean wroteHeader = false; Set<String> writtenFields = new HashSet<String>(); Iterable<? extends Field> staticFields; if (classDef instanceof DexBackedClassDef) { staticFields = ((DexBackedClassDef) classDef).getStaticFields(false); } else { staticFields = classDef.getStaticFields(); } for (Field field : staticFields) { if (!wroteHeader) { writer.write("\n\n"); writer.write("# static fields"); wroteHeader = true; } writer.write('\n'); boolean setInStaticConstructor; IndentingWriter fieldWriter = writer; String fieldString = ReferenceUtil.getShortFieldDescriptor(field); if (!writtenFields.add(fieldString)) { writer.write("# duplicate field ignored\n"); fieldWriter = new CommentingIndentingWriter(writer); System.err.println(String.format("Ignoring duplicate field: %s->%s", classDef.getType(), fieldString)); setInStaticConstructor = false; } else { setInStaticConstructor = fieldsSetInStaticConstructor.contains(fieldString); } FieldDefinition.writeTo(options, fieldWriter, field, setInStaticConstructor); } return writtenFields; } private void writeInstanceFields(IndentingWriter writer, Set<String> staticFields) throws IOException { if (!fullMethod&& !DexDiffInfo.addedClasses.contains(classDef)) { return; } boolean wroteHeader = false; Set<String> writtenFields = new HashSet<String>(); Iterable<? extends Field> instanceFields; if (classDef instanceof DexBackedClassDef) { instanceFields = ((DexBackedClassDef) classDef).getInstanceFields(false); } else { instanceFields = classDef.getInstanceFields(); } for (Field field : instanceFields) { if (!wroteHeader) { writer.write("\n\n"); writer.write("# instance fields"); wroteHeader = true; } writer.write('\n'); IndentingWriter fieldWriter = writer; String fieldString = ReferenceUtil.getShortFieldDescriptor(field); if (!writtenFields.add(fieldString)) { writer.write("# duplicate field ignored\n"); fieldWriter = new CommentingIndentingWriter(writer); System.err.println(String.format("Ignoring duplicate field: %s->%s", classDef.getType(), fieldString)); } else if (staticFields.contains(fieldString)) { System.err.println(String.format("Duplicate static+instance field found: %s->%s", classDef.getType(), fieldString)); System.err.println("You will need to rename one of these fields, including all references."); writer.write("# There is both a static and instance field with this signature.\n" + "# You will need to rename one of these fields, including all references.\n"); } FieldDefinition.writeTo(options, fieldWriter, field, false); } } private Set<String> writeDirectMethods(IndentingWriter writer) throws IOException { boolean wroteHeader = false; Set<String> writtenMethods = new HashSet<String>(); Iterable<? extends Method> directMethods; Set<? extends Method> modifieds = null; if (classDef instanceof DexBackedClassDef) { directMethods = ((DexBackedClassDef) classDef).getDirectMethods(false); modifieds = (Set<? extends Method>) DexDiffInfo.modifiedMethods; } else { directMethods = classDef.getDirectMethods(); } MethodReplaceAnnotation replaceAnnotaion; for (Method method : directMethods) { if (!fullMethod && !DexDiffInfo.addedClasses.contains(classDef)) { if (!modifieds.contains(method) && !DexDiffInfo.addedMethods.contains(method)) { continue; } } if (!wroteHeader) { writer.write("\n\n"); writer.write("# direct methods"); wroteHeader = true; } writer.write('\n'); // TODO: check for method validation errors String methodString = ReferenceUtil.getMethodDescriptor(method, true); IndentingWriter methodWriter = writer; if (!writtenMethods.add(methodString)) { writer.write("# duplicate method ignored\n"); methodWriter = new CommentingIndentingWriter(writer); } MethodImplementation methodImpl = method.getImplementation(); if (methodImpl == null) { MethodDefinition.writeEmptyMethodTo(methodWriter, method, options); } else { MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl); methodDefinition.setFullMethod(fullMethod); methodDefinition.writeTo(methodWriter); } } return writtenMethods; } private void writeVirtualMethods(IndentingWriter writer, Set<String> directMethods) throws IOException { boolean wroteHeader = false; Set<String> writtenMethods = new HashSet<String>(); Iterable<? extends Method> virtualMethods; Set<? extends Method> modifieds = null; if (classDef instanceof DexBackedClassDef) { virtualMethods = ((DexBackedClassDef) classDef).getVirtualMethods(false); modifieds = (Set<? extends Method>) DexDiffInfo.modifiedMethods; } else { virtualMethods = classDef.getVirtualMethods(); } MethodReplaceAnnotation replaceAnnotaion; for (Method method : virtualMethods) { if (!fullMethod && !DexDiffInfo.addedClasses.contains(classDef)) { if (!modifieds.contains(method) && !DexDiffInfo.addedMethods.contains(method)) { continue; } } if (!wroteHeader) { writer.write("\n\n"); writer.write("# virtual methods"); wroteHeader = true; } writer.write('\n'); // TODO: check for method validation errors String methodString = ReferenceUtil.getMethodDescriptor(method, true); IndentingWriter methodWriter = writer; if (!writtenMethods.add(methodString)) { writer.write("# duplicate method ignored\n"); methodWriter = new CommentingIndentingWriter(writer); } else if (directMethods.contains(methodString)) { writer.write("# There is both a direct and virtual method with this signature.\n" + "# You will need to rename one of these methods, including all references.\n"); System.err.println(String.format("Duplicate direct+virtual method found: %s->%s", classDef.getType(), methodString)); System.err.println("You will need to rename one of these methods, including all references."); } MethodImplementation methodImpl = method.getImplementation(); if (methodImpl == null) { MethodDefinition.writeEmptyMethodTo(methodWriter, method, options); } else { MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl); methodDefinition.writeTo(methodWriter); } } } public ClassDef getClassDef() { return classDef; } }