Repository: androguard/androguard
Issue: remove all asserts

actually using asserts for checks is bad. If you use optimization, all asserts will be removed, thus broken files will not be rejected during parsing. 
Therefore all asserts should be removed and replaced by normal checks.

Changed files:
tests/test_apk.py, androguard/core/bytecodes/apk.py, androguard/decompiler/dad/tests/rpo_test.py, androguard/decompiler/dad/util.py, androguard/decompiler/dad/ast.py, androguard/core/bytecodes/dvm.py, androguard/decompiler/dad/decompile.py, androguard/core/bytecodes/mutf8.py, androguard/decompiler/dad/writer.py, androguard/decompiler/dad/tests/dataflow_test.py, androguard/decompiler/dad/graph.py, androguard/decompiler/decompiler.py, tests/test_axml.py, androguard/decompiler/dad/tests/dominator_test.py, androguard/cli/entry_points.py, androguard/core/bytecodes/axml/__init__.py, androguard/core/analysis/analysis.py

diff --git a/androguard/cli/entry_points.py b/androguard/cli/entry_points.py
index 77065492..9bc1520c 100755
--- a/androguard/cli/entry_points.py
+++ b/androguard/cli/entry_points.py
@@ -214,8 +214,8 @@ def arsc(input_,
 
         # All the information is in the config.
         # we simply need to get the actual value of the entry
-        for config, entry in arscobj.get_res_configs(i_id):
-            print("{} = '{}'".format(config.get_qualifier() if not config.is_default() else "<default>", entry.get_key_data()))
+        for config, entry in arscobj.get_resolved_res_configs(i_id):
+            print("{} = '{}'".format(config.get_qualifier() if not config.is_default() else "<default>", entry))
 
         sys.exit(0)
 
diff --git a/androguard/core/analysis/analysis.py b/androguard/core/analysis/analysis.py
index ac49a78b..b7210576 100644
--- a/androguard/core/analysis/analysis.py
+++ b/androguard/core/analysis/analysis.py
@@ -1250,7 +1250,7 @@ class Analysis:
 
     def get_classes(self):
         """
-        Returns a list of `ClassAnalysis` objects
+        Returns a list of :class:`ClassAnalysis` objects
 
         Returns both internal and external classes (if any)
 
diff --git a/androguard/core/bytecodes/apk.py b/androguard/core/bytecodes/apk.py
index d946c5b8..23344d0a 100644
--- a/androguard/core/bytecodes/apk.py
+++ b/androguard/core/bytecodes/apk.py
@@ -1404,24 +1404,27 @@ class APK(object):
                 # before the end of central dir...
 
                 # These things should not happen for APKs
-                assert this_disk == 0, "Not sure what to do with multi disk ZIP!"
-                assert disk_central == 0, "Not sure what to do with multi disk ZIP!"
+                if this_disk != 0:
+                    raise BrokenAPKError("Not sure what to do with multi disk ZIP!")
+                if disk_central != 0:
+                    raise BrokenAPKError("Not sure what to do with multi disk ZIP!")
                 break
             f.seek(-4, io.SEEK_CUR)
 
         if not offset_central:
             return
-        
+
         f.seek(offset_central)
         r, = unpack('<4s', f.read(4))
         f.seek(-4, io.SEEK_CUR)
-        assert r == self._PK_CENTRAL_DIR, "No Central Dir at specified offset"
+        if r != self._PK_CENTRAL_DIR:
+            raise BrokenAPKError("No Central Dir at specified offset")
 
         # Go back and check if we have a magic
         end_offset = f.tell()
         f.seek(-24, io.SEEK_CUR)
         size_of_block, magic = unpack('<Q16s', f.read(24))
-        
+
         self._is_signed_v2 = False
         self._is_signed_v3 = False
 
@@ -1431,7 +1434,8 @@ class APK(object):
         # go back size_of_blocks + 8 and read size_of_block again
         f.seek(-(size_of_block + 8), io.SEEK_CUR)
         size_of_block_start, = unpack("<Q", f.read(8))
-        assert size_of_block_start == size_of_block, "Sizes at beginning and and does not match!"
+        if size_of_block_start != size_of_block:
+            raise BrokenAPKError("Sizes at beginning and and does not match!")
 
         # Store all blocks
         while f.tell() < end_offset - 24:
@@ -1478,15 +1482,14 @@ class APK(object):
         #    * maxSDK
         #    * signatures
         #    * publickey
-        
         size_sequence = self.read_uint32_le(block)
-        assert size_sequence + 4 == len(block_bytes), "size of sequence and blocksize does not match"
-        
-        while block.tell() < len(block_bytes):
+        if size_sequence + 4 != len(block_bytes):
+            raise BrokenAPKError("size of sequence and blocksize does not match")
 
+        while block.tell() < len(block_bytes):
             off_signer = block.tell()
             size_signer = self.read_uint32_le(block)
-            
+
             # read whole signed data, since we might to parse
             # content within the signed data, and mess up offset
             len_signed_data = self.read_uint32_le(block)
@@ -1565,7 +1568,7 @@ class APK(object):
         block_bytes = self._v2_blocks[self._APK_SIG_KEY_V2_SIGNATURE]
         block = io.BytesIO(block_bytes)
         view = block.getvalue()
-        
+
         # V2 signature Block data format:
         #
         # * signer:
@@ -1579,13 +1582,12 @@ class APK(object):
         #    * publickey
 
         size_sequence = self.read_uint32_le(block)
-        assert size_sequence + 4 == len(block_bytes), "size of sequence and blocksize does not match"
+        if size_sequence + 4 != len(block_bytes):
+            raise BrokenAPKError("size of sequence and blocksize does not match")
 
         while block.tell() < len(block_bytes):
-
             off_signer = block.tell()
             size_signer = self.read_uint32_le(block)
-            
 
             # read whole signed data, since we might to parse
             # content within the signed data, and mess up offset
@@ -1593,7 +1595,6 @@ class APK(object):
             signed_data_bytes = block.read(len_signed_data)
             signed_data = io.BytesIO(signed_data_bytes)
 
-                
             # Digests
             len_digests = self.read_uint32_le(signed_data)
             raw_digests = signed_data.read(len_digests)
@@ -1726,7 +1727,6 @@ class APK(object):
         Therefore this is just a list of all certificates found in all signers.
         """
         return [ x509.Certificate.load(cert) for cert in self.get_certificates_der_v2()]
-        
 
     def get_certificates_v1(self):
         """
@@ -1866,7 +1866,6 @@ class APK(object):
                 show_Certificate(c)
 
 
-
 def show_Certificate(cert, short=False):
     """
         Print Fingerprints, Issuer and Subject of an X509 Certificate.
@@ -1877,8 +1876,6 @@ def show_Certificate(cert, short=False):
         :type cert: :class:`asn1crypto.x509.Certificate`
         :type short: Boolean
     """
-
-
     print("SHA1 Fingerprint: {}".format(cert.sha1_fingerprint))
     print("SHA256 Fingerprint: {}".format(cert.sha256_fingerprint))
     print("Issuer: {}".format(get_certificate_name_string(cert.issuer.native, short=short)))
@@ -1897,6 +1894,7 @@ def ensure_final_value(packageName, arsc, value):
     if value:
         returnValue = value
         if value[0] == '@':
+            # TODO: @packagename:DEADBEEF is not supported here!
             try:  # can be a literal value or a resId
                 res_id = int('0x' + value[1:], 16)
                 res_id = arsc.get_id(packageName, res_id)[1]
@@ -1959,3 +1957,4 @@ def get_apkid(apkfile):
         versionName = ''  # versionName is expected to always be a str
 
     return appid, versionCode, versionName.strip('\\0')
+
diff --git a/androguard/core/bytecodes/axml/__init__.py b/androguard/core/bytecodes/axml/__init__.py
index e6aff1e0..6fd5e00f 100644
--- a/androguard/core/bytecodes/axml/__init__.py
+++ b/androguard/core/bytecodes/axml/__init__.py
@@ -88,6 +88,11 @@ FRACTION_UNITS = ["%", "%p"]
 COMPLEX_UNIT_MASK = 0x0F
 
 
+class ResParserError(Exception):
+    """Exception for the parsers"""
+    pass
+
+
 def complexToFloat(xcomplex):
     """
     Convert a complex unit into float
@@ -236,8 +241,8 @@ class StringBlock(object):
 
         data = self.m_charbuff[offset: offset + encoded_bytes]
 
-        assert self.m_charbuff[offset + encoded_bytes] == 0, \\
-            "UTF-8 String is not null terminated! At offset={}".format(offset)
+        if self.m_charbuff[offset + encoded_bytes] != 0:
+            raise ResParserError("UTF-8 String is not null terminated! At offset={}".format(offset))
 
         return self._decode_bytes(data, 'utf-8', str_len)
 
@@ -256,8 +261,8 @@ class StringBlock(object):
 
         data = self.m_charbuff[offset: offset + encoded_bytes]
 
-        assert self.m_charbuff[offset + encoded_bytes:offset + encoded_bytes + 2] == b"\\x00\\x00", \\
-            "UTF-16 String is not null terminated! At offset={}".format(offset)
+        if self.m_charbuff[offset + encoded_bytes:offset + encoded_bytes + 2] != b"\\x00\\x00":
+            raise ResParserError("UTF-16 String is not null terminated! At offset={}".format(offset))
 
         return self._decode_bytes(data, 'utf-16', str_len)
 
@@ -308,6 +313,7 @@ class StringBlock(object):
             length = length1
             size = sizeof_char
 
+        # These are true asserts, as the size should never be less than the values
         if sizeof_char == 1:
             assert length <= 0x7FFF, "length of UTF-8 string is too large! At offset={}".format(offset)
         else:
@@ -388,7 +394,7 @@ class AXMLParser(object):
 
         try:
             axml_header = ARSCHeader(self.buff)
-        except AssertionError as e:
+        except ResParserError as e:
             log.error("Error parsing first resource header: %s", e)
             self._valid = False
             return
@@ -427,7 +433,7 @@ class AXMLParser(object):
         # Now we parse the STRING POOL
         try:
             header = ARSCHeader(self.buff)
-        except AssertionError as e:
+        except ResParserError as e:
             log.error("Error parsing resource header of string pool: %s", e)
             self._valid = False
             return
@@ -486,7 +492,7 @@ class AXMLParser(object):
             # Again, we read an ARSCHeader
             try:
                 h = ARSCHeader(self.buff)
-            except AssertionError as e:
+            except ResParserError as e:
                 log.error("Error parsing resource header: %s", e)
                 self._valid = False
                 return
@@ -1215,7 +1221,8 @@ class ARSCParser(object):
                 self.stringpool_main = StringBlock(self.buff, res_header)
 
             elif res_header.type == RES_TABLE_PACKAGE_TYPE:
-                assert len(self.packages) < self.packageCount, "Got more packages than expected"
+                if len(self.packages) > self.packageCount:
+                    raise ResParserError("Got more packages ({}) than expected ({})".format(len(self.packages), self.packageCount))
 
                 current_package = ARSCResTablePackage(self.buff, res_header)
                 package_name = current_package.get_name()
@@ -1226,15 +1233,17 @@ class ARSCParser(object):
                 # After the Header, we have the resource type symbol table
                 self.buff.set_idx(current_package.header.start + current_package.typeStrings)
                 type_sp_header = ARSCHeader(self.buff)
-                assert type_sp_header.type == RES_STRING_POOL_TYPE, \\
-                    "Expected String Pool header, got %x" % type_sp_header.type
+                if type_sp_header.type != RES_STRING_POOL_TYPE:
+                    raise ResParserError("Expected String Pool header, got %x" % type_sp_header.type)
+
                 mTableStrings = StringBlock(self.buff, type_sp_header)
 
                 # Next, we should have the resource key symbol table
                 self.buff.set_idx(current_package.header.start + current_package.keyStrings)
                 key_sp_header = ARSCHeader(self.buff)
-                assert key_sp_header.type == RES_STRING_POOL_TYPE, \\
-                    "Expected String Pool header, got %x" % key_sp_header.type
+                if key_sp_header.type != RES_STRING_POOL_TYPE:
+                    raise ResParserError("Expected String Pool header, got %x" % key_sp_header.type)
+
                 mKeyStrings = StringBlock(self.buff, key_sp_header)
 
                 # Add them to the dict of read packages
@@ -1689,40 +1698,73 @@ class ARSCParser(object):
 
     class ResourceResolver(object):
         """
-        Resolves resources by ID
+        Resolves resources by ID and configuration.
+        This resolver deals with complex resources as well as with references.
         """
         def __init__(self, android_resources, config=None):
+            """
+            :param ARSCParser android_resources: A resource parser
+            :param ARSCResTableConfig config: The desired configuration or None to resolve all.
+            """
             self.resources = android_resources
             self.wanted_config = config
 
         def resolve(self, res_id):
+            """
+            the given ID into the Resource and returns a list of matching resources.
+
+            :param int res_id: numerical ID of the resource
+            :return: a list of tuples of (ARSCResTableConfig, str)
+            """
             result = []
             self._resolve_into_result(result, res_id, self.wanted_config)
             return result
 
         def _resolve_into_result(self, result, res_id, config):
+            # First: Get all candidates
             configs = self.resources.get_res_configs(res_id, config)
-            if configs:
-                for config, ate in configs:
-                    self.put_ate_value(result, ate, config)
+
+            for config, ate in configs:
+                # deconstruct them and check if more candidates are generated
+                self.put_ate_value(result, ate, config)
 
         def put_ate_value(self, result, ate, config):
+            """
+            Put a ResTableEntry into the list of results
+            :param list result: results array
+            :param ARSCResTableEntry ate:
+            :param ARSCResTableConfig config:
+            :return:
+            """
             if ate.is_complex():
                 complex_array = []
                 result.append((config, complex_array))
                 for _, item in ate.item.items:
-                    self.put_item_value(complex_array, item, config, complex_=True)
+                    self.put_item_value(complex_array, item, config, ate, complex_=True)
             else:
-                self.put_item_value(result, ate.key, config, complex_=False)
-
-        def put_item_value(self, result, item, config, complex_):
+                self.put_item_value(result, ate.key, config, ate, complex_=False)
+
+        def put_item_value(self, result, item, config, parent, complex_):
+            """
+            Put the tuple (ARSCResTableConfig, resolved string) into the result set
+
+            :param list result: the result set
+            :param ARSCResStringPoolRef item:
+            :param ARSCResTableConfig config:
+            :param ARSCResTableEntry parent: the originating entry
+            :param bool complex_: True if the originating :class:`ARSCResTableEntry` was complex
+            :return:
+            """
             if item.is_reference():
                 res_id = item.get_data()
                 if res_id:
-                    self._resolve_into_result(
-                        result,
-                        item.get_data(),
-                        self.wanted_config)
+                    # Infinite loop detection:
+                    # TODO should this stay here or should be detect the loop much earlier?
+                    if res_id == parent.mResId:
+                        log.warning("Infinite loop detected at resource item {}. It references itself!".format(parent))
+                        return
+
+                    self._resolve_into_result(result, item.get_data(), self.wanted_config)
             else:
                 if complex_:
                     result.append(item.format_value())
@@ -1730,6 +1772,18 @@ class ARSCParser(object):
                     result.append((config, item.format_value()))
 
     def get_resolved_res_configs(self, rid, config=None):
+        """
+        Return a list of resolved resource IDs with their corresponding configuration.
+        It has a similar return type as :meth:`get_res_configs` but also handles complex entries
+        and references.
+        Also instead of returning :class:`ARSCResTableEntry` in the tuple, the actual values are resolved.
+
+        This is the preferred way of resolving resource IDs to their resources.
+
+        :param int rid: the numerical ID of the resource
+        :param ARSCTableResConfig config: the desired configuration or None to retrieve all
+        :return: A list of tuples of (ARSCResTableConfig, str)
+        """
         resolver = ARSCParser.ResourceResolver(self, config)
         return resolver.resolve(rid)
 
@@ -1796,7 +1850,7 @@ class ARSCParser(object):
             raise ValueError("'rid' must be an int")
 
         if rid not in self.resource_values:
-            log.info("The requested rid could not be found in the resources.")
+            log.warning("The requested rid '0x{:08x}' could not be found in the list of resources.".format(rid))
             return []
 
         res_options = self.resource_values[rid]
@@ -1804,7 +1858,7 @@ class ARSCParser(object):
             if config in res_options:
                 return [(config, res_options[config])]
             elif fallback and config == ARSCResTableConfig.default_config():
-                log.warning("No default resource config could be found for the given rid, using fallback!")
+                log.warning("No default resource config could be found for the given rid '0x{:08x}', using fallback!".format(rid))
                 return [list(self.resource_values[rid].items())[0]]
             else:
                 return []
@@ -1937,31 +1991,37 @@ class ARSCHeader(object):
     Object which contains a Resource Chunk.
     This is an implementation of the `ResChunk_header`.
 
-    It will throw an AssertionError if the header could not be read successfully.
+    It will throw an :class:`ResParserError` if the header could not be read successfully.
 
     It is not checked if the data is outside the buffer size nor if the current
     chunk fits into the parent chunk (if any)!
 
     See http://androidxref.com/9.0.0_r3/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h#196
+    :raises: ResParserError
     """
     SIZE = 2 + 2 + 4
 
     def __init__(self, buff):
         self.start = buff.get_idx()
         # Make sure we do not read over the buffer:
-        assert buff.size() >= self.start + self.SIZE, "Can not read over the buffer size! Offset={}".format(self.start)
+        if buff.size() < self.start + self.SIZE:
+            raise ResParserError("Can not read over the buffer size! Offset={}".format(self.start))
+
         self._type, self._header_size, self._size = unpack('<HHL', buff.read(self.SIZE))
 
         # Assert that the read data will fit into the chunk.
         # The total size must be equal or larger than the header size
-        assert self._header_size >= self.SIZE, \\
-            "declared header size is smaller than required size of {}! Offset={}".format(self.SIZE, self.start)
-        assert self._size >= self.SIZE, \\
-            "declared chunk size is smaller than required size of {}! Offset={}".format(self.SIZE, self.start)
-        assert self._size >= self._header_size, \\
-            "declared chunk size ({}) is smaller than header size ({})! Offset={}".format(self._size,
-                                                                                          self._header_size,
-                                                                                          self.start)
+        if self._header_size < self.SIZE:
+            raise ResParserError(
+                "declared header size is smaller than required size of {}! Offset={}".format(self.SIZE, self.start))
+        if self._size < self.SIZE:
+            raise ResParserError(
+                "declared chunk size is smaller than required size of {}! Offset={}".format(self.SIZE, self.start))
+        if self._size < self._header_size:
+            raise ResParserError(
+                "declared chunk size ({}) is smaller than header size ({})! Offset={}".format(self._size,
+                                                                                              self._header_size,
+                                                                                              self.start))
 
     @property
     def type(self):
@@ -2036,8 +2096,10 @@ class ARSCResTypeSpec(object):
         self.id = unpack('<B', buff.read(1))[0]
         self.res0 = unpack('<B', buff.read(1))[0]
         self.res1 = unpack('<H', buff.read(2))[0]
-        assert self.res0 == 0, "res0 must be zero!"
-        assert self.res1 == 0, "res1 must be zero!"
+        if self.res0 != 0:
+            raise ResParserError("res0 must be zero!")
+        if self.res1 != 0:
+            raise ResParserError("res1 must be zero!")
         self.entryCount = unpack('<I', buff.read(4))[0]
 
         self.typespec_entries = []
@@ -2056,7 +2118,8 @@ class ARSCResType(object):
         self.id = unpack('<B', buff.read(1))[0]
         self.flags, = unpack('<B', buff.read(1))
         self.reserved = unpack('<H', buff.read(2))[0]
-        assert self.reserved == 0, "reserved must be zero!"
+        if self.reserved != 0:
+            raise ResParserError("reserved must be zero!")
         self.entryCount = unpack('<I', buff.read(4))[0]
         self.entriesStart = unpack('<I', buff.read(4))[0]
 
@@ -2406,7 +2469,7 @@ class ARSCResTableConfig(object):
         return self._get_tuple() == other._get_tuple()
 
     def __repr__(self):
-        return "<ARSCResTableConfig '{}'='{}'>".format(self.get_qualifier(), repr(self._get_tuple()))
+        return "<ARSCResTableConfig '{}'={}>".format(self.get_qualifier(), repr(self._get_tuple()))
 
 
 class ARSCResTableEntry(object):
@@ -2483,7 +2546,8 @@ class ARSCResStringPoolRef(object):
 
         self.size, = unpack("<H", buff.read(2))
         self.res0, = unpack("<B", buff.read(1))
-        assert self.res0 == 0, "res0 must be always zero!"
+        if self.res0 != 0:
+            raise ResParserError("res0 must be always zero!")
         self.data_type = unpack('<B', buff.read(1))[0]
         self.data = unpack('<I', buff.read(4))[0]
 
diff --git a/androguard/core/bytecodes/dvm.py b/androguard/core/bytecodes/dvm.py
index 2fc510a5..8e91321e 100644
--- a/androguard/core/bytecodes/dvm.py
+++ b/androguard/core/bytecodes/dvm.py
@@ -1839,7 +1839,8 @@ class StringDataItem:
         not supported by python.
         """
         s = mutf8.decode(self.data)
-        assert len(s) == self.utf16_size, "UTF16 Length does not match!"
+        if len(s) != self.utf16_size:
+            raise ValueError("UTF16 Length does not match!")
 
         # Return a UTF16 String
         return s
@@ -1852,7 +1853,8 @@ class StringDataItem:
         Valid surrogates are encoded as 32bit values, ie. \\U00024f5c.
         """
         s = mutf8.decode(self.data)
-        assert len(s) == self.utf16_size, "UTF16 Length does not match!"
+        if len(s) != self.utf16_size:
+            raise ValueError("UTF16 Length does not match!")
         # log.debug("Decoding UTF16 string with IDX {}, utf16 length {} and hexdata '{}'.".format(self.offset, self.utf16_size, binascii.hexlify(self.data)))
         return mutf8.patch_string(s)
 
@@ -3578,7 +3580,7 @@ class ClassDefItem(object):
         """
         Return the name of this class
 
-        :rtype: int
+        :rtype: str
         """
         return self.name
 
@@ -3586,7 +3588,7 @@ class ClassDefItem(object):
         """
         Return the name of the super class
 
-        :rtype: string
+        :rtype: str
         """
         return self.sname
 
@@ -3594,7 +3596,7 @@ class ClassDefItem(object):
         """
         Return the name of the interface
 
-        :rtype: string
+        :rtype: str
         """
         return self.interfaces
 
@@ -3602,7 +3604,7 @@ class ClassDefItem(object):
         """
         Return the access flags string of the class
 
-        :rtype: string
+        :rtype: str
         """
         if self.access_flags_string is None:
             self.access_flags_string = get_access_flags_string(
@@ -7276,6 +7278,12 @@ class ClassManager(object):
         return [type_.get_string() for type_ in i.get_list()]
 
     def get_type(self, idx):
+        """
+        Return the resolved type name based on the index
+        :param int idx:
+        :return: the type name
+        :rtype: str
+        """
         _type = self.__manage_item["TYPE_TYPE_ID_ITEM"].get(idx)
         if _type == -1:
             return "AG:ITI: invalid type"
diff --git a/androguard/core/bytecodes/mutf8.py b/androguard/core/bytecodes/mutf8.py
index 91170423..9b14a404 100644
--- a/androguard/core/bytecodes/mutf8.py
+++ b/androguard/core/bytecodes/mutf8.py
@@ -32,6 +32,7 @@ def decode(b):
 
     :param b: bytes to decode
     :rtype: unicode (py2), str (py3) of 16bit chars
+    :raises: UnicodeDecodeError if string is not decodable
     """
     res = u""
 
@@ -44,14 +45,19 @@ def decode(b):
         elif x >> 5 == 0b110:
             # 2 byte Multichar
             b2 = next(b)
-            assert b2 >> 6 == 0b10, "Second byte of 2 byte sequence does not looks right."
+            if b2 >> 6 != 0b10:
+                raise UnicodeDecodeError("Second byte of 2 byte sequence does not looks right.")
+
             res += chr((x & 0x1f) << 6 | b2 & 0x3f)
         elif x >> 4 == 0b1110:
             # 3 byte Multichar
             b2 = next(b)
             b3 = next(b)
-            assert b2 >> 6 == 0b10, "Second byte of 3 byte sequence does not looks right."
-            assert b3 >> 6 == 0b10, "Third byte of 3 byte sequence does not looks right."
+            if b2 >> 6 != 0b10:
+                raise UnicodeDecodeError("Second byte of 3 byte sequence does not looks right.")
+            if b3 >> 6 != 0b10:
+                raise UnicodeDecodeError("Third byte of 3 byte sequence does not looks right.")
+
             res += chr((x & 0xf) << 12 | (b2 & 0x3f) << 6 | b3 & 0x3f)
         else:
             raise UnicodeDecodeError("Could not decode byte")
diff --git a/androguard/decompiler/dad/ast.py b/androguard/decompiler/dad/dast.py
similarity index 100%
rename from androguard/decompiler/dad/ast.py
rename to androguard/decompiler/dad/dast.py
diff --git a/androguard/decompiler/dad/decompile.py b/androguard/decompiler/dad/decompile.py
index f1ab6933..2516f84e 100644
--- a/androguard/decompiler/dad/decompile.py
+++ b/androguard/decompiler/dad/decompile.py
@@ -1,7 +1,8 @@
 from __future__ import print_function
 
+import logging
+import struct
 import sys
-
 from builtins import input
 from builtins import map
 # This file is part of Androguard.
@@ -24,43 +25,35 @@ from builtins import next
 from builtins import object
 from builtins import range
 from builtins import str
-
-sys.path.append('./')
-
-import logging
-import struct
 from collections import defaultdict
+
 import androguard.core.androconf as androconf
 import androguard.decompiler.dad.util as util
 from androguard.core.analysis import analysis
 from androguard.core.bytecodes import apk, dvm
-from androguard.decompiler.dad.ast import (
-    JSONWriter, parse_descriptor, literal_string, literal_hex_int,
-    dummy)
 from androguard.decompiler.dad.control_flow import identify_structures
+from androguard.decompiler.dad.dast import (
+    JSONWriter,
+    parse_descriptor,
+    literal_string,
+    literal_hex_int,
+    dummy
+)
 from androguard.decompiler.dad.dataflow import (
-    build_def_use, place_declarations, dead_code_elimination,
-    register_propagation, split_variables)
+    build_def_use,
+    place_declarations,
+    dead_code_elimination,
+    register_propagation,
+    split_variables
+)
 from androguard.decompiler.dad.graph import construct, simplify, split_if_nodes
 from androguard.decompiler.dad.instruction import Param, ThisParam
 from androguard.decompiler.dad.writer import Writer
 from androguard.util import read
 
