Ensure class permissions are valid when resolving a virtual method while deodexing
diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java
index 2e398f6..189a252 100644
--- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java
+++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java
@@ -593,6 +593,10 @@
 
         private final int classDepth;
 
+        // classes can only be public or package-private. Internally, any private/protected inner class is actually
+        // package-private.
+        private final boolean isPublic;
+
         private final VirtualMethod[] vtable;
 
         //this maps a method name of the form method(III)Ljava/lang/String; to an integer
@@ -635,6 +639,7 @@
                 implementedInterfaces.add(ClassPath.getClassDef("Ljava/lang/Cloneable;"));
                 implementedInterfaces.add(ClassPath.getClassDef("Ljava/io/Serializable;"));
                 isInterface = false;
+                isPublic = true;
 
                 vtable = superclass.vtable;
                 methodLookup = superclass.methodLookup;
@@ -652,6 +657,7 @@
                 this.superclass = null;
                 implementedInterfaces = null;
                 isInterface = false;
+                isPublic = true;
                 vtable = null;
                 methodLookup = null;
                 instanceFields = null;
@@ -665,6 +671,7 @@
                 this.superclass = ClassPath.getClassDef("Ljava/lang/Object;");
                 implementedInterfaces = new TreeSet<ClassDef>();
                 isInterface = false;
+                isPublic = true;
 
                 vtable = superclass.vtable;
                 methodLookup = superclass.methodLookup;
@@ -679,6 +686,7 @@
 
         protected ClassDef(UnresolvedClassInfo classInfo)  {
             classType = classInfo.classType;
+            isPublic = classInfo.isPublic;
             isInterface = classInfo.isInterface;
 
             superclass = loadSuperclass(classInfo);
@@ -732,6 +740,10 @@
             return this.isInterface;
         }
 
