def run_line(self, lino: int):
        ast_line = ast_tree.get(lino)
        lk.logd(ast_line, length=8)
        # -> [(obj_type, obj_val), ...]

        for i in ast_line:
            obj_type, obj_val = i
            lk.loga(obj_type, obj_val)
            # obj_type: str. e.g. "<class '_ast.Call'>"
            # obj_val: str/dict. e.g. '__name__', {'os': 'os'}, ...

            method = self.registered_methods.get(obj_type, self.do_nothing)
            method(obj_val)
    def run_block(self, current_module: str):
        """
        IN: module: str
        OT: self.calls: list
        """
        lk.logd('run block', current_module, style='■')

        if current_module not in self.module_linos:
            # 说明此 module 是从外部导入的模块, 如 module = 'testflight.downloader'.
            assert self.module_analyser.is_prj_module(current_module)
            self.outer_calls.append(current_module)
            # return module_path

        # update hooks
        # self.module_hooks 需要在每次更新 self.run_block() 时同步更新. 这是因为, 不同
        # 的 block 定义的区间范围不同, 而不同的区间范围包含的变量指配 (assigns) 也可能是不同
        # 的.
        # 例如在 module = testflight.test_app_launcher.module 层级, self.module
        # _hooks = {'main': 'testflight.test_app_launcher.main'}. 到了 module =
        # testflight.test_app_launcher.Init 来运行 run_block 的时候, self.module
        # _hooks 就变成了 {'main': 'testflight.test_app_launcher.Init.main'}. 也就
        # 是说在不同的 block 区间, 'main' 指配的 module 对象发生了变化, 因此必须更新 self
        # .module_hooks 才能适应最新变化.
        self.module_hooks = self.assign_analyser.indexing_assign_reachables(
            current_module, self.module_linos)
        lk.logt('[I4252]', 'update module hooks', self.module_hooks)

        # reset hooks
        self.var_hooks.clear()
        self.calls.clear()

        linos = self.module_linos[current_module]
        # the linos is in ordered.
        lk.loga(current_module, linos)

        for lino in linos:
            self.run_line(lino)

        # | for index, lino in enumerate(linos):
        # |     # noinspection PyBroadException
        # |     try:
        # |         self.run_line(lino)
        # |     except Exception:
        # |         if index == 0:
        # |             continue
        # |         else:
        # |             raise Exception

        return self.calls
예제 #3
0
 def analyse_module(self, module, linos, var_reachables):
     """
     发现该 module 下的与其他 module 之间的调用关系.
     """
     lk.logd('analyse_module', module, style='■')
     
     related_calls = []
     self.line_parser.reset(var_reachables)
     
     for lino in linos:
         ast_line = self.ast_tree[lino]
         modules = self.analyse_line(ast_line)
         for m in modules:
             if m not in related_calls:
                 related_calls.append(m)
     
     self.module_calls.update({module: tuple(related_calls)})
예제 #4
0
    def analyse_module(self, module, linos):
        """
        发现该 module 下的与其他 module 之间的调用关系.
        """
        lk.logd('analyse_module', module, style='■')

        # lk.logt('[TEMPRINT]_20190811_214927',
        #         self.line_parser.get_global_vars())
        
        related_calls = []
        
        for lino in linos:
            ast_line = self.ast_tree[lino]
            module_called = self.analyse_line(ast_line)
            # lk.logt('[D3233]', module_called)
            for m in module_called:
                if m not in related_calls:
                    related_calls.append(m)

        lk.logt('[I3259]', related_calls)
        self.module_calls.update({module: tuple(related_calls)})