-
 logger = logging.getLogger('dad')
 
 
-def auto_vm(filename):
-    ret = androconf.is_android(filename)
-    if ret == 'APK':
-        return dvm.DalvikVMFormat(apk.APK(filename).get_dex())
-    elif ret == 'DEX':
-        return dvm.DalvikVMFormat(read(filename))
-    elif ret == 'DEY':
-        return dvm.DalvikOdexVMFormat(read(filename))
-    return None
-
-
 # No seperate DvField class currently
 def get_field_ast(field):
     triple = field.get_class_name()[1:-1], field.get_name(
@@ -86,7 +79,14 @@ def get_field_ast(field):
 
 
 class DvMethod(object):
+    """
+    This is a wrapper around :class:`~androguard.core.analysis.analysis.MethodAnalysis` and
+    :class:`~androguard.core.bytecodes.dvm.EncodedMethod` inside the decompiler.
+    """
     def __init__(self, methanalysis):
+        """
+        :param androguard.core.analysis.analysis.MethodAnalysis methanalysis:
+        """
         method = methanalysis.get_method()
         self.method = method
         self.start_block = next(methanalysis.get_basic_blocks().get(), None)
@@ -122,10 +122,11 @@ class DvMethod(object):
                 self.lparams.append(param)
                 self.var_to_name[param] = Param(param, ptype)
                 num_param += util.get_type_size(ptype)
+
         if not __debug__:
             from androguard.core import bytecode
-            bytecode.method2png('/tmp/dad/graphs/%s#%s.png' % \\
-                                (self.cls_name.split('/')[-1][:-1], self.name), methanalysis)
+            # TODO: use tempfile to create a correct tempfile (cross platform compatible)
+            bytecode.method2png('/tmp/dad/graphs/%s#%s.png' % (self.cls_name.split('/')[-1][:-1], self.name), methanalysis)
 
     def process(self, doAST=False):
         logger.debug('METHOD : %s', self.name)
@@ -140,10 +141,12 @@ class DvMethod(object):
                 self.writer.write_method()
             return
 
+        # Construct the CFG
         graph = construct(self.start_block, self.var_to_name, self.exceptions)
         self.graph = graph
 
         if not __debug__:
+            # TODO: use tempfile to create a correct tempfile (cross platform compatible)
             util.create_png(self.cls_name, self.name, graph, '/tmp/dad/blocks')
 
         use_defs, def_uses = build_def_use(graph, self.lparams)
@@ -171,14 +174,14 @@ class DvMethod(object):
         graph.compute_rpo()
 
         if not __debug__:
-            util.create_png(self.cls_name, self.name, graph,
-                            '/tmp/dad/pre-structured')
+            # TODO: use tempfile to create a correct tempfile (cross platform compatible)
+            util.create_png(self.cls_name, self.name, graph, '/tmp/dad/pre-structured')
 
         identify_structures(graph, graph.immediate_dominators())
 
         if not __debug__:
-            util.create_png(self.cls_name, self.name, graph,
-                            '/tmp/dad/structured')
+            # TODO: use tempfile to create a correct tempfile (cross platform compatible)
+            util.create_png(self.cls_name, self.name, graph, '/tmp/dad/structured')
 
         if doAST:
             self.ast = JSONWriter(graph, self).get_ast()
@@ -208,7 +211,17 @@ class DvMethod(object):
 
 
 class DvClass(object):
+    """
+    This is a wrapper for :class:`~androguard.core.bytecodes.dvm.ClassDefItem` inside the decompiler.
+
+    At first, :py:attr:`methods` contains a list of :class:`~androguard.core.bytecodes.dvm.EncodedMethods`,
+    which are successively replaced by :class:`DvMethod` in the process of decompilation.
+    """
     def __init__(self, dvclass, vma):
+        """
+        :param androguard.core.bytecodes.dvm.ClassDefItem dvclass: the class item
+        :param androguard.core.analysis.analysis.Analysis vma: an Analysis object
+        """
         name = dvclass.get_name()
         if name.find('/') > 0:
             pckg, name = name.rsplit('/', 1)
@@ -261,6 +274,7 @@ class DvClass(object):
             try:
                 self.process_method(i, doAST=doAST)
             except Exception as e:
+                # FIXME: too broad exception?
                 logger.warning('Error decompiling method %s: %s', self.methods[i], e)
 
     def get_ast(self):
@@ -409,20 +423,61 @@ class DvClass(object):
 
 
 class DvMachine(object):
+    """
+    Wrapper class for a Dalvik Object, like a DEX or ODEX file.
+
+    The wrapper allows to take a Dalvik file and get a list of Classes out of it.
+    The :class:`~androguard.decompiler.dad.decompile.DvMachine` can take either an APK file directly,
+    where all DEX files from the multidex are used, or a single DEX or ODEX file as an argument.
+
+    At first, :py:attr:`classes` contains only :class:`~androguard.core.bytecodes.dvm.ClassDefItem` as values.
+    Then these objects are replaced by :class:`DvClass` items successively.
+    """
     def __init__(self, name):
-        vm = auto_vm(name)
-        if vm is None:
-            raise ValueError('Format not recognised: %s' % name)
-        self.vma = analysis.Analysis(vm)
-        self.classes = dict((dvclass.get_name(), dvclass)
-                            for dvclass in vm.get_classes())
+        """
+
+        :param name: filename to load
+        """
+        self.vma = analysis.Analysis()
+
+        # Proper detection which supports multidex inside APK
+        ftype = androconf.is_android(name)
+        if ftype == 'APK':
+            for d in apk.APK(name).get_all_dex():
+                self.vma.add(dvm.DalvikVMFormat(d))
+        elif ftype == 'DEX':
+            self.vma.add(dvm.DalvikVMFormat(read(name)))
+        elif ftype == 'DEY':
+            self.vma.add(dvm.DalvikOdexVMFormat(read(name)))
+        else:
+            raise ValueError("Format not recognised for filename '%s'" % name)
+
+        self.classes = dict((dvclass.orig_class.get_name(), dvclass.orig_class) for dvclass in self.vma.get_classes())
+        # TODO why not?
         # util.merge_inner(self.classes)
 
     def get_classes(self):
+        """
+        Return a list of classnames contained in this machine.
+        The format of each name is Lxxx;
+
+        :return: list of class names
+        """
         return list(self.classes.keys())
 
     def get_class(self, class_name):
+        """
+        Return the :class:`DvClass` with the given name
+
+        The name is partially matched against the known class names and the first result is returned.
+        For example, the input `foobar` will match on Lfoobar/bla/foo;
+
+        :param str class_name:
+        :return: the class matching on the name
+        :rtype: DvClass
+        """
         for name, klass in self.classes.items():
+            # TODO why use the name partially?
             if class_name in name:
                 if isinstance(klass, DvClass):
                     return klass
@@ -430,6 +485,11 @@ class DvMachine(object):
                 return dvclass
 
     def process(self):
+        """
+        Process all classes inside the machine.
+
+        This calls :meth:`~androgaurd.decompiler.dad.decompile.DvClass.process` on each :class:`DvClass`.
+        """
         for name, klass in self.classes.items():
             logger.debug('Processing class: %s', name)
             if isinstance(klass, DvClass):
@@ -439,10 +499,19 @@ class DvMachine(object):
                 dvclass.process()
 
     def show_source(self):
+        """
+        Calls `show_source` on all classes inside the machine.
+        This prints the source to stdout.
+
+        This calls :meth:`~androgaurd.decompiler.dad.decompile.DvClass.show_source` on each :class:`DvClass`.
+        """
         for klass in self.classes.values():
             klass.show_source()
 
     def process_and_show(self):
+        """
+        Run :meth:`process` and :meth:`show_source` after each other.
+        """
         for name, klass in sorted(self.classes.items()):
             logger.debug('Processing class: %s', name)
             if not isinstance(klass, DvClass):
@@ -450,6 +519,23 @@ class DvMachine(object):
             klass.process()
             klass.show_source()
 
+    def get_ast(self):
+        """
+        Processes each class with AST enabled and returns a dictionary with all single ASTs
+        Classnames as keys.
+
+        :return: an dictionary for all classes
+        :rtype: dict
+        """
+        ret = dict()
+        for name, cls in sorted(self.classes.items()):
+            logger.debug('Processing class: %s', name)
+            if not isinstance(cls, DvClass):
+                cls = DvClass(cls, self.vma)
+            cls.process(doAST=True)
+            ret[name] = cls.get_ast()
+        return ret
+
 
 sys.setrecursionlimit(5000)
 
@@ -474,7 +560,7 @@ def main():
         logger.info(' %s', class_name)
     logger.info('========================')
 
-    cls_name = input('Choose a class: ')
+    cls_name = input('Choose a class (* for all classes): ')
     if cls_name == '*':
         machine.process_and_show()
     else:
@@ -486,7 +572,7 @@ def main():
             for i, method in enumerate(cls.get_methods()):
                 logger.info('%d: %s', i, method.name)
             logger.info('======================')
-            meth = input('Method: ')
+            meth = input('Method (* for all methods): ')
             if meth == '*':
                 logger.info('CLASS = %s', cls)
                 cls.process()
diff --git a/androguard/decompiler/dad/graph.py b/androguard/decompiler/dad/graph.py
index a4d9f485..d2b8f696 100644
--- a/androguard/decompiler/dad/graph.py
+++ b/androguard/decompiler/dad/graph.py
@@ -30,13 +30,21 @@ from androguard.decompiler.dad.instruction import Variable
 logger = logging.getLogger('dad.graph')
 
 
+# TODO Could use networkx here, as it has plenty of tools already, no need to reengineer the wheel
 class Graph(object):
+    """
+    Stores a CFG (Control Flow Graph), which is a directed graph.
+
+    The CFG defines an entry node :py:attr:`entry`, a single exit node :py:attr:`exit`, a list of nodes
+    :py:attr:`nodes` and a list of edges :py:attr:`edges`.
+    """
     def __init__(self):
         self.entry = None
         self.exit = None
         self.nodes = list()
-        self.rpo = []
         self.edges = defaultdict(list)
+
+        self.rpo = []
         self.catch_edges = defaultdict(list)
         self.reverse_edges = defaultdict(list)
         self.reverse_catch_edges = defaultdict(list)
@@ -57,6 +65,11 @@ class Graph(object):
             node, []))
 
     def add_node(self, node):
