changeset 111:3820379b34e8

Add dexfile_insert_class_relative()
author Thinker K.F. Li <thinker@codemud.net>
date Mon, 01 Aug 2011 22:23:55 +0800
parents 6380730a80b4
children 650dcb9c01ee
files paraspace/dexfile.py paraspace/injection.py paraspace/tests/injection_test.py
diffstat 3 files changed, 235 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/paraspace/dexfile.py	Mon Aug 01 15:00:29 2011 +0800
+++ b/paraspace/dexfile.py	Mon Aug 01 22:23:55 2011 +0800
@@ -1925,6 +1925,17 @@
             classdata.virtualMethods.items
         return methods
 
+    ## \brief Find all method IDs that is part of given type.
+    #
+    # \param typeid is ID of type that IDs of its methods will be returned.
+    # \return a list of method IDs.
+    #
+    def find_methodids_typeid(self, typeid):
+        methodids = [methodid
+                     for methodid in self.methodIds.items
+                     if methodid.classIdx == typeid]
+        return methodids
+
     ## \brief Dump content of a proto ID.
     @staticmethod
     def dump_protoid(protoid):
--- a/paraspace/injection.py	Mon Aug 01 15:00:29 2011 +0800
+++ b/paraspace/injection.py	Mon Aug 01 22:23:55 2011 +0800
@@ -94,12 +94,12 @@
     pass
 
 
-## \brief Clone a class definition item
+## \brief Clone a composite object.
 #
-# \param dex is the DEXFile that clazz is cloning for.
-# \param clazz is the class definition item that is cloning.
+# \param dex is the DEXFile that the composite object is cloning for.
+# \param comobj is composite object that is cloning.
 #
-def _clone_classdef(dex, clazz):
+def _clone_composite(dex, comobj):
     from copy import copy
     from paraspace.dexfile import _DEX_StringDataItem, _DEX_StringId
     from paraspace.dexfile import _DEX_TypeId
@@ -175,12 +175,7 @@
             pass
         pass
 
-    def has_classdef(clazz):
-        class_typeIds = set([classdef.classIdx
-                             for classdef in dex.classDefs.items])
-        return clazz.classIdx in class_typeIds
-
-    _travel_desc_relocatable(clazz, cloner, visit_log)
+    _travel_desc_relocatable(comobj, cloner, visit_log)
 
     merge_unique_strdata()
     merge_unique_strid()
@@ -195,12 +190,35 @@
         dex_append_obj_list(dex, obj)
         pass
     
+    clone = visit_log[id(comobj)]
+    return clone
+
+
+## \brief Clone a class definition item
+#
+# \param dex is the DEXFile that clazz is cloning for.
+# \param clazz is the class definition item that is cloning.
+# \return the cloning _DEX_ClassDef.
+#
+def _clone_classdef(dex, clazz):
+    from paraspace.dexfile import DEXFile_linked, _DEX_ClassDef
+    
+    def has_classdef(clazz):
+        classname = DEXFile_linked.get_classdef_name(clazz)
+        try:
+            dex.find_class_name(classname)
+        except ValueError:
+            return False
+        return True
+
+    assert isinstance(clazz, _DEX_ClassDef)
+
     if has_classdef(clazz):
         raise RuntimeError, \
             'clone a class \'%s\'that is already in the DEXFile' % \
             classdef_name(clazz)
     
-    clone = visit_log[id(clazz)]
+    clone = _clone_composite(dex, clazz)
     return clone
 
 
@@ -212,11 +230,131 @@
 # \param dex is a DEXFile_linked to insert the clone.
 # \param class_def is a class definition going to be cloned.
 #
-def dexfile_insert_class(dex, class_def):
-    clone = _clone_classdef(dex, class_def)
+def dexfile_insert_class(dex, classdef):
+    clone = _clone_classdef(dex, classdef)
     return clone
 
 