+        public boolean isPublic() {
+            return this.isPublic;
+        }
+
         public boolean extendsClass(ClassDef superclassDef) {
             if (superclassDef == null) {
                 return false;
@@ -1220,6 +1232,7 @@
     private static class UnresolvedClassInfo {
         public final String dexFilePath;
         public final String classType;
+        public final boolean isPublic;
         public final boolean isInterface;
         public final String superclassType;
         public final String[] interfaces;
@@ -1233,6 +1246,7 @@
 
             classType = classDefItem.getClassType().getTypeDescriptor();
 
+            isPublic = (classDefItem.getAccessFlags() & AccessFlags.PUBLIC.getValue()) != 0;
             isInterface = (classDefItem.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0;
 
             TypeIdItem superclassType = classDefItem.getSuperclass();
diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java
index be689cc..f31ec56 100644
--- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java
+++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java
@@ -75,8 +75,9 @@
 
     private static final Pattern shortMethodPattern = Pattern.compile("([^(]+)\\(([^)]*)\\)(.+)");
 
-    public MethodIdItem lookupVirtualMethod(ClassPath.ClassDef classDef, int methodIndex) {
-        String method = classDef.getVirtualMethod(methodIndex);
+    public MethodIdItem lookupVirtualMethod(ClassPath.ClassDef accessingClass, ClassPath.ClassDef definingClass,
+                                            int methodIndex) {
+        String method = definingClass.getVirtualMethod(methodIndex);
         if (method == null) {
             return null;
         }
@@ -91,20 +92,20 @@
         String methodParams = m.group(2);
         String methodRet = m.group(3);
 
-        if (classDef instanceof ClassPath.UnresolvedClassDef) {
+        if (definingClass instanceof ClassPath.UnresolvedClassDef) {
             //if this is an unresolved class, the only way getVirtualMethod could have found a method is if the virtual
             //method being looked up was a method on java.lang.Object.
-            classDef = ClassPath.getClassDef("Ljava/lang/Object;");
-        } else if (classDef.isInterface()) {
-            classDef = classDef.getSuperclass();
-            assert classDef != null;
+            definingClass = ClassPath.getClassDef("Ljava/lang/Object;");
+        } else if (definingClass.isInterface()) {
+            definingClass = definingClass.getSuperclass();
+            assert definingClass != null;
         }
 
-        return parseAndResolveMethod(classDef, methodName, methodParams, methodRet);
+        return parseAndResolveMethod(accessingClass, definingClass, methodName, methodParams, methodRet);
     }
 
-    private MethodIdItem parseAndResolveMethod(ClassPath.ClassDef classDef, String methodName, String methodParams,
-                                               String methodRet) {
+    private MethodIdItem parseAndResolveMethod(ClassPath.ClassDef accessingClass, ClassPath.ClassDef definingClass,
+                                               String methodName, String methodParams, String methodRet) {
         StringIdItem methodNameItem = StringIdItem.lookupStringIdItem(dexFile, methodName);
         if (methodNameItem == null) {
             return null;
@@ -197,14 +198,15 @@
             return null;
         }
 
-        ClassPath.ClassDef methodClassDef = classDef;
+        ClassPath.ClassDef methodClassDef = definingClass;
 
         do {
             TypeIdItem classTypeItem = TypeIdItem.lookupTypeIdItem(dexFile, methodClassDef.getClassType());
 
+
             if (classTypeItem != null) {
                 MethodIdItem methodIdItem = MethodIdItem.lookupMethodIdItem(dexFile, classTypeItem, protoItem, methodNameItem);
-                if (methodIdItem != null) {
+                if (methodIdItem != null && checkClassAccess(accessingClass, methodClassDef)) {
                     return methodIdItem;
                 }
             }
@@ -214,6 +216,19 @@
         return null;
     }
 
+    private static boolean checkClassAccess(ClassPath.ClassDef accessingClass, ClassPath.ClassDef definingClass) {
+        return definingClass.isPublic() ||
+                getPackage(accessingClass.getClassType()).equals(getPackage(definingClass.getClassType()));
+    }
+
+    private static String getPackage(String classRef) {
+        int lastSlash = classRef.lastIndexOf('/');
+        if (lastSlash < 0) {
+            return "";
+        }
+        return classRef.substring(1, lastSlash);
+    }
+
     private FieldIdItem parseAndResolveField(ClassPath.ClassDef classDef, ClassPath.FieldDef field) {
         String definingClass = field.definingClass;
         String fieldName = field.name;
@@ -283,7 +298,8 @@
         private void loadMethod(DeodexUtil deodexUtil) {
             ClassPath.ClassDef classDef = ClassPath.getClassDef(classType);
 
-            this.methodIdItem = deodexUtil.parseAndResolveMethod(classDef, methodName, parameters, returnType);
+            this.methodIdItem = deodexUtil.parseAndResolveMethod(classDef, classDef, methodName, parameters,
+                    returnType);
         }
 
         public String getMethodString() {
diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/MethodAnalyzer.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/MethodAnalyzer.java
index 17bf86a..64f8ab6 100644
--- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/MethodAnalyzer.java
+++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/MethodAnalyzer.java
@@ -3621,12 +3621,16 @@
         }
 
         MethodIdItem methodIdItem = null;
+        ClassPath.ClassDef accessingClass =
+                ClassPath.getClassDef(this.encodedMethod.method.getContainingClass(), false);
+        if (accessingClass == null) {
+            throw new ExceptionWithContext(String.format("Could not find ClassDef for current class: %s",
+                    this.encodedMethod.method.getContainingClass()));
+        }
         if (isSuper) {
-            ClassPath.ClassDef classDef = ClassPath.getClassDef(this.encodedMethod.method.getContainingClass(), false);
-            assert classDef != null;
-
-            if (classDef.getSuperclass() != null) {
-                methodIdItem = deodexUtil.lookupVirtualMethod(classDef.getSuperclass(), methodIndex);
+            if (accessingClass.getSuperclass() != null) {
+                methodIdItem = deodexUtil.lookupVirtualMethod(accessingClass, accessingClass.getSuperclass(),
+                        methodIndex);
             }
 
             if (methodIdItem == null) {
@@ -3634,10 +3638,10 @@
                 //of from the superclass (although the superclass method is still what would actually be called).
                 //And so the MethodIdItem for the superclass method may not be in the dex file. Let's try to get the
                 //MethodIdItem for the method in the current class instead
-                methodIdItem = deodexUtil.lookupVirtualMethod(classDef, methodIndex);
+                methodIdItem = deodexUtil.lookupVirtualMethod(accessingClass, accessingClass, methodIndex);
             }
         } else{
-            methodIdItem = deodexUtil.lookupVirtualMethod(objectRegisterType.type, methodIndex);
+            methodIdItem = deodexUtil.lookupVirtualMethod(accessingClass, objectRegisterType.type, methodIndex);
         }
 
         if (methodIdItem == null) {