+        """
+        Adds the given node to the graph, without connecting it to anyhting else.
+
+        :param androguard.decompiler.dad.node.Node node: node to add
+        """
         self.nodes.append(node)
 
     def add_edge(self, e1, e2):
@@ -76,6 +89,11 @@ class Graph(object):
             lpreds.append(e1)
 
     def remove_node(self, node):
+        """
+        Remove the node from the graph, removes also all connections.
+
+        :param androguard.decompiler.dad.node.Node node: the node to remove
+        """
         preds = self.reverse_edges.get(node, [])
         for pred in preds:
             self.edges[pred].remove(node)
@@ -134,10 +152,9 @@ class Graph(object):
 
     def post_order(self):
         """
-        Return the nodes of the graph in post-order i.e we visit all the
+        Yields the :class`~androguard.decompiler.dad.node.Node`s of the graph in post-order i.e we visit all the
         children of a node before visiting the node itself.
         """
-
         def _visit(n, cnt):
             visited.add(n)
             for suc in self.all_sucs(n):
@@ -152,7 +169,17 @@ class Graph(object):
             yield node
 
     def draw(self, name, dname, draw_branches=True):
+        """
+        Writes the current graph as a PNG file
+
+        :param str name: filename (without .png)
+        :param str dname: directory of the output png
+        :param draw_branches:
+        :return:
+        """
         from pydot import Dot, Edge