+## \brief Collect info of classes mentioned by the code of given class.
+def _find_class_relative(dex, classdef):
+    def classify_typeids_defined(dex, typeids):
+        classdefs = []
+        undef_typeids = []
+        for typeid in typeids:
+            try:
+                classdef = dex.find_class_typeid(typeid)
+            except ValueError:
+                undef_typeids.append(typeid)
+            else:
+                classdefs.append(classdef)
+                pass
+            pass
+        return classdefs, undef_typeids
+
+    typeidxs = collect_typeidxs_mentioned_by_class(dex, classdef)
+    typeids = [dex.find_typeid_idx(typeidx)
+               for typeidx in typeidxs]
+    
+    classdefs, typeids = classify_typeids_defined(dex, typeids)
+    
+    return classdefs, typeids
+
+
+def dexfile_insert_classdefs(dex_dst, dex_src, classdefs):
+    for classdef in classdefs:
+        dexfile_insert_class(dex_dst, classdef)
+        pass
+    pass
+
+
+## \brief Clone and insert a _DEX_TypeId to another DEXFile_linked.
+#
+# \param dex_dst is a DEXFile_linked where the cloning one is inserted.
+# \param dex_src is a DEXFile_linked where the cloned one is from.
+# \param typeid is a _DEX_TypeId that is cloned.
+# \return the cloning _DEX_TypeId.
+#
+def dexfile_insert_typeid(dex_dst, dex_src, typeid):
+    from paraspace.dexfile import _DEX_TypeId, DEXFile_linked
+    
+    assert isinstance(typeid, _DEX_TypeId)
+    
+    cloning = _clone_composite(dex_dst, typeid)
+    
+    methodids = dex_src.find_methodids_typeid(dex_src, typeid)
+    for methodid in methodids:
+        _clone_composite(dex_dst, methodid)
+        pass
+    
+    return cloning
+
+
+## \brief Clone and insert a list of _DEX_TypeId objects to a DEXFile_linked.
+def dexfile_insert_typeids(dex_dst, dex_src, typeids):
+    for typeid in typeids:
+        dexfile_insert_typeid(dex_dst, dex_src, typeid)
+        pass
+    pass
+
+
+## \brief Collects relative type IDs and classes definition for given class.
+def collect_classdef_relative(dex, classdef):
+    rel_classdefs = set([classdef])
+    rel_typeids = set()
+    
+    classdef_queue = [classdef]
+    while classdef_queue:
+        cur_classdef = classdef_queue.pop(0)
+        
+        classdefs, typeids = _find_class_relative(dex, classdef)
+        rel_typeids.update(typeids)
+        new_classdefs = list(set(classdefs) - rel_classdefs)
+        classdef_queue = classdef_queue + new_classdefs
+        rel_classdefs.update(new_classdefs)
+        pass
+    return rel_classdefs, rel_typeids
+
+
+## \brief Clone and insert given and relative classes into another DEXFile.
+#
+# \param dex_dst is a DEXFile_linked where the class will be inserted.
+# \param dex_src is a DEXFile_linked where the cloned class is from.
+# \param classdef is a _DEX_ClassDef that will be cloned.
+#
+def dexfile_insert_class_relative(dex_dst, dex_src, classdef):
+    from paraspace.dexfile import DEXFile_linked
+    
+    def classdef_not_in_dst(classdef):
+        classname = DEXFile_linked.get_classdef_name(classdef)
+        try:
+            dex_dst.find_class_name(classname)
+        except ValueError:
+            return True
+        return False
+
+    def typeid_not_in_dst(typeid):
+        typename = DEXFile_linked.get_typeid_name(typeid)
+        try:
+            dex_dst.find_typeid_name(typename)
+        except ValueError:
+            return True
+        return False
+    
+    relative_classdefs, relative_typeids = \
+        collect_classdef_relative(dex_src, classdef)
+    
+    inserting_classdefs = filter(classdef_not_in_dst, relative_classdefs)
+    inserting_typeids = filter(typeid_not_in_dst, relative_typeids)
+    
+    dexfile_insert_classdefs(dex_dst, dex_src, inserting_classdefs)
+    dexfile_insert_typeids(dex_dst, dex_src, inserting_typeids)
+
+    classname = DEXFile_linked.get_classdef_name(classdef)
+    cloning = dex_dst.find_class_name(classname)
+    
+    return cloning
+
+
 ## \brief Redirect types and methods for the code of given method.
 def method_redirect_types(dex, method, types_redir, methods_redir):
     from paraspace.dalvik_opcodes import decode_insn_blk, all_opcodes
