Home | History | Annotate | Download | only in analysis
      1 /*
      2  * Copyright 2013, Google Inc.
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  *     * Redistributions of source code must retain the above copyright
     10  * notice, this list of conditions and the following disclaimer.
     11  *     * Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *     * Neither the name of Google Inc. nor the names of its
     16  * contributors may be used to endorse or promote products derived from
     17  * this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 package org.jf.dexlib2.analysis;
     33 
     34 import com.google.common.base.Supplier;
     35 import com.google.common.base.Suppliers;
     36 import com.google.common.cache.CacheBuilder;
     37 import com.google.common.cache.CacheLoader;
     38 import com.google.common.cache.LoadingCache;
     39 import com.google.common.collect.ImmutableList;
     40 import com.google.common.collect.ImmutableSet;
     41 import com.google.common.collect.Lists;
     42 import org.jf.dexlib2.DexFileFactory;
     43 import org.jf.dexlib2.DexFileFactory.DexFileNotFound;
     44 import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
     45 import org.jf.dexlib2.Opcodes;
     46 import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
     47 import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
     48 import org.jf.dexlib2.iface.ClassDef;
     49 import org.jf.dexlib2.iface.DexFile;
     50 import org.jf.dexlib2.immutable.ImmutableDexFile;
     51 import org.jf.util.ExceptionWithContext;
     52 
     53 import javax.annotation.Nonnull;
     54 import java.io.File;
     55 import java.io.IOException;
     56 import java.io.Serializable;
     57 import java.util.Arrays;
     58 import java.util.List;
     59 import java.util.regex.Matcher;
     60 import java.util.regex.Pattern;
     61 
     62 public class ClassPath {
     63     @Nonnull private final TypeProto unknownClass;
     64     @Nonnull private List<ClassProvider> classProviders;
     65     private final boolean checkPackagePrivateAccess;
     66     public final int oatVersion;
     67 
     68     public static final int NOT_ART = -1;
     69 
     70     /**
     71      * Creates a new ClassPath instance that can load classes from the given providers
     72      *
     73      * @param classProviders An iterable of ClassProviders. When loading a class, these providers will be searched in
     74      *                       order
     75      */
     76     public ClassPath(ClassProvider... classProviders) throws IOException {
     77         this(Arrays.asList(classProviders), false, NOT_ART);
     78     }
     79 
     80     /**
     81      * Creates a new ClassPath instance that can load classes from the given providers
     82      *
     83      * @param classProviders An iterable of ClassProviders. When loading a class, these providers will be searched in
     84      *                       order
     85      * @param checkPackagePrivateAccess Whether checkPackagePrivateAccess is needed, enabled for ONLY early API 17 by
     86      *                                  default
     87      * @param oatVersion The applicable oat version, or NOT_ART
     88      */
     89     public ClassPath(@Nonnull Iterable<? extends ClassProvider> classProviders, boolean checkPackagePrivateAccess,
     90                      int oatVersion) {
     91         // add fallbacks for certain special classes that must be present
     92         unknownClass = new UnknownClassProto(this);
     93         loadedClasses.put(unknownClass.getType(), unknownClass);
     94         this.checkPackagePrivateAccess = checkPackagePrivateAccess;
     95         this.oatVersion = oatVersion;
     96 
     97         loadPrimitiveType("Z");
     98         loadPrimitiveType("B");
     99         loadPrimitiveType("S");
    100         loadPrimitiveType("C");
    101         loadPrimitiveType("I");
    102         loadPrimitiveType("J");
    103         loadPrimitiveType("F");
    104         loadPrimitiveType("D");
    105         loadPrimitiveType("L");
    106 
    107         this.classProviders = Lists.newArrayList(classProviders);
    108         this.classProviders.add(getBasicClasses());
    109     }
    110 
    111     private void loadPrimitiveType(String type) {
    112         loadedClasses.put(type, new PrimitiveProto(this, type));
    113     }
    114 
    115     private static ClassProvider getBasicClasses() {
    116         // fallbacks for some special classes that we assume are present
    117         return new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), ImmutableSet.of(
    118                 new ReflectionClassDef(Class.class),
    119                 new ReflectionClassDef(Cloneable.class),
    120                 new ReflectionClassDef(Object.class),
    121                 new ReflectionClassDef(Serializable.class),
    122                 new ReflectionClassDef(String.class),
    123                 new ReflectionClassDef(Throwable.class))));
    124     }
    125 
    126     public boolean isArt() {
    127         return oatVersion != NOT_ART;
    128     }
    129 
    130     @Nonnull
    131     public TypeProto getClass(@Nonnull CharSequence type) {
    132         return loadedClasses.getUnchecked(type.toString());
    133     }
    134 
    135     private final CacheLoader<String, TypeProto> classLoader = new CacheLoader<String, TypeProto>() {
    136         @Override public TypeProto load(String type) throws Exception {
    137             if (type.charAt(0) == '[') {
    138                 return new ArrayProto(ClassPath.this, type);
    139             } else {
    140                 return new ClassProto(ClassPath.this, type);
    141             }
    142         }
    143     };
    144 
    145     @Nonnull private LoadingCache<String, TypeProto> loadedClasses = CacheBuilder.newBuilder().build(classLoader);
    146 
    147     @Nonnull
    148     public ClassDef getClassDef(String type) {
    149         for (ClassProvider provider: classProviders) {
    150             ClassDef classDef = provider.getClassDef(type);
    151             if (classDef != null) {
    152                 return classDef;
    153             }
    154         }
    155         throw new UnresolvedClassException("Could not resolve class %s", type);
    156     }
    157 
    158     @Nonnull
    159     public TypeProto getUnknownClass() {
    160         return unknownClass;
    161     }
    162 
    163     public boolean shouldCheckPackagePrivateAccess() {
    164         return checkPackagePrivateAccess;
    165     }
    166 
    167     @Nonnull
    168     public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
    169                                           int api, boolean experimental) {
    170         return fromClassPath(classPathDirs, classPath, dexFile, api, api == 17, experimental);
    171     }
    172 
    173     @Nonnull
    174     public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
    175                                           int api, boolean checkPackagePrivateAccess, boolean experimental) {
    176         List<ClassProvider> providers = Lists.newArrayList();
    177 
    178         int oatVersion = NOT_ART;
    179 
    180         for (String classPathEntry: classPath) {
    181             List<? extends DexFile> classPathDexFiles =
    182                     loadClassPathEntry(classPathDirs, classPathEntry, api, experimental);
    183             if (oatVersion == NOT_ART) {
    184                 for (DexFile classPathDexFile: classPathDexFiles) {
    185                     if (classPathDexFile instanceof OatDexFile) {
    186                         oatVersion = ((OatDexFile)classPathDexFile).getOatVersion();
    187                         break;
    188                     }
    189                 }
    190             }
    191             for (DexFile classPathDexFile: classPathDexFiles) {
    192                 providers.add(new DexClassProvider(classPathDexFile));
    193             }
    194         }
    195         providers.add(new DexClassProvider(dexFile));
    196         return new ClassPath(providers, checkPackagePrivateAccess, oatVersion);
    197     }
    198 
    199     @Nonnull
    200     public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
    201                                           int api, boolean checkPackagePrivateAccess, boolean experimental,
    202                                           int oatVersion) {
    203         List<ClassProvider> providers = Lists.newArrayList();
    204 
    205         for (String classPathEntry: classPath) {
    206             List<? extends DexFile> classPathDexFiles =
    207                     loadClassPathEntry(classPathDirs, classPathEntry, api, experimental);
    208             for (DexFile classPathDexFile: classPathDexFiles) {
    209                 providers.add(new DexClassProvider(classPathDexFile));
    210             }
    211         }
    212         providers.add(new DexClassProvider(dexFile));
    213         return new ClassPath(providers, checkPackagePrivateAccess, oatVersion);
    214     }
    215 
    216     private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
    217 
    218     @Nonnull
    219     private static List<? extends DexFile> loadClassPathEntry(@Nonnull Iterable<String> classPathDirs,
    220                                                               @Nonnull String bootClassPathEntry, int api,
    221                                                               boolean experimental) {
    222         File rawEntry = new File(bootClassPathEntry);
    223         // strip off the path - we only care about the filename
    224         String entryName = rawEntry.getName();
    225 
    226         // if it's a dalvik-cache entry, grab the name of the jar/apk
    227         if (entryName.endsWith("@classes.dex")) {
    228             Matcher m = dalvikCacheOdexPattern.matcher(entryName);
    229 
    230             if (!m.find()) {
    231                 throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", bootClassPathEntry));
    232             }
    233 
    234             entryName = m.group(1);
    235         }
    236 
    237         int extIndex = entryName.lastIndexOf(".");
    238 
    239         String baseEntryName;
    240         if (extIndex == -1) {
    241             baseEntryName = entryName;
    242         } else {
    243             baseEntryName = entryName.substring(0, extIndex);
    244         }
    245 
    246         for (String classPathDir: classPathDirs) {
    247             String[] extensions;
    248 
    249             if (entryName.endsWith(".oat")) {
    250                 extensions = new String[] { ".oat" };
    251             } else {
    252                 extensions = new String[] { "", ".odex", ".jar", ".apk", ".zip" };
    253             }
    254 
    255             for (String ext: extensions) {
    256                 File file = new File(classPathDir, baseEntryName + ext);
    257 
    258                 if (file.exists() && file.isFile()) {
    259                     if (!file.canRead()) {
    260                         System.err.println(String.format(
    261                                 "warning: cannot open %s for reading. Will continue looking.", file.getPath()));
    262                     } else {
    263                         try {
    264                             return ImmutableList.of(DexFileFactory.loadDexFile(file, api, experimental));
    265                         } catch (DexFileNotFound ex) {
    266                             // ignore and continue
    267                         } catch (MultipleDexFilesException ex) {
    268                             return ex.oatFile.getDexFiles();
    269                         } catch (Exception ex) {
    270                             throw ExceptionWithContext.withContext(ex,
    271                                     "Error while reading boot class path entry \"%s\"", bootClassPathEntry);
    272                         }
    273                     }
    274                 }
    275             }
    276         }
    277         throw new ExceptionWithContext("Cannot locate boot class path file %s", bootClassPathEntry);
    278     }
    279 
    280     private final Supplier<OdexedFieldInstructionMapper> fieldInstructionMapperSupplier = Suppliers.memoize(
    281             new Supplier<OdexedFieldInstructionMapper>() {
    282                 @Override public OdexedFieldInstructionMapper get() {
    283                     return new OdexedFieldInstructionMapper(isArt());
    284                 }
    285             });
    286 
    287     @Nonnull
    288     public OdexedFieldInstructionMapper getFieldInstructionMapper() {
    289         return fieldInstructionMapperSupplier.get();
    290     }
    291 }
    292