+        import os
+
         g = Dot()
         g.set_node_defaults(color='lightgray',
                             style='filled',
@@ -172,7 +199,8 @@ class Graph(object):
                                 color='black',
                                 style='dashed'))
 
-        g.write_png('%s/%s.png' % (dname, name))
+        logger.error("dname")
+        g.write(os.path.join(dname, '%s.png' % name), format='png')
 
     def immediate_dominators(self):
         return dom_lt(self)
@@ -309,7 +337,7 @@ def simplify(graph):
 
 
 def dom_lt(graph):
-    """Dominator algorithm from Lengaeur-Tarjan"""
+    """Dominator algorithm from Lengauer-Tarjan"""
 
     def _dfs(v, n):
         semi[v] = n = n + 1
@@ -372,6 +400,13 @@ def dom_lt(graph):
 
 
 def bfs(start):
+    """
+    Breadth first search
+
+    Yields all nodes found from the starting point
+
+    :param start: start node
+    """
     to_visit = [start]
     visited = set([start])
     while to_visit:
@@ -449,6 +484,15 @@ def make_node(graph, block, block_to_node, vmap, gen_ret):
 
 
 def construct(start_block, vmap, exceptions):
+    """
+    Constructs a CFG
+
+    :param androguard.core.analysis.analysis.DVMBasicBlock start_block: The startpoint
+    :param vmap: variable mapping
+    :param exceptions: list of androguard.core.analysis.analysis.ExceptionAnalysis
+
+    :rtype: Graph
+    """
     bfs_blocks = bfs(start_block)
 
     graph = Graph()