--- a/paraspace/tests/injection_test.py	Mon Aug 01 15:00:29 2011 +0800
+++ b/paraspace/tests/injection_test.py	Mon Aug 01 22:23:55 2011 +0800
@@ -213,3 +213,76 @@
     typeid_name = DEXFile_linked.get_typeid_name(typeid)
     assert typeid_name == 'Ljava/io/File;'
     pass
+
+
+def dexfile_insert_class_relative_test():
+    from paraspace.dex_deptracker import prepare_dep_decls
+    from paraspace.injection import dexfile_insert_class_relative
+    
+    _install_dexfile_4_deptracker()
+    
+    all_dep_decls = prepare_dep_decls()
+    
+    srcdir = os.path.dirname(__file__)
+    srcroot = os.path.join(srcdir, '..', '..')
+    
+    helloworld_fn = os.path.join(srcroot, 'data', 'helloworld.dex')
+    helloworld_dex = dexfile.DEXFile.open(helloworld_fn)
+
+    classdef_map = _find_map(helloworld_dex, 0x0006)
+    saved_classdef_map_sz = classdef_map.size
+    saved_methodids_sz = len(helloworld_dex.methodIds.items)
+    
+    codeitems_map = _find_map(helloworld_dex, 0x2001)
+    saved_codeitems_sz = codeitems_map.size
+
+    helloworld_linked = \
+        dexfile.DEXFile_linked.build_dependencies(helloworld_dex,
+                                                  all_dep_decls)
+
+    fakefile_fn = os.path.join(srcroot, 'data', 'fakefile.dex')
+    fakefile_dex = dexfile.DEXFile.open(fakefile_fn)
+    fakefile_linked = \
+        dexfile.DEXFile_linked. \
+        build_dependencies(fakefile_dex, all_dep_decls)
+    
+    fakefile_def = fakefile_linked. \
+        find_class_name('Lcom/codemud/fakefile/fakefile;')
+
+    fakefile_data = fakefile_def.classDataOffRef.value
+    assert len(fakefile_data.directMethods.items) == 1
+    assert len(fakefile_data.virtualMethods.items) == 0
+    fakefile_dataheader = fakefile_data.header
+    assert fakefile_dataheader.directMethodsSize == 1
+    assert fakefile_dataheader.virtualMethodsSize == 0
+
+    clone = dexfile_insert_class_relative(helloworld_linked,
+                                          fakefile_linked, fakefile_def)
+    assert clone
+    assert clone != fakefile_def
+
+    helloworld_unlinked = helloworld_linked.get_unlinked()
+    assert helloworld_unlinked
+
+    # map size for classdef must be increased by 1
+    classdef_map = _find_map(helloworld_unlinked, 0x0006)
+    assert classdef_map.size == saved_classdef_map_sz + 1
+
+    classdata_map = _find_map(helloworld_unlinked, 0x2000)
+    assert classdata_map.size == classdef_map.size
+
+    # Check strings
+    strdatas = helloworld_unlinked.stringDataItems.items
+    strs = sorted([strdata.data.data for strdata in strdatas])
+    assert len(strs) == len(set(strs)) # uniquely
+    assert 'Lcom/codemud/fakefile/fakefile;' in strs
+
+    # Check Method List
+    methodids_map = _find_map(helloworld_unlinked, 0x0005) # method ids
+    assert methodids_map.size == len(helloworld_unlinked.methodIds.items)
+    assert methodids_map.size == saved_methodids_sz + 1
+
+    # Check Code item List
+    codeitems_map = _find_map(helloworld_unlinked, 0x2001)
+    assert codeitems_map.size == saved_codeitems_sz + 1
+    pass