예제 #5
0
 def indexing_module_linos(self, master_module='', linos=None):
     """
     获取 pyfile 内每个 module 对应的行号范围.
     根据 {lino:indent} 和 ast_tree 创建 {module:linos} 的字典.
     注:
         1. 每个 module (无论是父子关系还是兄弟关系) 之间的范围互不重叠.
         2. AClass.__init__ 被认作 AClass
     
     IN: master_module: str.
             当为空时, 将使用默认值 self.top_module (e.g. 'src.app')
             不为空时, 请注意传入的是当前要观察的 module 的上一级 module. 例如我们要编译
                 src.app.main.child_method 所在的层级, 则 top_module 应传入 src
                 .app.main. 用例参考: src.analyser.AssignAnalyser#update
                 _assigns
         linos: None/list. 您可以自定义要读取的 module 范围, 本方法会仅针对这个区间进行
             编译.
             例如:
                 1 | def aaa():
                 2 |     def bbb():      # <- start
                 3 |         def ccc():
                 4 |             pass
                 5 |                     # <- end
                 6 | def ddd():
                 7 |     pass
             则本方法只编译 range(2, 5) 范围内的数据, 并返回 {'src.app.aaa.bbb': [
             2, 5], 'src.app.aaa.bbb.ccc': [3, 4]} 作为编译结果.
             注意: 指定的范围的开始位置的缩进必须小于等于结束位置的缩进 (空行除外).
             如果该参数为 None, 则默认使用所有行号 (`range(0, len(code_lines))`).
         self.ast_tree: dict. {lino: [(obj_type, obj_val), ...], ...}
             lino: int. 行号, 从 1 开始数.
             obj_type: str. 对象类型, 例如 "<class 'FunctionDef'>" 等. 完整的支持
                 列表参考 src.utils.ast_helper.AstHelper#eval_node().
             obj_val: str/dict. 对象的值, 目前仅存在 str 或 dict 类型的数据.
                 示例:
                     (str) 'print'
                     (dict) {'src.downloader.Downloader':
                         'src.downloader.Downloader'} (多用于描述 Import)
         self.ast_indents: dict. {lino: indent, ...}. 由 AstHelper#create
             _lino_indent_dict() 提供.
             lino: int. 行号, 从 1 开始数.
             indent: int. 该行的列缩进位置, 为 4 的整数倍数, 如 0, 4, 8, 12 等.
         self.top_module: str. e.g. 'src.app'
     OT:
         module_linos: dict. {module: [lino, ...]}
             module: str. 模块的路径名.
             lino_list: list. 模块所涉及的行号列表, 已经过排序, 行号从 1 开始数, 最大
                 不超过当前 pyfile 的总代码行数.
             e.g. {
                 'src.app.module': [1, 2, 3, 9, 10],
                 'src.app.main': [4, 5, 8],
                 'src.app.main.child_method': [6, 7],
                 ...
             }
             有了 module_linos 以后, 我们就可以在已知 module 的调用关系的情况下, 专注
             于读取该 module 对应的区间范围, 逐行分析每条语句, 并进一步发现新的调用关系,
             以此产生裂变效应. 详见 src.analyser.VirtualRunner#main().
     """
     if master_module == '':
         master_module = self.top_module
         assert linos is None
         linos = self.prj_linos
         # the linos are already sorted.
     else:
         assert linos is not None
     
     lk.logd('indexing module linos', master_module)
     
     # ------------------------------------------------
     
     # ast_defs: ast definitions
     ast_defs = ("<class '_ast.FunctionDef'>", "<class '_ast.ClassDef'>")
     ast_args = ("<class '_ast.arg'>",)
     
     indent_module_holder = {-4: self.top_module}  # format: {indent: module}
     module_linos = {}  # format: {module: linos}
     
     last_module = ''
     last_indent = 0
     # last_indent 初始化值不影响方法的正常执行. 因为我们事先能保证 linos 参数的第一个
     # lino 的 indent 一定是正常的, 而伴随着第一个 lino 的循环结尾, last_indent 就能
     # 得到安全的更新, 因此 last_indent 的初始值无论是几都是安全的.
     
     for lino in linos:
         obj_type, obj_val = self.eval_ast_line(lino)
         # -> "<class '_ast.FunctionDef'>", 'main'
         
         indent = self.ast_indents.get(lino, -1)
         parent_indent = indent - 4
         
         # lk.loga(lino, indent, last_module, obj_type, obj_val)
         
         if parent_indent in indent_module_holder:
             parent_module = indent_module_holder[parent_indent]
             
             if obj_type in ast_defs:
                 # obj_type = "<class 'FunctionDef'>", obj_val = 'main'
                 if obj_val == '__init__':
                     # source_code = `def __init__(self):`
                     current_module = parent_module
                 else:
                     current_module = parent_module + '.' + obj_val
                 # -> 'src.app.main'
             elif obj_type in ast_args:
                 current_module = last_module
             elif indent == 0 \
                     or last_module == self.runtime_module:
                 current_module = self.runtime_module
                 # | current_module = parent_module + '.module'
             else:
                 # indent > 0 and last_parent_module not in (
                 # master_module, self
                 # .runtime_module
                 current_module = parent_module
             
             # update indent_module_holder
             indent_module_holder.update({indent: current_module})
             # -> {0: 'src.app.main'}, {4: 'src.app.main.child_method'}, ...
         
         else:
             lk.loga(lino, indent, last_module)
             indent = last_indent
             current_module = last_module
         """
         case 1:
             source_code = ```
                 1 | def main():  # <- cursor
                 2 |     pass
             ```
             indent = 0, obj_val = 'main'
             -> indent - 4 = -4
             -> parent_module = 'src.app'
             -> current_module = 'src.app.main'
         case 2:
             source_code = ```
                 1 | def main():
                 2 |     def child_method():  # <- cursor
                 3 |         pass
             ```
             indent = 4, obj_val = 'child_method'
             -> indent - 4 = 0
             -> parent_module = 'src.app.main'
             -> current_module = 'src.app.main.child_method'
         case 3:
             source_code = ```
                 1 | def main():
                 2 |     def child_method():
                 3 |         print('aaa', 'bbb'
                 4 |               'ccc')  # <- cursor
             ```
             indent = 14, obj_val = "'ccc'"
             -> indent - 4 = 10
             -> 10 not in indent_module_holder.keys(), so return the fallback
                value: 'src.app.main.child_method'
             -> current_module = 'src.app.main.child_method'
         """
         
         # update module_linos
         node = module_linos.setdefault(current_module, [])
         node.append(lino)  # NOTE: the lino is in ordered
         
         # update last vars
         last_module = current_module
         last_indent = indent
     
     # TEST show
     # lk.logt('[D3421]', self.top_module, indent_module_holder)
     show_modules_lightly = tuple(
         self.module_helper.get_module_seg(x, 'r0')
         for x in module_linos
     )
     lk.logt('[I4204]', self.top_module, show_modules_lightly)
     """
     -> module_linos = {
         'testflight.test_app_launcher.module': [1, 3, 4, 38, 39],
         'testflight.test_app_launcher.main'  : [8, 11, 12, 21, 22, 24, 25,
                                                 27, 28],
         'testflight.test_app_launcher.main.child_method' : [14, 15],
         'testflight.test_app_launcher.main.child_method2': [17, 18, 19],
         'testflight.test_app_launcher.Init'              : [31],
         'testflight.test_app_launcher.Init.main'         : [33, 35]
     }
     """
     
     # raise Exception  # TEST
     return module_linos
    def indexing_module_linos(self, top_module='', linos=None):
        """
        根据 {lino:indent} 和 ast_tree 创建 {module:linos} 的字典.
        
        ARGS:
            top_module
            linos: list. 您可以自定义要读取的 module 范围, 本方法会仅针对这个区间进行编译.
                例如:
                    1 | def aaa():
                    2 |     def bbb():      # <- start
                    3 |         def ccc():
                    4 |             pass
                    5 |                     # <- end
                    6 | def ddd():
                    7 |     pass
                则本方法只编译 start=2 - end=5 范围内的数据, 并返回 {'src.app.aaa.bbb'
                : [2, 5], 'src.app.aaa.bbb.ccc': [3, 4]} 作为编译结果.
                注意: 指定的范围的开始位置的缩进必须小于等于结束位置的缩进 (空行除外).

        IN:
            ast_tree: dict. {lino: [(obj_type, obj_val), ...], ...}
                lino: int. 行号, 从 1 开始数
                obj_type: str. 对象类型, 例如 "<class 'FunctionDef'>" 等. 完整的支持
                    列表参考 src.utils.ast_helper.AstHelper#eval_node()
                obj_val: str/dict. 对象的值, 目前仅存在 str 或 dict 类型的数据.
                    示例:
                        (str) 'print'
                        (dict) (多用于描述 Import) {'src.downloader.Downloader':
                            'src.downloader.Downloader'}
            lino_indent: dict. {lino: indent, ...}. 由 AstHelper#create
                _lino_indent_dict() 提供
                lino: int. 行号, 从 1 开始数
                indent: int. 该行的列缩进位置, 为 4 的整数倍数, 如 0, 4, 8, 12 等
            self.top_module: str. e.g. 'src.app'
        OT:
            module_linos: dict. {module: [lino, ...]}
                module: str. 模块的路径名.
                lino_list: list. 模块所涉及的行号列表, 已经过排序, 行号从 1 开始数, 最
                    大不超过当前 pyfile 的总代码行数.
                e.g. {'src.app.module': [1, 2, 3, 9, 10],
                      'src.app.main': [4, 5, 8],
                      'src.app.main.child_method': [6, 7],
                      ...}
                有了 module_linos 以后, 我们就可以在已知 module 的调用关系的情况下, 专注于
                读取该 module 对应的区间范围, 逐行分析每条语句, 并进一步发现新的调用关系, 以
                此产生裂变效应. 详见 src.analyser.VirtualRunner#main().
        """
        lk.logd('indexing module linos')

        if top_module == '':
            top_module = self.top_module
        if linos is None:
            linos = list(ast_tree.keys())
            linos.sort()

        # ------------------------------------------------

        def eval_ast_line():
            ast_line = ast_tree[lino]  # type: list
            # ast_line is type of list, assert it not empty.
            assert ast_line
            # here we only take its first element.
            return ast_line[0]

        # ast_defs: ast definitions
        ast_defs = ("<class '_ast.FunctionDef'>", "<class '_ast.ClassDef'>")
        indent_module_dict = {}  # format: {indent: module}
        out = {}  # format: {module: lino_list}

        last_indent = 0

        for lino in linos:
            # lk.loga(lino)

            obj_type, obj_val = eval_ast_line()

            indent = ast_indents.get(lino, -1)
            """
            special:
                indent 有一个特殊值 -1, 表示下面这种情况:
                    def abc():        # -> indent = 0
                        print('aaa',  # -> indent = 4
                              'bbb',  # -> indent = 10 -> -1
                              'ccc')  # -> indent = 10 -> -1
                当 indent 为 -1 时, 则认为本行的 indent 保持与上次 indent 一致.
            """

            if indent == -1:
                indent = last_indent
                assert obj_type not in ast_defs
            assert indent % 4 == 0, (lino, indent)
            """
            当 indent >= last_indent 时: 在 indent_holder 中开辟新键.
            当 indent < last_indent 时: 从 indent_holder 更新并移除高缩进的键.
            """

            lk.loga(lino, indent, obj_type, obj_val)

            # noinspection PyUnresolvedReferences
            parent_module = indent_module_dict.get(indent - 4, top_module)
            """
            case 1:
                indent = 0, obj_val = 'main'
                -> indent - 4 = -4
                -> -4 not in indent_module_dict. so assign default:
                    parent_module = top_module = 'src.app'
                -> current_module = 'src.app.main'
            case 2:
                indent = 4, obj_val = 'child_method'
                -> indent - 4 = 0
                -> parent_module = 'src.app.main'
                -> current_module = 'src.app.main.child_method'
            """
            if obj_type in ast_defs:
                # obj_type = "<class 'FunctionDef'>", obj_val = 'main'
                current_module = parent_module + '.' + obj_val
                # lk.logt('[TEMPRINT]', current_module)
                # -> 'src.app.main'
            elif indent > 0:
                current_module = parent_module
            else:
                current_module = parent_module + '.' + 'module'
                # -> 'src.app.module'

            # lk.loga(parent_module, current_module)

            node = out.setdefault(current_module, [])
            node.append(lino)  # NOTE: the lino is in ordered

            # update indent_module_dict
            indent_module_dict.update({indent: current_module})
            # -> {0: 'src.app.main'}, {4: 'src.app.main.child_method'}, ...

            # update last_indent
            last_indent = indent

        # sort
        for lino_list in out.values():
            lino_list.sort()

        # TEST print
        lk.loga(indent_module_dict)
        lk.logt('[I4204]', out)

        return out