diff --git a/androguard/decompiler/dad/util.py b/androguard/decompiler/dad/util.py
index d5f23496..594e71ac 100644
--- a/androguard/decompiler/dad/util.py
+++ b/androguard/decompiler/dad/util.py
@@ -200,6 +200,14 @@ def get_params_type(descriptor):
 
 
 def create_png(cls_name, meth_name, graph, dir_name='graphs2'):
+    """
+    Creates a PNG from a given :class:`~androguard.decompiler.dad.graph.Graph`.
+
+    :param str cls_name: name of the class
+    :param str meth_name: name of the method
+    :param androguard.decompiler.dad.graph.Graph graph:
+    :param str dir_name: output directory
+    """
     m_name = ''.join(x for x in meth_name if x.isalnum())
     name = ''.join((cls_name.split('/')[-1][:-1], '#', m_name))
     graph.draw(name, dir_name)
diff --git a/androguard/decompiler/dad/writer.py b/androguard/decompiler/dad/writer.py
index abcf9f47..89332b57 100644
--- a/androguard/decompiler/dad/writer.py
+++ b/androguard/decompiler/dad/writer.py
@@ -32,6 +32,10 @@ logger = logging.getLogger('dad.writer')
 
 
 class Writer(object):
+    """
+    Transforms a method into Java code.
+
+    """
     def __init__(self, graph, method):
         self.graph = graph
         self.method = method
