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.cache.CacheBuilder;
     35 import com.google.common.cache.CacheLoader;
     36 import com.google.common.cache.LoadingCache;
     37 import com.google.common.collect.ImmutableSet;
     38 import com.google.common.collect.Iterables;
     39 import com.google.common.collect.Lists;
     40 import com.google.common.collect.Maps;
     41 import org.jf.dexlib2.DexFileFactory;
     42 import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
     43 import org.jf.dexlib2.iface.ClassDef;
     44 import org.jf.dexlib2.iface.DexFile;
     45 import org.jf.dexlib2.immutable.ImmutableDexFile;
     46 import org.jf.util.ExceptionWithContext;
     47 
     48 import javax.annotation.Nonnull;
     49 import java.io.File;
     50 import java.io.IOException;
     51 import java.io.Serializable;
     52 import java.util.ArrayList;
     53 import java.util.HashMap;
     54 import java.util.regex.Matcher;
     55 import java.util.regex.Pattern;
     56 
     57 public class ClassPath {
     58     @Nonnull private final TypeProto unknownClass;
     59     @Nonnull private HashMap<String, ClassDef> availableClasses = Maps.newHashMap();
     60     private boolean checkPackagePrivateAccess;
     61 
     62     /**
     63      * Creates a new ClassPath instance that can load classes from the given dex files
     64      *
     65      * @param classPath An array of DexFile objects. When loading a class, these dex files will be searched in order
     66      */
     67     public ClassPath(DexFile... classPath) throws IOException {
     68         this(Lists.newArrayList(classPath), 15);
     69     }
     70 
     71     /**
     72      * Creates a new ClassPath instance that can load classes from the given dex files
     73      *
     74      * @param classPath An iterable of DexFile objects. When loading a class, these dex files will be searched in order
     75      * @param api API level
     76      */
     77     public ClassPath(@Nonnull Iterable<DexFile> classPath, int api) {
     78         this(Lists.newArrayList(classPath), api == 17);
     79     }
     80 
     81     /**
     82      * Creates a new ClassPath instance that can load classes from the given dex files
     83      *
     84      * @param classPath An iterable of DexFile objects. When loading a class, these dex files will be searched in order
     85      * @param checkPackagePrivateAccess Whether checkPackagePrivateAccess is needed, enabled for ONLY early API 17 by default
     86      */
     87     public ClassPath(@Nonnull Iterable<DexFile> classPath, boolean checkPackagePrivateAccess) {
     88         // add fallbacks for certain special classes that must be present
     89         Iterable<DexFile> dexFiles = Iterables.concat(classPath, Lists.newArrayList(getBasicClasses()));
     90 
     91         unknownClass = new UnknownClassProto(this);
     92         loadedClasses.put(unknownClass.getType(), unknownClass);
     93         this.checkPackagePrivateAccess = checkPackagePrivateAccess;
     94 
     95         loadPrimitiveType("Z");
     96         loadPrimitiveType("B");
     97         loadPrimitiveType("S");
     98         loadPrimitiveType("C");
     99         loadPrimitiveType("I");
    100         loadPrimitiveType("J");
    101         loadPrimitiveType("F");
    102         loadPrimitiveType("D");
    103         loadPrimitiveType("L");
    104 
    105         for (DexFile dexFile: dexFiles) {
    106             for (ClassDef classDef: dexFile.getClasses()) {
    107                 ClassDef prev = availableClasses.get(classDef.getType());
    108                 if (prev == null) {
    109                     availableClasses.put(classDef.getType(), classDef);
    110                 }
    111             }
    112         }
    113     }
    114 
    115     private void loadPrimitiveType(String type) {
    116         loadedClasses.put(type, new PrimitiveProto(this, type));
    117     }
    118 
    119     private static DexFile getBasicClasses() {
    120         // fallbacks for some special classes that we assume are present
    121         return new ImmutableDexFile(ImmutableSet.of(
    122                 new ReflectionClassDef(Class.class),
    123                 new ReflectionClassDef(Cloneable.class),
    124                 new ReflectionClassDef(Object.class),
    125                 new ReflectionClassDef(Serializable.class),
    126                 new ReflectionClassDef(String.class),
    127                 new ReflectionClassDef(Throwable.class)));
    128     }
    129 
    130     @Nonnull
    131     public TypeProto getClass(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         ClassDef ret = availableClasses.get(type);
    150         if (ret == null) {
    151             throw new UnresolvedClassException("Could not resolve class %s", type);
    152         }
    153         return ret;
    154     }
    155 
    156     @Nonnull
    157     public TypeProto getUnknownClass() {
    158         return unknownClass;
    159     }
    160 
    161     public boolean shouldCheckPackagePrivateAccess() {
    162         return checkPackagePrivateAccess;
    163     }
    164 
    165     @Nonnull
    166     public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
    167                                           int api) {
    168         return fromClassPath(classPathDirs, classPath, dexFile, api, api == 17);
    169     }
    170 
    171     @Nonnull
    172     public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
    173                                           int api, boolean checkPackagePrivateAccess) {
    174         ArrayList<DexFile> dexFiles = Lists.newArrayList();
    175 
    176         for (String classPathEntry: classPath) {
    177             try {
    178                 dexFiles.add(loadClassPathEntry(classPathDirs, classPathEntry, api));
    179             } catch (ExceptionWithContext e){}
    180         }
    181         dexFiles.add(dexFile);
    182         return new ClassPath(dexFiles, checkPackagePrivateAccess);
    183     }
    184 
    185     private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
    186 
    187     @Nonnull
    188     private static DexFile loadClassPathEntry(@Nonnull Iterable<String> classPathDirs,
    189                                               @Nonnull String bootClassPathEntry, int api) {
    190         File rawEntry = new File(bootClassPathEntry);
    191         // strip off the path - we only care about the filename
    192         String entryName = rawEntry.getName();
    193 
    194         // if it's a dalvik-cache entry, grab the name of the jar/apk
    195         if (entryName.endsWith("@classes.dex")) {
    196             Matcher m = dalvikCacheOdexPattern.matcher(entryName);
    197 
    198             if (!m.find()) {
    199                 throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", bootClassPathEntry));
    200             }
    201 
    202             entryName = m.group(1);
    203         }
    204 
    205         int extIndex = entryName.lastIndexOf(".");
    206 
    207         String baseEntryName;
    208         if (extIndex == -1) {
    209             baseEntryName = entryName;
    210         } else {
    211             baseEntryName = entryName.substring(0, extIndex);
    212         }
    213 
    214         for (String classPathDir: classPathDirs) {
    215             for (String ext: new String[]{"", ".odex", ".jar", ".apk", ".zip"}) {
    216                 File file = new File(classPathDir, baseEntryName + ext);
    217 
    218                 if (file.exists() && file.isFile()) {
    219                     if (!file.canRead()) {
    220                         System.err.println(String.format(
    221                                 "warning: cannot open %s for reading. Will continue looking.", file.getPath()));
    222                     } else {
    223                         try {
    224                             return DexFileFactory.loadDexFile(file, api);
    225                         } catch (DexFileFactory.NoClassesDexException ex) {
    226                             // ignore and continue
    227                         } catch (Exception ex) {
    228                             throw ExceptionWithContext.withContext(ex,
    229                                     "Error while reading boot class path entry \"%s\"", bootClassPathEntry);
    230                         }
    231                     }
    232                 }
    233             }
    234         }
    235         throw new ExceptionWithContext("Cannot locate boot class path file %s", bootClassPathEntry);
    236     }
    237 }
    238