@@ -426,6 +430,7 @@ class Writer(object):
             self.end_ins()
 
     def visit_constant(self, cst):
+        # FIXME: basestring should be replaced
         if isinstance(cst, basestring):
             return self.write(string(cst), data="CONSTANT_STRING")
         self.write('%r' % cst,
diff --git a/androguard/decompiler/decompiler.py b/androguard/decompiler/decompiler.py
index c27e7eb7..8f93f893 100644
--- a/androguard/decompiler/decompiler.py
+++ b/androguard/decompiler/decompiler.py
@@ -537,11 +537,15 @@ class DecompilerDex2Fernflower(object):
 class DecompilerDAD:
     def __init__(self, vm, vmx):
         """
-        Decompiler wrapper for DAD
+        Decompiler wrapper for DAD: **D**AD is **A** **D**ecompiler
         DAD is the androguard internal decompiler.
 
-        :param vm: `DalvikVMFormat` object
-        :param vmx: `Analysis` object
+        This Method does not use the :class:`~androguard.decompiler.dad.decompile.DvMachine` but
+        creates :class:`~androguard.decompiler.dad.decompile.DvClass` and
+        :class:`~androguard.decompiler.dad.decompile.DvMethod` on demand.
+
+        :param androguard.core.bytecodes.dvm.DalvikVMFormat vm: `DalvikVMFormat` object
+        :param androguard.core.analysis.analysis.Analysis vmx: `Analysis` object
         """
         self.vm = vm
         self.vmx = vmx
diff --git a/androguard/decompiler/dad/tests/dataflow_test.py b/tests/dataflow_test.py
similarity index 100%
rename from androguard/decompiler/dad/tests/dataflow_test.py
rename to tests/dataflow_test.py
diff --git a/androguard/decompiler/dad/tests/dominator_test.py b/tests/dominator_test.py
similarity index 100%
rename from androguard/decompiler/dad/tests/dominator_test.py
rename to tests/dominator_test.py
diff --git a/androguard/decompiler/dad/tests/rpo_test.py b/tests/rpo_test.py
similarity index 100%
rename from androguard/decompiler/dad/tests/rpo_test.py
rename to tests/rpo_test.py
diff --git a/tests/test_apk.py b/tests/test_apk.py
index 3a439828..b476e558 100644
--- a/tests/test_apk.py
+++ b/tests/test_apk.py
@@ -275,15 +275,15 @@ class APKTest(unittest.TestCase):
 
                 # Special error cases
                 if apath == "v2-only-apk-sig-block-size-mismatch.apk":
-                    with self.assertRaises(AssertionError):
+                    with self.assertRaises(apk.BrokenAPKError):
                         a.is_signed_v2()
                     continue
                 elif apath == "v2-only-empty.apk":
-                    with self.assertRaises(AssertionError):
+                    with self.assertRaises(apk.BrokenAPKError):
                         a.is_signed_v2()
                     continue
                 elif apath == "v3-only-with-rsa-pkcs1-sha512-4096-apk-sig-block-size-mismatch.apk":
-                    with self.assertRaises(AssertionError):
+                    with self.assertRaises(apk.BrokenAPKError):
                         a.is_signed_v3()
                     continue
 
diff --git a/tests/test_axml.py b/tests/test_axml.py
index 6518aa7c..3db371d5 100644
--- a/tests/test_axml.py
+++ b/tests/test_axml.py
@@ -147,19 +147,19 @@ class AXMLTest(unittest.TestCase):
 
     def testArscHeader(self):
         """Test if wrong arsc headers are rejected"""
-        with self.assertRaises(AssertionError) as cnx:
+        with self.assertRaises(axml.ResParserError) as cnx:
             axml.ARSCHeader(bytecode.BuffHandle(b"\\x02\\x01"))
         self.assertTrue("Can not read over the buffer size" in str(cnx.exception))
 
-        with self.assertRaises(AssertionError) as cnx:
+        with self.assertRaises(axml.ResParserError) as cnx:
             axml.ARSCHeader(bytecode.BuffHandle(b"\\x02\\x01\\xFF\\xFF\\x08\\x00\\x00\\x00"))
         self.assertTrue("smaller than header size" in str(cnx.exception))
 
-        with self.assertRaises(AssertionError) as cnx:
+        with self.assertRaises(axml.ResParserError) as cnx:
             axml.ARSCHeader(bytecode.BuffHandle(b"\\x02\\x01\\x01\\x00\\x08\\x00\\x00\\x00"))
         self.assertTrue("declared header size is smaller than required size" in str(cnx.exception))
 
-        with self.assertRaises(AssertionError) as cnx:
+        with self.assertRaises(axml.ResParserError) as cnx:
             axml.ARSCHeader(bytecode.BuffHandle(b"\\x02\\x01\\x08\\x00\\x04\\x00\\x00\\x00"))
         self.assertTrue("declared chunk size is smaller than required size" in str(cnx.exception))
 
@@ -257,7 +257,7 @@ class AXMLTest(unittest.TestCase):
         """
         filename = "examples/axml/AndroidManifest_StringNotTerminated.xml"
 
-        with self.assertRaises(AssertionError) as cnx:
+        with self.assertRaises(axml.ResParserError) as cnx:
             with open(filename, "rb") as f:
                 ap = axml.AXMLPrinter(f.read())
         self.assertTrue("not null terminated" in str(cnx.exception))