已合并
openEuler社区软件包加固-第6期(自动化生成用例工具开发)python部分 #66
AtomGit-Bot创建于 2023年7月12日
openEuler社区软件包加固-第6期(自动化生成用例工具开发)python部分 #66
已合并
AtomGit-Bot创建于 2023年7月12日
refs/pull/66/head合入到master
3 个文件变更+1913-0
Aauto-tc-gen/python_parse/commonlib.py+98-0
@@ -0,0 +1,98 @@
1+# This program is licensed under Mulan PSL v2.
2+# You can use it according to the terms and conditions of the Mulan PSL v2.
3+# http://license.coscl.org.cn/MulanPSL2
4+# THIS PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
5+# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
6+# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
7+# See the Mulan PSL v2 for more details.
8+####################################
9+# @Author : hourui
10+# @Contact : softgreet@qq.com
11+# @Date : 2023-10-12 12:00:00
12+# @License : Mulan PSL v2
13+# @Version : 1.0
14+# @Desc : python中参数解析
15+#####################################
16+ 
17+#coding=UTF-8
18+ 
19+from enum import Enum
20+import subprocess
21+import locale
22+import itertools
23+import ast
24+ 
25+# 比较两个抽象语法树是否相同
26+def compare_ast(node1, node2):
27+ if type(node1) is not type(node2):
28+ return False
29+ if isinstance(node1, ast.AST):
30+ if node1._fields != node2._fields:
31+ return False
32+ for k in node1._fields:
33+ if not compare_ast(getattr(node1, k), getattr(node2, k)):
34+ return False
35+ return True
36+ elif isinstance(node1, list):
37+ if len(node1) != len(node2):
38+ return False
39+ return all(itertools.starmap(compare_ast, zip(node1, node2)))
40+ else:
41+ return node1 == node2
42+ 
43+class FileType(Enum):
44+ SRCRPM = 'src.rpm file'
45+ RPM = 'rpm file'
46+ EXE = 'other script except shell|py|perl'
47+ PYTHON = 'python script'
48+ SHELL = 'shell script'
49+ PERL = 'perl script'
50+ ELF = 'elf file'
51+ LINK = 'link file'
52+ OTHER = 'other file'
53+ NOTFILE = 'not file'
54+ 
55+ def get_linked_file(link: str):
56+ ret = subprocess.run(args='file -b \'' + link + '\'', stdout=subprocess.PIPE,
57+ stderr=subprocess.STDOUT, shell=True, encoding=locale.getpreferredencoding())
58+ if ret.returncode == 0:
59+ output = ret.stdout.strip().lower()
60+ if output.find('symbolic link') != -1:
61+ target = output.split(' ')[-1]
62+ if target[0] != '/':
63+ if target.startswith('./'):
64+ target = target.lstrip('./')
65+ target = link[:link.rfind('/') + 1] + target
66+ return target
67+ else:
68+ return link
69+ else:
70+ raise Exception(ret.stdout)
71+ 
72+ def filetype(filename: str):
73+ ret = subprocess.run(args='file -b \'' + filename + '\'', stdout=subprocess.PIPE,
74+ stderr=subprocess.STDOUT, shell=True, encoding=locale.getpreferredencoding())
75+ if ret.returncode == 0:
76+ output = ret.stdout.lower()
77+ if output.find('cannot open') != -1:
78+ return FileType.NOTFILE
79+ elif output.find('rpm') != -1:
80+ if output.find('bin') != -1:
81+ return FileType.RPM
82+ elif output.find('src') != -1:
83+ return FileType.SRCRPM
84+ elif output.find('executable') != -1:
85+ if output.find('python') != -1:
86+ return FileType.PYTHON
87+ elif output.find('shell') != -1:
88+ return FileType.SHELL
89+ elif output.find('perl') != -1:
90+ return FileType.PERL
91+ elif output.find('elf') != -1:
92+ return FileType.ELF
93+ else:
94+ return FileType.EXE
95+ elif output.find('symbolic link') != -1:
96+ return FileType.LINK
97+ else:
98+ return FileType.OTHER
Aauto-tc-gen/python_parse/parser.py+769-0
@@ -0,0 +1,769 @@
1+# This program is licensed under Mulan PSL v2.
2+# You can use it according to the terms and conditions of the Mulan PSL v2.
3+# http://license.coscl.org.cn/MulanPSL2
4+# THIS PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
5+# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
6+# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
7+# See the Mulan PSL v2 for more details.
8+####################################
9+# @Author : hourui
10+# @Contact : softgreet@qq.com
11+# @Date : 2023-10-12 12:00:00
12+# @License : Mulan PSL v2
13+# @Version : 1.0
14+# @Desc : python中参数解析
15+#####################################
16+ 
17+#coding=UTF-8
18+ 
19+import argparse
20+import optparse
21+from typing import Any, Iterable, Sequence, Union, List, Tuple, Dict
22+import copy
23+import random
24+from copy import deepcopy
25+ 
26+# 常量的定义
27+UNUSED = 0
28+USED = 1
29+ 
30+# 在一次递归的搜索参数时判断是否需要继续的全局变量
31+MORE = False
32+ 
33+# 参数的action是以下这些值的时候表明该参数不需要赋值
34+NOVALARGS = ['store_true', 'store_false', 'store_const',
35+ 'version', 'append_const', 'count', 'callback', 'help']
36+ 
37+# 用以简化一个参数解析对象里的属性,是默认值Ellipsis{...}时表明没有传递值过来,那么就可以从结果中去掉
38+def simplify(pair: dict):
39+ for key, val in list(pair.items()):
40+ if val == Ellipsis:
41+ pair.pop(key)
42+ return pair
43+ 
44+ 
45+class SubParserManager():
46+ def __init__(self, title=..., description=..., prog=..., parser_class=..., action=...,
47+ option_string=..., dest=..., required=..., help=..., metavar=...) -> None:
48+ self.title = title
49+ self.description = description
50+ self.prog = prog
51+ self.parser_class = parser_class
52+ self.action = action
53+ self.option_string = option_string
54+ self.dest = dest
55+ self.required = required
56+ self.help = help if help != ArgParser.SUPPRESS else ArgParser.DEFAULTHELP
57+ self.metavar = metavar
58+ 
59+ self.subparsers: List[SubParser] = []
60+ 
61+ def add_parser(self,
62+ command: str,
63+ help: str = ...,
64+ aliases: Sequence[str] = ...,
65+ prog=...,
66+ usage=...,
67+ description=...,
68+ epilog=...,
69+ parents=...,
70+ formatter_class=...,
71+ prefix_chars=...,
72+ fromfile_prefix_chars=...,
73+ argument_default=...,
74+ conflict_handler=...,
75+ add_help=...,
76+ allow_abbrev=...,
77+ exit_on_error=...):
78+ subparser = SubParser(command, help=help, aliases=aliases, prog=prog, usage=usage, description=description,
79+ epilog=epilog, parents=parents, formatter_class=formatter_class,
80+ prefix_chars=prefix_chars,
81+ fromfile_prefix_chars=fromfile_prefix_chars, argument_default=argument_default,
82+ conflict_handler=conflict_handler, add_help=add_help, allow_abbrev=allow_abbrev,
83+ exit_on_error=exit_on_error)
84+ self.subparsers.append(subparser)
85+ return subparser
86+ 
87+ def add_helpdfs(self):
88+ for subparser in self.subparsers:
89+ subparser.add_helpdfs()
90+ 
91+ def display(self, prefix=''):
92+ s = f'{prefix}Subcommands: '
93+ params = simplify({
94+ 'title': self.title,
95+ 'description': self.description,
96+ 'prog': self.prog,
97+ 'parser_class': self.parser_class,
98+ 'action': self.action,
99+ 'option_string': self.option_string,
100+ 'dest': self.dest,
101+ 'required': self.required,
102+ 'help': self.help,
103+ 'metavar': self.metavar
104+ })
105+ if params:
106+ s += str(params)
107+ prefix += '\t'
108+ for subparser in self.subparsers:
109+ s += f"\n{subparser.display(prefix)}"
110+ return s
111+ 
112+ def get_num(self) -> int:
113+ return sum(subparser.get_num() for subparser in self.subparsers)
114+ 
115+ def union(self, manager):
116+ if manager:
117+ self.subparsers.extend(manager.subparsers)
118+ 
119+ def add_val_help(self, value_map, help_map):
120+ for subparser in self.subparsers:
121+ subparser.add_val_help(value_map, help_map)
122+ 
123+ def yield_cmd(self, cmd):
124+ more = False
125+ n = len(cmd)
126+ for subparser in self.subparsers:
127+ more = subparser.yield_cmd(cmd)
128+ if more:
129+ break
130+ else:
131+ while len(cmd) > n:
132+ cmd.pop()
133+ return more
134+ 
135+ 
136+class Argument():
137+ def __init__(self, *name_or_flags: str, action=..., nargs=..., const=...,
138+ default: Any = ..., type=..., choices=..., required: bool = ...,
139+ help=..., metavar: Union[str, tuple[str, ...]] = ...,
140+ dest: str = ..., version: str = ..., **kwargs: Any) -> None:
141+ self.args = name_or_flags
142+ self.action = action
143+ self.nargs = nargs
144+ self.const = const
145+ self.default = default
146+ self.type = type
147+ self.choices = choices
148+ self.required = required
149+ # 默认参数的处理
150+ self.help = help if help != ArgParser.SUPPRESS else ArgParser.DEFAULTHELP
151+ self.metavar = metavar
152+ self.dest = dest
153+ self.version = version
154+ self.used = UNUSED
155+ 
156+ def __str__(self) -> str:
157+ args = list(filter(lambda x: x is not None, self.args))
158+ if not args:
159+ return ''
160+ s = ','.join(self.args)
161+ keywords = simplify({
162+ 'action': self.action,
163+ 'nargs': self.nargs,
164+ 'const': self.const,
165+ 'default': self.default,
166+ 'type': self.type,
167+ 'choices': self.choices,
168+ 'required': self.required,
169+ 'help': self.help,
170+ 'metavar': self.metavar,
171+ 'dest': self.dest,
172+ 'version': self.version
173+ })
174+ if keywords:
175+ s = f"{s}\n{keywords}"
176+ return s
177+ 
178+ def get_num(self) -> int:
179+ return 1
180+ 
181+ def add_val_help(self, value_map, help_map):
182+ args = list(filter(lambda x: x is not None, self.args))
183+ if not args:
184+ return
185+ args.sort(key=lambda x: len(x))
186+ label = args[-1].lstrip('-').replace('-', '_').upper()
187+ if self.metavar and self.metavar != Ellipsis:
188+ label = self.metavar.upper()
189+ elif self.dest and self.dest != Ellipsis:
190+ label = self.dest
191+ key = f"{UniArgParser.get_cmd_name()},{label}"
192+ if self.choices is not None and self.choices != Ellipsis:
193+ value_map[key] = self.choices
194+ else:
195+ value_map[key] = []
196+ if self.default is not None and self.default != Ellipsis:
197+ value_map[key].append(self.default)
198+ if self.const is not None and self.const != Ellipsis:
199+ value_map[key] = self.const
200+ if not value_map[key]:
201+ value_map.pop(key)
202+ if self.type and self.type != Ellipsis:
203+ help_map[key] = self.type
204+ 
205+ def yield_cmd(self, cmd):
206+ global MORE
207+ args = list(filter(lambda x: x is not None, self.args))
208+ if not args:
209+ return False
210+ required = (self.required == True or (not args[0].startswith(
211+ '-') and self.nargs != '?')) and self.default == Ellipsis
212+ if required or (MORE and self.used == UNUSED):
213+ args.sort(key=lambda x: len(x))
214+ label = args[-1].lstrip('-').replace('-', '_').upper()
215+ if self.metavar and self.metavar != Ellipsis:
216+ label = self.metavar.upper()
217+ elif self.dest and self.dest != Ellipsis:
218+ label = self.dest.upper()
219+ if self.action in NOVALARGS or '-h' in args or '--help' in args:
220+ cmd.append(args[-1])
221+ elif not args[0].startswith('-'):
222+ cmd.append(label)
223+ else:
224+ cmd.extend([args[-1], label])
225+ self.used = USED
226+ if not required:
227+ return True
228+ return False
229+ 
230+ 
231+# 分组参数,和普通参数没有区别,只是提供一个整体的抽象表述,某些参数聚集在一个组中有个统一的title和description,可能表示这些参数有某些联系或者是完成一个功能的相关参数
232+class GroupArg(Argument):
233+ def __init__(self, title=..., description=..., *name_or_flags: str, **kwargs: Any) -> None:
234+ super().__init__(*name_or_flags, **kwargs)
235+ self.arguments = []
236+ self.title = title
237+ self.description = description
238+ 
239+ def add_argument(self,
240+ *name_or_flags: str,
241+ action=...,
242+ nargs=...,
243+ const=...,
244+ default: Any = ...,
245+ type=...,
246+ choices=...,
247+ required: bool = ...,
248+ help=...,
249+ metavar: Union[str, tuple[str, ...]] = ...,
250+ dest: str = ...,
251+ version: str = ...,
252+ **kwargs: Any):
253+ self.arguments.append(Argument(*name_or_flags, action=action, nargs=nargs, const=const,
254+ default=default, type=type, choices=choices, required=required,
255+ help=help, metavar=metavar, dest=dest, version=version, **kwargs))
256+ 
257+ def get_num(self) -> int:
258+ return len(self.arguments)
259+ 
260+ def __str__(self) -> str:
261+ info = {}
262+ s = f'arg group: {len(self.arguments)}'
263+ if self.title and self.title != Ellipsis:
264+ info['title'] = self.title
265+ if self.description and self.description != Ellipsis:
266+ info['description'] = self.description
267+ if info:
268+ s += f" {info}"
269+ for arg in self.arguments:
270+ s += f"\n{arg}"
271+ return s
272+ 
273+ def add_val_help(self, value_map, help_map):
274+ for argument in self.arguments:
275+ argument.add_val_help(value_map, help_map)
276+ 
277+ def yield_cmd(self, cmd):
278+ global MORE
279+ more = False
280+ for argument in self.arguments:
281+ more = more or argument.yield_cmd(cmd)
282+ if more:
283+ MORE = False
284+ return more
285+ 
286+ 
287+# 互斥组参数,在分组参数的基础上多了一个组内参数不能同时被使用的限制
288+class ExclusiveGroupArg(GroupArg):
289+ def __init__(self, required=...) -> None:
290+ super().__init__()
291+ self.required = required
292+ self.arguments = []
293+ 
294+ def __str__(self) -> str:
295+ if len(self.arguments) == 0:
296+ return ''
297+ s = f'exclusive arg group: {len(self.arguments)}'
298+ for arg in self.arguments:
299+ s += f"\n{arg}"
300+ return s
301+ 
302+ def yield_cmd(self, cmd):
303+ global MORE
304+ more = False
305+ for argument in self.arguments:
306+ more = argument.yield_cmd(cmd)
307+ if more:
308+ return True
309+ if self.required == True:
310+ index = random.randint(0, len(self.arguments) - 1)
311+ self.arguments[index].used = UNUSED
312+ self.arguments[index].yield_cmd(cmd)
313+ return False
314+ return more
315+ 
316+ 
317+# 和argparse的ArgumentParser对应的类
318+class ArgParser:
319+ SUPPRESS = argparse.SUPPRESS
320+ DEFAULTHELP = 'show this help message and exit'
321+ 
322+ def __init__(self,
323+ prog=...,
324+ usage=...,
325+ description=...,
326+ epilog=...,
327+ parents=...,
328+ formatter_class=...,
329+ prefix_chars=...,
330+ fromfile_prefix_chars=...,
331+ argument_default=...,
332+ conflict_handler=...,
333+ add_help=...,
334+ allow_abbrev=...,
335+ exit_on_error=...) -> None:
336+ self.arguments = []
337+ self.subparserManager = None
338+ self.ArgumentParser(prog, usage, description, epilog, parents, formatter_class, prefix_chars,
339+ fromfile_prefix_chars, argument_default, conflict_handler, add_help, allow_abbrev,
340+ exit_on_error)
341+ 
342+ def __str__(self) -> str:
343+ params = simplify({
344+ 'prog': self.prog,
345+ 'usage': self.usage,
346+ 'description': self.description,
347+ 'epilog': self.epilog,
348+ 'parents': self.parents,
349+ 'formatter_class': self.formatter_class,
350+ 'prefix_chars': self.prefix_chars,
351+ 'fromfile_prefix_chars': self.fromfile_prefix_chars,
352+ 'argument_default': self.argument_default,
353+ 'conflict_handler': self.conflict_handler,
354+ 'add_help': self.add_help,
355+ 'allow_abbrev': self.allow_abbrev,
356+ 'exit_on_error': self.exit_on_error
357+ })
358+ s = f"{params}" if params else ''
359+ for arg in self.arguments:
360+ if s:
361+ s += f"\n{arg}"
362+ else:
363+ s = f"{arg}"
364+ if self.subparserManager:
365+ if s:
366+ s += f"\n{self.subparserManager.display()}"
367+ else:
368+ s = self.subparserManager.display()
369+ return s
370+ 
371+ def get_num(self) -> int:
372+ numInArgs = sum(arg.get_num() for arg in self.arguments)
373+ numInSubparser = self.subparserManager.get_num() if self.subparserManager else 0
374+ return numInArgs + numInSubparser
375+ 
376+ def ArgumentParser(self, prog=..., usage=..., description=..., epilog=..., parents=...,
377+ formatter_class=..., prefix_chars=..., fromfile_prefix_chars=..., argument_default=...,
378+ conflict_handler=..., add_help=..., allow_abbrev=..., exit_on_error=...):
379+ if not prog:
380+ prog = UniArgParser.get_cmd_name()
381+ self.prog = prog
382+ self.usage = usage
383+ self.description = description
384+ self.epilog = epilog
385+ self.parents = ...
386+ self.formatter_class = formatter_class
387+ self.prefix_chars = prefix_chars
388+ self.fromfile_prefix_chars = fromfile_prefix_chars
389+ self.argument_default = argument_default
390+ self.conflict_handler = conflict_handler
391+ self.add_help = add_help
392+ self.allow_abbrev = allow_abbrev
393+ self.exit_on_error = exit_on_error
394+ if parents != Ellipsis and parents:
395+ for parent in parents:
396+ if isinstance(parent, (ArgParser, SubParser)):
397+ self.arguments.extend(deepcopy(parent.arguments))
398+ if self.subparserManager:
399+ self.subparserManager.union(
400+ deepcopy(parent.subparserManager))
401+ else:
402+ self.subparserManager = deepcopy(
403+ parent.subparserManager)
404+ elif isinstance(parent, (GroupArg, ExclusiveGroupArg)):
405+ self.arguments.append(deepcopy(parent))
406+ else:
407+ raise RuntimeError("unknown parent type")
408+ return self
409+ 
410+ def add_argument(self, *name_or_flags: str, action=..., nargs=..., const=..., default: Any = ..., type=...,
411+ choices=..., required: bool = ..., help=..., metavar: Union[str, tuple[str, ...]] = ...,
412+ dest: str = ..., version: str = ..., **kwargs: Any):
413+ self.arguments.append(Argument(*name_or_flags, action=action, nargs=nargs, const=const,
414+ default=default, type=type, choices=choices, required=required,
415+ help=help, metavar=metavar, dest=dest, version=version, **kwargs))
416+ 
417+ def add_subparsers(self, title=..., description=..., prog=..., parser_class=..., action=...,
418+ option_string=..., dest=..., required=..., help=..., metavar=...):
419+ self.subparserManager = SubParserManager(
420+ title, description, prog, parser_class, action, option_string, dest, required, help, metavar)
421+ return self.subparserManager
422+ 
423+ def add_mutually_exclusive_group(self, required=...):
424+ arg = ExclusiveGroupArg(required)
425+ self.arguments.append(arg)
426+ return arg
427+ 
428+ def add_argument_group(self, title=..., description=...):
429+ arg = GroupArg(title, description)
430+ self.arguments.append(arg)
431+ return arg
432+ 
433+ def set_defaults(self, **kwargs):
434+ self.argument_default = kwargs
435+ 
436+ # 递归的判断命令以及子命令是否有-h这样的帮助选项
437+ def add_helpdfs(self):
438+ if self.add_help == Ellipsis or self.add_help:
439+ self.add_argument('-h', '--help', help=ArgParser.DEFAULTHELP)
440+ if self.subparserManager:
441+ self.subparserManager.add_helpdfs()
442+ 
443+ def parse_args(self, args=..., values=...):
444+ self.add_helpdfs()
445+ UniArgParser.finalParser = copy.deepcopy(self)
446+ 
447+ def parse_intermixed_args(self, args=..., values=...):
448+ self.parse_args(args, values)
449+ 
450+ def parse_known_intermixed_args(self, args=..., values=...):
451+ self.parse_args(args, values)
452+ 
453+ def parse_known_args(self, args=..., values=...):
454+ self.parse_args(args, values)
455+ 
456+ def add_val_help(self, value_map: Dict, help_map: Dict):
457+ for argument in self.arguments:
458+ argument.add_val_help(value_map, help_map)
459+ if self.subparserManager:
460+ self.subparserManager.add_val_help(value_map, help_map)
461+ 
462+ def yield_cmd(self, cmd):
463+ """
464+ :return: 返回是否取到了更多的非必选且之前没有选过的参数
465+ """
466+ global MORE
467+ more = False
468+ for argument in self.arguments:
469+ more = more or argument.yield_cmd(cmd)
470+ if more:
471+ MORE = False
472+ if more:
473+ return more
474+ if self.subparserManager:
475+ more = self.subparserManager.yield_cmd(cmd)
476+ return more
477+ 
478+ def getCmds_Vals(self) -> Tuple[List, Dict, Dict]:
479+ global MORE
480+ commands, value_map, help_map = [], {}, {}
481+ """
482+ 每次循环生成一个可行的命令格式, 在循环开始的时候设置全局变量MORE为True,然后递归的进行处理
483+ 递归的每一层对应着命令的层级,每次先处理当前层的arguments,直到所有非必选的arguments都被选过之后在进入子命令去处理
484+ """
485+ while True:
486+ MORE = True
487+ cmd = [UniArgParser.get_cmd_name()]
488+ if self.yield_cmd(cmd):
489+ commands.append(cmd)
490+ else:
491+ break
492+ # 递归的获取参数可用的值的信息以及类型信息
493+ self.add_val_help(value_map, help_map)
494+ return commands, value_map, help_map
495+ 
496+ 
497+class SubParser(ArgParser):
498+ def __init__(self,
499+ name: str,
500+ help: str = ...,
501+ aliases: Sequence[str] = ...,
502+ prog=...,
503+ usage=...,
504+ description=...,
505+ epilog=...,
506+ parents=...,
507+ formatter_class=...,
508+ prefix_chars=...,
509+ fromfile_prefix_chars=...,
510+ argument_default=...,
511+ conflict_handler=...,
512+ add_help=...,
513+ allow_abbrev=...,
514+ exit_on_error=...) -> None:
515+ super().__init__(prog, usage, description, epilog, parents, formatter_class, prefix_chars,
516+ fromfile_prefix_chars, argument_default, conflict_handler, add_help, allow_abbrev,
517+ exit_on_error)
518+ self.name = name
519+ self.help = help if help != ArgParser.SUPPRESS else ArgParser.DEFAULTHELP
520+ self.aliases = aliases
521+ self.cur = False
522+ 
523+ def add_helpdfs(self):
524+ if self.add_help == Ellipsis or self.add_help:
525+ self.add_argument('-h', '--help', help=ArgParser.DEFAULTHELP)
526+ 
527+ def display(self, prefix=''):
528+ s = f"{prefix}Subcommand: {self.name}\t"
529+ params = simplify({
530+ 'help': self.help,
531+ 'alias': self.aliases,
532+ 'prog': self.prog,
533+ 'usage': self.usage,
534+ 'description': self.description,
535+ 'epilog': self.epilog,
536+ 'parents': self.parents,
537+ 'formatter_class': self.formatter_class,
538+ 'prefix_chars': self.prefix_chars,
539+ 'fromfile_prefix_chars': self.fromfile_prefix_chars,
540+ 'argument_default': self.argument_default,
541+ 'conflict_handler': self.conflict_handler,
542+ 'add_help': self.add_help,
543+ 'allow_abbrev': self.allow_abbrev,
544+ 'exit_on_error': self.exit_on_error
545+ })
546+ if params:
547+ s += f"{params}"
548+ for arg in self.arguments:
549+ s += '\n'
550+ s += "\n".join(f"{prefix}{line}" for line in str(arg).split('\n') if line)
551+ if self.subparserManager:
552+ s += f"\n{self.subparserManager.display(prefix)}"
553+ return s
554+ 
555+ def yield_cmd(self, cmd):
556+ global MORE
557+ more = False
558+ name = f"^{self.name}"
559+ n = len(cmd)
560+ for argument in self.arguments:
561+ more = argument.yield_cmd(cmd) or more
562+ if more:
563+ if n != -1:
564+ cmd.insert(n, name)
565+ n = -1
566+ MORE = False
567+ if more:
568+ return more
569+ if isinstance(self.argument_default, dict) and 'func' in self.argument_default and not self.cur and len(
570+ cmd) == n:
571+ self.cur = True
572+ cmd.insert(n, name)
573+ MORE = False
574+ return True
575+ if self.subparserManager:
576+ cmd.insert(n, name)
577+ more = self.subparserManager.yield_cmd(cmd)
578+ return more
579+ 
580+ 
581+# 和optparse的OptionParse对象对应,这里采用了继承的方式,在此基础上添加自定义的对参数的处理逻辑
582+class Option(optparse.Option):
583+ ATTRS = ['action',
584+ 'type',
585+ 'dest',
586+ 'default',
587+ 'nargs',
588+ 'const',
589+ 'choices',
590+ 'callback',
591+ 'callback_args',
592+ 'callback_kwargs',
593+ 'help',
594+ 'metavar']
595+ 
596+ def __init__(self, *opts, **attrs: Any) -> None:
597+ self.attrs = {key: val for key,
598+ val in attrs.items() if key in self.ATTRS}
599+ super().__init__(*opts, **attrs)
600+ 
601+ def __str__(self) -> str:
602+ opts = []
603+ opts.extend(self._short_opts)
604+ opts.extend(self._long_opts)
605+ s = ','.join(opts)
606+ return f"{s}\n{self.attrs}" if self.attrs else s
607+ 
608+ 
609+class OptParser(optparse.OptionParser):
610+ SUPPRESS_HELP = optparse.SUPPRESS_HELP
611+ SUPPRESS_USAGE = optparse.SUPPRESS_USAGE
612+ 
613+ # optionparser中有帮助的信息的标签
614+ ATTRS = [
615+ 'usage',
616+ 'version',
617+ 'description',
618+ 'prog',
619+ 'epilog']
620+ 
621+ def __init__(self,
622+ usage: str = None,
623+ option_list: Iterable[optparse.Option] = None,
624+ option_class: type[optparse.Option] = Option,
625+ version: str = None,
626+ conflict_handler: str = "error",
627+ description: str = None,
628+ formatter: optparse.HelpFormatter = None,
629+ add_help_option: bool = True,
630+ prog: str = None,
631+ epilog: str = None) -> None:
632+ if not prog:
633+ prog = UniArgParser.get_cmd_name()
634+ super().__init__(usage=usage, option_list=option_list, option_class=option_class, version=version,
635+ conflict_handler=conflict_handler, description=description,
636+ formatter=formatter, add_help_option=add_help_option, prog=prog, epilog=epilog)
637+ 
638+ def OptionParser(self, *args, **kwargs):
639+ if args and len(args) == 1 and ('usage' not in kwargs or not kwargs['usage']):
640+ kwargs['usage'] = args[0]
641+ super().__init__(**kwargs)
642+ return self
643+ 
644+ def add_option(self, *args, **kwargs):
645+ opt = Option(*args, **kwargs)
646+ super().add_option(opt)
647+ 
648+ def OptionGroup(self, parser, title, description=None):
649+ return OptGroup(parser, title, description)
650+ 
651+ def parse_args(self, args=None, vals=None):
652+ if self.usage:
653+ self.usage = self.usage.replace(
654+ '%prog', UniArgParser.get_cmd_name())
655+ UniArgParser.finalParser = copy.deepcopy(self)
656+ super().parse_args()
657+ 
658+ def get_num(self) -> int:
659+ num = len(self.option_list)
660+ for group in self.option_groups:
661+ if isinstance(group, OptGroup):
662+ num += group.get_num()
663+ else:
664+ num += len(group.option_list)
665+ return num
666+ 
667+ def __str__(self) -> str:
668+ attrs = {attr: getattr(self, attr)
669+ for attr in self.ATTRS if getattr(self, attr)}
670+ s = f"{attrs}" if attrs else ''
671+ for option in self.option_list:
672+ if s:
673+ s += f"\n{option}"
674+ else:
675+ s = str(option)
676+ for group in self.option_groups:
677+ if s:
678+ s += f"\n\n{group}"
679+ else:
680+ s = str(group)
681+ return s
682+ 
683+ # optparse包的option都是可选项,直接遍历
684+ def getCmds_Vals(self) -> Tuple[List, Dict, Dict]:
685+ if not UniArgParser.CurCommand:
686+ return None
687+ name = UniArgParser.get_cmd_name()
688+ cmds, vals, typeVals, options = [], {}, {}, []
689+ options.extend(self.option_list)
690+ for groups in self.option_groups:
691+ options.extend(groups.option_list)
692+ for option in options:
693+ opts = []
694+ opts.extend(option._short_opts)
695+ opts.extend(option._long_opts)
696+ opts.sort(key=lambda x: len(x))
697+ label = opts[-1].lstrip('-').replace('-', '_').upper()
698+ if 'metavar' in option.attrs:
699+ label = option.attrs['metavar'].upper()
700+ elif 'dest' in option.attrs:
701+ label = option.attrs['dest'].upper()
702+ if ('action' in option.attrs and option.attrs['action'] in NOVALARGS) or '-h' in opts or '--help' in opts:
703+ cmds.append([f"{name}", opts[0]])
704+ else:
705+ cmds.append([f"{name}", opts[0], label])
706+ key = f"{name},{label}"
707+ if 'choices' in option.attrs:
708+ vals[key] = option.attrs['choices'] if option.attrs['choices'] else []
709+ else:
710+ vals[key] = []
711+ if 'default' in option.attrs and option.attrs['default'] is not None:
712+ vals[key].append(option.attrs['default'])
713+ # if 'const' in option.attrs and option.attrs['const'] is not None:
714+ # vals[key] = option.attrs['const']
715+ if not vals[key]:
716+ vals.pop(key)
717+ if 'type' in option.attrs:
718+ typeVals[key] = option.attrs['type']
719+ return (cmds, vals, typeVals)
720+ 
721+ 
722+class OptGroup(optparse.OptionGroup):
723+ def __init__(self, parser, title, description=None) -> None:
724+ super().__init__(parser, title, description=description)
725+ 
726+ def add_option(self, *args, **kwargs):
727+ opt = Option(*args, **kwargs)
728+ super().add_option(opt)
729+ 
730+ def get_num(self) -> int:
731+ return len(self.option_list)
732+ 
733+ def __str__(self) -> str:
734+ params = {
735+ 'title': self.title
736+ }
737+ if self.description:
738+ params['description'] = self.description
739+ s = f"Group: {params}"
740+ for option in self.option_list:
741+ s += f"\n{option}"
742+ return s
743+ 
744+ 
745+class UniArgParser:
746+ PARSERS = ('argparse.ArgumentParser',
747+ 'optparse.OptionParser', 'argparse', 'optparse')
748+ 
749+ finalParser = None
750+ CurCommand = None
751+ 
752+ @classmethod
753+ def build(cls, parser: str, args=[], keywords={}):
754+ """
755+ 工厂方法,根据传入的parser的不同创建不同的参数处理对象
756+ """
757+ if parser in cls.PARSERS:
758+ return ArgParser(**keywords) if parser.startswith('argparse') else OptParser(**keywords)
759+ return None
760+ 
761+ @classmethod
762+ def get_cmd_name(cls):
763+ """
764+ 获取当前处理的命令的名字
765+ """
766+ if cls.CurCommand:
767+ curCmd = cls.CurCommand
768+ return curCmd.path[curCmd.path.rfind('/') + 1:]
769+ return "%prog"
Aauto-tc-gen/python_parse/py_arg_extractor.py+1046-0
@@ -0,0 +1,1046 @@
1+# This program is licensed under Mulan PSL v2.
2+# You can use it according to the terms and conditions of the Mulan PSL v2.
3+# http://license.coscl.org.cn/MulanPSL2
4+# THIS PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
5+# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
6+# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
7+# See the Mulan PSL v2 for more details.
8+####################################
9+# @Author : hourui
10+# @Contact : softgreet@qq.com
11+# @Date : 2023-10-12 12:00:00
12+# @License : Mulan PSL v2
13+# @Version : 1.0
14+# @Desc : python中参数解析
15+#####################################
16+ 
17+#coding=UTF-8
18+ 
19+import ast
20+import os
21+import subprocess
22+import sys
23+from abc import ABCMeta
24+from copy import deepcopy
25+from typing import Union, Iterable
26+import timeout_decorator
27+from pebble import ProcessPool
28+from python_parse.parser import UniArgParser, ArgParser, OptParser
29+from python_parse.commonlib import FileType, compare_ast
30+from logger import log
31+ 
32+# 检测到这些函数调用时不予执行
33+no_exe_label = {
34+ 'input',
35+ 'print',
36+ 'os.system',
37+ 'time.sleep',
38+ 'subprocess'
39+}
40+ 
41+pool = ProcessPool()
42+log_file = ''
43+ 
44+ 
45+def subprocess_task(code='', global_env={}):
46+ """
47+ Args:
48+ code: 放在子进程中尝试执行的代码,防止因执行所需系统环境和参数的缺失导致的崩溃
49+ global_env: python代码的执行时的环境
50+ """
51+ stdin_bac, stdout_bac, stderr_bac = sys.stdin, sys.stdout, sys.stderr
52+ sys.stdin, sys.stdout, sys.stderr = None, None, None
53+ # 对于因执行所需系统环境和参数的缺失导致的异常不作处理
54+ try:
55+ ret_sink = '__RET_SINK'
56+ exec(f"{ret_sink}={code}", global_env)
57+ return global_env[ret_sink]
58+ except:
59+ log.warning("因模拟执行python代码时的输入参数未知以及所需系统环境的缺失导致的异常", log_file)
60+ finally:
61+ sys.stdin, sys.stdout, sys.stderr = stdin_bac, stdout_bac, stderr_bac
62+ 
63+ 
64+@timeout_decorator.timeout(1)
65+def timing_wrap_func(func, args, keywords):
66+ """
67+ 封装需要执行的函数,超时退出
68+ :param: 执行的回调函数func及其参数args, keywords
69+ """
70+ stdin_bac, stdout_bac, stderr_bac = sys.stdin, sys.stdout, sys.stderr
71+ sys.stdin, sys.stdout, sys.stderr = None, None, None
72+ try:
73+ return func(*args, **keywords)
74+ except:
75+ log.warning("因模拟执行python代码时的输入参数未知以及所需系统环境的缺失导致的异常", log_file)
76+ finally:
77+ sys.stdin, sys.stdout, sys.stderr = stdin_bac, stdout_bac, stderr_bac
78+ 
79+ 
80+class ImportObj:
81+ """
82+ 引用对象, 封装了引用的路径,如果不封装直接用string表示的话将无法区分是一个引用还是一个值为字符串的变量。
83+ """
84+ 
85+ def __init__(self, val=None) -> None:
86+ self.val: str = val
87+ 
88+ # 引用路径的合并
89+ def __add__(self, obj):
90+ if isinstance(obj, str):
91+ return ImportObj(f"{self.val}.{obj}")
92+ elif isinstance(obj, ImportObj):
93+ return ImportObj(f"{self.val}.{obj.val}")
94+ else:
95+ raise RuntimeError("Wrong parameter type")
96+ 
97+ def __str__(self) -> str:
98+ return self.val
99+ 
100+ 
101+class Environment(metaclass=ABCMeta):
102+ """python执行环境的一个不完全简单模拟
103+ 作为抽象基类存储了当前环境下定义和引用的变量,函数以及类;同时定义了对各种ast节点的通用处理方法,即命名为exec_{ast节点类型}的方法
104+ Attributes:
105+ driver: ast节点的处理方法的驱动,通过表驱动的方式用来判断该如何处理特定类型的ast节点,具体通过类方法exec_driver来使用和维护
106+ UNSCAN/SCANNING/SCANNED: 当前环境的扫描状态
107+ """
108+ UNSCAN = 0
109+ SCANNING = 1
110+ SCANNED = 2
111+ 
112+ driver = None
113+ 
114+ @classmethod
115+ def exec_driver(cls, env, node: ast.AST):
116+ """
117+ Args:
118+ env:通过类方法来调用的,所以需要再传入一个Environment对象,调用的时候通常是传递self
119+ node: 抽象语法树节点
120+ """
121+ if not cls.driver:
122+ # 为不需要进行处理的节点定义的默认不作任何处理的函数
123+ def default(m, n): return None
124+ 
125+ cls.collection = {
126+ ast.List: list,
127+ ast.Tuple: tuple,
128+ ast.Set: set,
129+ }
130+ cls.bin_op = {
131+ ast.Add: lambda x, y: x + y,
132+ ast.Sub: lambda x, y: x - y,
133+ ast.Mult: lambda x, y: x * y,
134+ ast.Div: lambda x, y: x / y,
135+ ast.FloorDiv: lambda x, y: x // y,
136+ ast.Mod: lambda x, y: x % y,
137+ ast.Pow: lambda x, y: x ** y,
138+ ast.LShift: lambda x, y: x << y,
139+ ast.RShift: lambda x, y: x >> y,
140+ ast.BitOr: lambda x, y: x | y,
141+ ast.BitAnd: lambda x, y: x & y,
142+ ast.MatMult: lambda x, y: x @ y
143+ }
144+ cls.driver = {
145+ ast.Import: cls.exec_import_statement,
146+ ast.ImportFrom: cls.exec_import_from,
147+ ast.ClassDef: cls.exec_cls_or_fun_def,
148+ ast.FunctionDef: cls.exec_cls_or_fun_def,
149+ ast.Expr: cls.exec_expr,
150+ ast.Call: cls.exec_call,
151+ ast.Return: cls.exec_return,
152+ ast.If: cls.exec_if_statement,
153+ ast.With: cls.exec_block,
154+ ast.Try: cls.exec_block,
155+ ast.While: cls.exec_block,
156+ ast.For: cls.exec_block,
157+ ast.Constant: cls.exec_constant,
158+ ast.Attribute: cls.exec_attribute,
159+ ast.Name: cls.exec_name,
160+ ast.Assign: cls.exec_assign,
161+ ast.AugAssign: cls.exec_aug_assign,
162+ ast.List: cls.exec_collection,
163+ ast.Tuple: cls.exec_collection,
164+ ast.Set: cls.exec_collection,
165+ ast.Dict: cls.exec_dict,
166+ ast.ListComp: cls.exec_collection_comp,
167+ ast.SetComp: cls.exec_collection_comp,
168+ ast.GeneratorExp: cls.exec_collection_comp,
169+ ast.DictComp: cls.exec_collection_comp,
170+ ast.Delete: default,
171+ ast.Break: default,
172+ ast.Continue: default,
173+ ast.Raise: default,
174+ ast.arguments: default,
175+ ast.Compare: default,
176+ ast.Global: default,
177+ ast.Lambda: default,
178+ ast.BinOp: cls.exec_bin_op,
179+ ast.UnaryOp: cls.exec_unary_op,
180+ ast.BoolOp: default,
181+ ast.IfExp: default,
182+ ast.Pass: default,
183+ ast.Slice: default,
184+ ast.Subscript: default,
185+ ast.Assert: default,
186+ ast.Yield: default,
187+ ast.YieldFrom: default,
188+ ast.Starred: default,
189+ ast.JoinedStr: default
190+ }
191+ return cls.driver[type(node)](env, node)
192+ 
193+ def __init__(self, node: Union[ast.Module, ast.FunctionDef, ast.ClassDef], parent_env) -> None:
194+ """
195+ Args:
196+ node: 和当前环境相对应的ast节点,节点类型只能是ast.Module, ast.FunctionDef, ast.ClassDef,
197+ 分别对应了python执行时的模块级别的全局环境(ModEnv),函数和方法内部的局部环境(FuncEnv)以及类环境(ClsEnv)
198+ parent_env: 当前环境的父环境。ModEnv没有父环境。而三种环境下面都可能会有子FuncEnv,子ClsEnv。
199+ Attributes:
200+ self.unknown_import_star: 未知的from xxx import *
201+ self.imports: 对python内置变量、方法和对象,第三方包的变量、方法和对象以及无法求出值的包内其它模块的变量的引用
202+ self.variables: 存储了当前环境下定义的变量对象和方法/类的定义以及从包内其它模块导入的可以求出值的变量和方法/类定义
203+ self.import_codes: 存储了检测到的import语句,用于在执行一些未知方法时模拟Python的命名空间
204+ self.scanned: 当前Environment是否被扫描过,扫描过的将不再重复检测方法/类定义
205+ """
206+ self.node = node
207+ self.parent_env: Environment = parent_env
208+ self.unknown_import_star = set()
209+ self.imports = {}
210+ self.variables = {}
211+ self.import_codes = []
212+ self.scanned = Environment.UNSCAN
213+ 
214+ def exec(self, args: list = None, keywords: dict = None):
215+ if self.scanned == Environment.UNSCAN:
216+ self.scanned = Environment.SCANNING
217+ ret = self.exec_codeblock(list(ast.iter_child_nodes(self.node)))
218+ self.scanned = Environment.SCANNED
219+ return ret
220+ 
221+ def exec_codeblock(self, nodes: list[ast.AST]):
222+ ret = None
223+ for node in nodes:
224+ val = self.exec_driver(self, node)
225+ # 检测的目的是检测到所有语句,所以遇到return语句并不真的返回,并且选择最后一个作为返回值
226+ if val and isinstance(node, ast.Return):
227+ ret = val
228+ return ret
229+ 
230+ def exec_import_statement(self, node: ast.Import) -> None:
231+ """处理import语句
232+ 能成功求解出值的放入Variable中,否则放入Import中
233+ """
234+ if self.scanned != Environment.SCANNING:
235+ return
236+ for alias in node.names:
237+ name = alias.asname if alias.asname else alias.name
238+ target = self.locate(alias.name)
239+ if target:
240+ self.variables[name] = target
241+ else:
242+ self.imports[name] = ImportObj(alias.name)
243+ self.import_codes.append(ast.unparse(node))
244+ 
245+ def exec_import_from(self, node: ast.ImportFrom) -> None:
246+ if self.scanned != Environment.SCANNING:
247+ return None
248+ if node.level != 0:
249+ # 处理从相对路径.和..导入的情况
250+ path = self.get_root_env().belongs.get_path()
251+ if path.endswith('__init__'):
252+ path = path[:-9]
253+ node.level -= 1
254+ while node.level != 0:
255+ path = path[:path.rfind('.')]
256+ node.level -= 1
257+ node.module = f"{path}.{node.module}" if node.module else path
258+ for alias in node.names:
259+ name = alias.asname if alias.asname else alias.name
260+ if name != '*':
261+ # 处理from xxx import *
262+ module_path = f"{node.module}.{alias.name}"
263+ val = self.locate(module_path)
264+ if val:
265+ self.variables[name] = val
266+ else:
267+ self.imports[name] = ImportObj(module_path)
268+ self.import_codes.append(ast.unparse(node))
269+ else:
270+ module = self.locate(node.module)
271+ if module:
272+ # 定位到了Package/Module这样的Python包内文件组织结构上的抽象类时需要转化为对应的初始模块(__init__.py)/ModEnv
273+ if isinstance(module, Package):
274+ module = module.init
275+ if isinstance(module, Module):
276+ source_env = module.env
277+ else:
278+ source_env = module
279+ if not source_env.scanned:
280+ source_env.exec()
281+ self.imports.update(source_env.imports)
282+ self.variables.update(source_env.variables)
283+ else:
284+ self.unknown_import_star.add(ImportObj(node.module))
285+ self.import_codes.append(ast.unparse(node))
286+ 
287+ def exec_cls_or_fun_def(self, node: Union[ast.ClassDef, ast.FunctionDef]) -> None:
288+ """
289+ 存储函数和类定义到Variable中
290+ """
291+ if self.scanned != Environment.SCANNING:
292+ return
293+ if isinstance(node, ast.ClassDef):
294+ self.variables[node.name] = ClsEnv(node, self)
295+ elif isinstance(node, ast.FunctionDef):
296+ self.variables[node.name] = FuncEnv(node, self)
297+ else:
298+ raise RuntimeError("Wrong args in exec_ClsOrFunDef!")
299+ 
300+ def dfs_assign(self, targets: Union[list, tuple], vals: Iterable):
301+ n = len(targets)
302+ if len(vals) != n:
303+ vals = [None for i in range(n)]
304+ for i in range(n):
305+ target = str(targets[i])
306+ if (vals[i] is not None) or (target not in self.variables):
307+ self.variables[target] = vals[i]
308+ 
309+ def exec_assign(self, node: ast.Assign) -> None:
310+ """
311+ 处理赋值语句, 因为Python支持a,(b,[c,d],e),f这样的递归赋值,需要递归的进行处理(调用dfs_Assign方法)
312+ """
313+ val = self.exec_driver(self, node.value)
314+ targets = [self.exec_driver(self, target) for target in node.targets]
315+ if isinstance(targets[0], (tuple, list)):
316+ if not isinstance(val, Iterable):
317+ val = [val]
318+ self.dfs_assign(targets[0], val)
319+ else:
320+ for target in targets:
321+ if not target:
322+ continue
323+ if isinstance(target, ImportObj):
324+ target = target.val
325+ if target.startswith('self.'):
326+ cls_env = self.get_cls_env()
327+ cls_env.variables[target[5:]] = val
328+ else:
329+ self.variables[target] = val
330+ else:
331+ raise RuntimeError("unknown ret type")
332+ 
333+ def exec_aug_assign(self, node: ast.AugAssign) -> None:
334+ trans_bin_op_node = ast.BinOp()
335+ trans_bin_op_node.left = deepcopy(node.target)
336+ trans_bin_op_node.left.ctx = ast.Load()
337+ trans_bin_op_node.op = node.op
338+ trans_bin_op_node.right = node.value
339+ trans_assign_node = ast.Assign()
340+ trans_assign_node.targets = [node.target]
341+ trans_assign_node.value = trans_bin_op_node
342+ # 防止在ast.unparse查看对应代码时出错,所以给了个虚假的临时行号1,但它不影响unparse的结果
343+ trans_assign_node.lineno = 1
344+ self.exec_driver(self, trans_assign_node)
345+ 
346+ def exec_if_statement(self, node: ast.If):
347+ # 当测试条件是__name__ == '__main__'时只有启动模块才往下执行内部的代码块
348+ target = ast.parse(
349+ "if __name__ == '__main__':\n\tpass", mode='exec').body[0].test
350+ if (not compare_ast(node.test, target)) or self.booted():
351+ return self.exec_codeblock(node.body)
352+ 
353+ def exec_block(self, node: Union[ast.With, ast.Try, ast.While, ast.For]):
354+ return self.exec_codeblock(node.body)
355+ 
356+ def exec_expr(self, node: ast.Expr) -> None:
357+ self.exec_driver(self, node.value)
358+ 
359+ def exec_call(self, node: ast.Call):
360+ func = self.exec_driver(self, node.func)
361+ node_args, node_keywords = node.args, node.keywords
362+ args, keywords = [], {}
363+ if node_args:
364+ if isinstance(node_args[-1], ast.Starred):
365+ star_val = self.exec_driver(self, node_args[-1].value)
366+ if isinstance(star_val, Iterable):
367+ args.extend(star_val)
368+ node_args = node_args[:-1]
369+ for arg in node_args:
370+ val = self.exec_driver(self, arg)
371+ args.append(val.val if isinstance(val, ImportObj) else val)
372+ if node_keywords:
373+ if not node_keywords[-1].arg:
374+ star_val = self.exec_driver(self, node_keywords[-1].value)
375+ if isinstance(star_val, dict):
376+ keywords.update(star_val)
377+ node_keywords = node_keywords[:-1]
378+ for keyword in node_keywords:
379+ val = self.exec_driver(self, keyword.value)
380+ if isinstance(val, Environment):
381+ val = str(val)
382+ keywords[keyword.arg] = val.val if isinstance(
383+ val, ImportObj) else val
384+ if func:
385+ # 对类名的调用视为对类的__init__方法的调用,在该类之前未被扫描过的时候先执行一遍
386+ if isinstance(func, ClsEnv):
387+ if func.scanned == Environment.UNSCAN:
388+ func.exec()
389+ if '__init__' in func.variables:
390+ func.variables['__init__'].exec(args, keywords)
391+ return func
392+ # 对自身的递归调用不予执行
393+ if isinstance(func, FuncEnv):
394+ if func != self:
395+ return func.exec(args, keywords)
396+ return None
397+ elif isinstance(func, ImportObj):
398+ func = func.val
399+ # 程序国际化时对字符串的封装,_("xxx")可以将"xxx"转化为对应国家地区的文字
400+ if func == '_':
401+ return args[0] if args else None
402+ # 通过load_entry_point根据group和entry_name动态加载软件包真正的执行入口函数时的处理
403+ if func == 'load_entry_point':
404+ _, group, entry_name = args
405+ name = self.get_entry(group, entry_name)
406+ target = self.locate(name)
407+ if isinstance(target, FuncEnv):
408+ return target.exec(args, keywords)
409+ return None
410+ # 对super调用的处理,仅处理了和参数相关的部分,其它调用不予处理
411+ if func == "super":
412+ if not args or args[0] == self.get_cls_env():
413+ for base in self.get_cls_env().bases:
414+ if isinstance(base, ArgParser) or isinstance(base, OptParser):
415+ return base
416+ return None
417+ # 处理self调用
418+ if func.startswith('self.'):
419+ cls_env = self.get_cls_env()
420+ ok, ret = cls_env.find(func[5:])
421+ if ok:
422+ return ret
423+ if func in UniArgParser.PARSERS:
424+ # 创建参数处理库optparse/argparse的参数parser对象时转为对Parser.py中自定义参数parser的对象的创建
425+ return UniArgParser.build(func, args, keywords)
426+ else:
427+ # 未知的引用方法的执行
428+ try:
429+ keywords = ','.join(
430+ f"{key}={val}" for key, val in keywords.items())
431+ args = ','.join(f"'{arg}'" if isinstance(
432+ arg, str) else str(arg) for arg in args)
433+ if keywords and args:
434+ code = "{}({},{})".format(func, args, keywords)
435+ elif keywords:
436+ code = "{}({})".format(func, keywords)
437+ elif args:
438+ code = "{}({})".format(func, args)
439+ else:
440+ code = "{}()".format(func)
441+ if func not in no_exe_label:
442+ return self.exec_code(code)
443+ except:
444+ # 屏蔽处理未知引用方法导致的异常
445+ log.warning("因模拟执行python代码时的输入参数未知以及所需系统环境的缺失导致的异常", log_file)
446+ elif callable(func):
447+ # 可调用对象的处理,包括内置方法(str.split,list.append等)的执行和自定义参数parser对象的方法的执行
448+ return timing_wrap_func(func, args=args, keywords=keywords)
449+ return None
450+ 
451+ def exec_return(self, node: ast.Return):
452+ if node.value:
453+ return self.exec_driver(self, node.value)
454+ return None
455+ 
456+ def exec_constant(self, node: ast.Constant):
457+ return node.value
458+ 
459+ def exec_attribute(self, node: ast.Attribute):
460+ """ 处理属性Attribute
461+ 属性在ast节点中被表示为:{node.value}.{node.attr},且使用属性的方式有ast.Load,ast.Store,ast.Del
462+ ast.Load需要去加载属性的值
463+ ast.Store只需要返回对应的ImportObj对象,方便后续赋值
464+ ast.Del不予处理,目标是检测代码而不是真的执行,不过仍然和ast.Store一样返回了对应的ImportObj对象
465+ """
466+ val = self.exec_driver(self, node.value)
467+ if val:
468+ # node.value是Package对象时递归的去寻找node.attr,对应于从包名开始书写的Python表达式,eg: os.path.xxx
469+ if isinstance(val, Package):
470+ return val.dfs_locate([node.attr])
471+ # node.value是Module对象时需要转化为Module对应的ModEnv,然后在后续在ModEnv中进行寻找
472+ if isinstance(val, Module):
473+ val = val.env
474+ if isinstance(node.ctx, ast.Load):
475+ if isinstance(val, (ModEnv, ClsEnv)):
476+ return val.find(node.attr)[1]
477+ elif isinstance(val, ImportObj):
478+ if val.val == 'self':
479+ ok, ret = self.get_cls_env().find(node.attr)
480+ if ok:
481+ return ret
482+ val = val + node.attr
483+ if val.val in no_exe_label:
484+ return None
485+ ok, ret = self.find(val.val)
486+ if ok:
487+ return ret
488+ if val.val not in UniArgParser.PARSERS:
489+ return val
490+ else:
491+ return UniArgParser.build(val.val)
492+ # 可能在之前已经得到了一个对象,即node.value是一个对象,此时通过内置函数直接返回对应的属性对象
493+ elif hasattr(val, node.attr):
494+ return getattr(val, node.attr)
495+ else:
496+ return None
497+ else:
498+ if isinstance(val, (ModEnv, ClsEnv)):
499+ val = ImportObj(val.get_path())
500+ if isinstance(val, ImportObj):
501+ return val + node.attr
502+ return None
503+ 
504+ def exec_name(self, node: ast.Name):
505+ # python 中__file__表示当前文件名,这里没有选择去获取当前环境的根环境的所属模块的名字,直接取了命令名字。在检测参数的目标导向下其它文件的文件名并不重要
506+ if node.id == '__file__':
507+ return self.get_cur_command()
508+ if isinstance(node.ctx, ast.Load):
509+ # self直接返回了一个属性对象,放到了上一级去处理,也可以返回当前所属的ClsEnv
510+ if node.id == 'self':
511+ return ImportObj('self')
512+ ok, ret = self.find(node.id)
513+ if ok:
514+ if isinstance(ret, ImportObj):
515+ val = ret.val
516+ if val in no_exe_label:
517+ return None
518+ for i in range(len(val) + 1):
519+ if (i == len(val) or val[i] == '.') and val[:i] in UniArgParser.PARSERS:
520+ ret = UniArgParser.build(val[:i])
521+ if val[i + 1:]:
522+ attrs = val[i + 1:].split('.')
523+ for attr in attrs:
524+ if hasattr(ret, attr):
525+ ret = getattr(ret, attr)
526+ break
527+ return ret
528+ return ImportObj(node.id)
529+ 
530+ def exec_collection(self, node: Union[ast.List, ast.Tuple, ast.Set]):
531+ # 处理集合对象list,tuple,set。该return语句等价于list/tuple/set(...), ...指代集合对象内部的值
532+ return self.__class__.collection[type(node)](self.exec_driver(self, elt) for elt in node.elts)
533+ 
534+ def exec_collection_comp(self, node: Union[ast.ListComp, ast.SetComp, ast.GeneratorExp]):
535+ # 处理Python中的推导式,类似于[i for i in ...]
536+ val = self.exec_code(ast.unparse(node))
537+ if val and isinstance(node, ast.GeneratorExp):
538+ try:
539+ return list(val)
540+ except:
541+ # 并非所有情况都能转化成功
542+ log.warning("因模拟执行python代码时的输入参数未知以及所需系统环境的缺失导致的异常", log_file)
543+ return val
544+ 
545+ def exec_code(self, code: str):
546+ """在Python中通过exec的方式真正的执行一段代码code
547+ 在执行之前先获取到当前环境以及父环境的所有可能的import语句并执行,得到一个global_env
548+ 同时将当前环境以及父环境的variables中的内容更新到global_env中
549+ 最后将code,globalEnv封装成一个子进程任务放到子进程中去执行
550+ """
551+ try:
552+ global_env = {}
553+ for importCode in self.get_import_codes():
554+ try:
555+ exec(importCode, global_env)
556+ except ImportError:
557+ continue
558+ global_env.update(self.variables)
559+ node = self.parent_env
560+ while node:
561+ for key, val in node.variables.items():
562+ if key not in global_env:
563+ global_env[key] = val
564+ node = node.parent_env
565+ future = pool.schedule(subprocess_task, args=[
566+ code, global_env], timeout=1)
567+ result = future.result(1)
568+ return result
569+ except:
570+ return None
571+ 
572+ def exec_dict(self, node: ast.Dict):
573+ """
574+list/set/tuple类似,不过是键值对的形式
575+ """
576+ keys, vals = node.keys, node.values
577+ ret = {}
578+ if keys:
579+ if keys[-1] is None:
580+ dblstar_val = self.exec_driver(self, vals[-1])
581+ if isinstance(dblstar_val, dict):
582+ ret.update(dblstar_val)
583+ keys = keys[:-1]
584+ vals = vals[:-1]
585+ for i in range(len(keys)):
586+ key = self.exec_driver(self, keys[i])
587+ val = self.exec_driver(self, vals[i])
588+ ret[key] = val
589+ return ret
590+ 
591+ def exec_bin_op(self, node: ast.BinOp):
592+ """对二元运算符的处理
593+ 具体的二元运算符的类型以及对应的执行方式定义在cls.bin_op中
594+ """
595+ left = self.exec_driver(self, node.left)
596+ right = self.exec_driver(self, node.right)
597+ try:
598+ return self.bin_op[type(node.op)](left, right)
599+ except:
600+ return None
601+ 
602+ def exec_unary_op(self, node: ast.UnaryOp):
603+ val = self.exec_driver(self, node.operand)
604+ try:
605+ op = node.op
606+ if isinstance(op, ast.UAdd):
607+ return val
608+ elif isinstance(op, ast.USub):
609+ return -val
610+ elif isinstance(op, ast.Not):
611+ return not val
612+ elif isinstance(op, ast.Invert):
613+ return ~val
614+ except:
615+ return None
616+ 
617+ # 判断当前环境所属模块是否是启动模块
618+ def booted(self) -> bool:
619+ return self.get_root_env().belongs.booted()
620+ 
621+ # 调用dfs_locate的入口
622+ def locate(self, name: str):
623+ if name:
624+ name = name.split('.')
625+ root_env = self.get_root_env()
626+ site_package = root_env.belongs.get_root_pyunit()
627+ return site_package.dfs_locate(name)
628+ return None
629+ 
630+ def dfs_locate(self, names: list[str]):
631+ """递归的一层一层的去定位一个变量/ImportObj对象/函数/类所在的位置"""
632+ if self.scanned == Environment.UNSCAN:
633+ self.exec()
634+ name = names[0]
635+ if name in self.imports:
636+ if len(names) != 1:
637+ return None
638+ else:
639+ return self.imports[name]
640+ elif name in self.variables:
641+ target = self.variables[name]
642+ if isinstance(target, (ClsEnv, FuncEnv)):
643+ if len(names) > 1:
644+ return target.dfs_locate(names[1:])
645+ else:
646+ return target
647+ else:
648+ if len(names) != 1:
649+ return None
650+ else:
651+ return target
652+ else:
653+ return None
654+ 
655+ def get_cur_command(self) -> str:
656+ return self.parent_env.get_cur_command()
657+ 
658+ # 获取当前环境在Python包中的完整路径
659+ def get_path(self) -> str:
660+ return f"{self.parent_env.get_path()}.{self.node.name}"
661+ 
662+ def get_root_env(self):
663+ return self.parent_env.get_root_env()
664+ 
665+ # 根据group,name获取真正的执行入口entry
666+ def get_entry(self, group, name):
667+ root_env = self.get_root_env()
668+ site_package = root_env.belongs.get_root_pyunit()
669+ return site_package.get_entry(group, name)
670+ 
671+ def get_import_codes(self):
672+ import_codes = []
673+ import_codes.extend(self.parent_env.get_import_codes())
674+ import_codes.extend(self.import_codes)
675+ return import_codes
676+ 
677+ def get_cls_env(self):
678+ return None
679+ 
680+ # 在环境中查找具体的对象和变量,如果找不到,就在父环境中去找
681+ def find(self, name: str):
682+ if self.scanned == Environment.UNSCAN:
683+ self.exec()
684+ ok = False
685+ if name in self.variables:
686+ name = self.variables[name]
687+ ok = True
688+ elif name in self.imports:
689+ name = self.imports[name]
690+ ok = True
691+ elif self.parent_env:
692+ ok, name = self.parent_env.find(name)
693+ return ok, name
694+ 
695+ 
696+class PyUnit(metaclass=ABCMeta):
697+ def __init__(self, path: str, name: str, parent_node) -> None:
698+ self.name = name
699+ self.path = path
700+ self.parent_node: PyUnit = parent_node
701+ 
702+ def get_path(self) -> str:
703+ if self.parent_node:
704+ path = self.parent_node.get_path()
705+ if path:
706+ return f"{path}.{self.name}"
707+ return self.name
708+ 
709+ def get_root_pyunit(self):
710+ if self.parent_node:
711+ return self.parent_node.get_root_pyunit()
712+ return self
713+ 
714+ def get_cur_command(self) -> str:
715+ return self.parent_node.get_cur_command()
716+ 
717+ 
718+class Package(PyUnit):
719+ """
720+ 扫描python代码包的组织结构并以树的形式存储
721+ """
722+ 
723+ def __init__(self, path: str, name: str, parent_node) -> None:
724+ super().__init__(path, name, parent_node)
725+ self.subunits = {}
726+ self.init: Module = None
727+ for f in os.listdir(path):
728+ if f == '__pycache__':
729+ continue
730+ filepath = os.path.join(path, f)
731+ if os.path.isfile(filepath) and (
732+ FileType.filetype(filepath) == FileType.PYTHON or filepath.endswith('.py')):
733+ if f.endswith('.py'):
734+ f = f[:-3]
735+ if f == '__init__':
736+ self.init = Module(filepath, f, self)
737+ else:
738+ self.subunits[f] = Module(filepath, f, self)
739+ elif os.path.isdir(filepath):
740+ self.subunits[f] = Package(filepath, f, self)
741+ 
742+ def dfs_locate(self, names: list[str]):
743+ if self.init:
744+ ans = self.init.dfs_locate(names)
745+ if ans:
746+ return ans
747+ name = names[0]
748+ for subunit_name, subunit in self.subunits.items():
749+ if subunit_name == name:
750+ if len(names) > 1:
751+ return subunit.dfs_locate(names[1:])
752+ else:
753+ return subunit
754+ return None
755+ 
756+ def get_boot_mod(self):
757+ for subunit in self.subunits.values():
758+ val = subunit.get_boot_mod()
759+ if val:
760+ return val
761+ return None
762+ 
763+ 
764+class SitePackage(PyUnit):
765+ """是源码中的python一级包的父目录的抽象,还包括了可执行的python命令在内"""
766+ 
767+ def __init__(self, path: str) -> None:
768+ self.entry_points = {}
769+ self.packages = {}
770+ self.commands = []
771+ super().__init__(path, '', None)
772+ for f in os.listdir(path):
773+ filepath = os.path.join(path, f)
774+ if os.path.isdir(filepath):
775+ if filepath.endswith('egg-info'):
776+ entry_path = os.path.join(filepath, 'entry_points.txt')
777+ if not os.path.isfile(entry_path):
778+ continue
779+ group = None
780+ with open(entry_path, mode='r') as entry_points:
781+ for line in entry_points.readlines():
782+ line = line.strip()
783+ if line.startswith('[') and line.endswith(']'):
784+ group = line[1:-1]
785+ entry = {}
786+ self.entry_points[group] = entry
787+ elif line.find('=') != -1:
788+ name, val = line.split('=')
789+ entry[name.strip()] = val.strip().replace(':', '.')
790+ else:
791+ self.packages[f] = Package(filepath, f, self)
792+ 
793+ def dfs_locate(self, names: list[str]):
794+ name = names[0]
795+ for package_name, package in self.packages.items():
796+ if package_name == name:
797+ if len(names) > 1:
798+ return package.dfs_locate(names[1:])
799+ else:
800+ return package
801+ return None
802+ 
803+ def get_entry(self, group, name):
804+ if self.entry_points and group in self.entry_points.keys():
805+ entry_points = self.entry_points[group]
806+ if name in entry_points.keys():
807+ return entry_points[name]
808+ return None
809+ 
810+ def get_cur_command(self):
811+ return self.get_boot_mod()
812+ 
813+ def get_boot_mod(self):
814+ """
815+ 确定启动模块
816+ """
817+ for package in self.packages.values():
818+ val = package.get_boot_mod()
819+ if val:
820+ return val
821+ for commands in self.commands:
822+ val = commands.get_boot_mod()
823+ if val:
824+ return val
825+ return '__file__'
826+ 
827+ def extend_commands(self, commands):
828+ self.commands.extend(commands)
829+ 
830+ 
831+class Module(PyUnit):
832+ def __init__(self, path: str, name: str, parent_node) -> None:
833+ super().__init__(path, name, parent_node)
834+ srcfile = open(self.path, mode='r')
835+ source = ''.join(srcfile.readlines())
836+ node = ast.parse(source, mode='exec')
837+ self.env = ModEnv(node, self)
838+ self.boot = False
839+ 
840+ def exec(self):
841+ self.env.exec()
842+ 
843+ def dfs_locate(self, names: list[str]):
844+ return self.env.dfs_locate(names)
845+ 
846+ def booted(self) -> bool:
847+ return self.boot
848+ 
849+ def get_cur_command(self) -> str:
850+ if self.boot:
851+ return self.path[self.path.rfind('/') + 1:]
852+ if self.parent_node:
853+ return self.parent_node.get_cur_command()
854+ return None
855+ 
856+ def get_boot_mod(self) -> str:
857+ if self.boot:
858+ return self.path[self.path.rfind('/') + 1:]
859+ return None
860+ 
861+ 
862+class ModEnv(Environment):
863+ def __init__(self, node: ast.Module, mod: Module) -> None:
864+ super().__init__(node, None)
865+ self.belongs: Module = mod
866+ 
867+ def get_path(self) -> str:
868+ return self.belongs.get_path()
869+ 
870+ def get_root_env(self):
871+ return self
872+ 
873+ def get_cur_command(self):
874+ return self.belongs.get_cur_command()
875+ 
876+ def get_import_codes(self):
877+ return self.import_codes
878+ 
879+ def exec(self, args: list = None, keywords: dict = None):
880+ super().exec(args, keywords)
881+ 
882+ 
883+class ClsEnv(Environment):
884+ def __init__(self, node: ast.ClassDef, parent_env) -> None:
885+ super().__init__(node, parent_env)
886+ 
887+ def find(self, name: str):
888+ """类环境下的find需要在父环境中寻找之前先在基类中寻找
889+ 对于基类中存在argparse,optparse的参数解析类时也要判断之前已经处理过的自定义的解析对象是否满足条件
890+ """
891+ if self.scanned == Environment.UNSCAN:
892+ self.exec()
893+ ok, ret = False, None
894+ if name in self.variables:
895+ ret = self.variables[name]
896+ ok = True
897+ elif name in self.imports:
898+ ret = self.imports[name]
899+ ok = True
900+ else:
901+ self.get_cls_env()
902+ if self.bases != Ellipsis and self.bases:
903+ for base in self.bases:
904+ if isinstance(base, ClsEnv):
905+ ok, ret = base.find(name)
906+ if ok:
907+ break
908+ elif isinstance(base, ArgParser) or isinstance(base, OptParser):
909+ if hasattr(base, name):
910+ ret = getattr(base, name)
911+ ok = True
912+ break
913+ if not ok and self.parent_env:
914+ ok, ret = self.parent_env.find(name)
915+ return (ok, ret)
916+ 
917+ # 获取类环境时处理一下基类bases
918+ def get_cls_env(self) -> Environment:
919+ if not hasattr(self, 'bases'):
920+ setattr(self, 'bases', ...)
921+ self.bases = [self.exec_driver(self, base)
922+ for base in self.node.bases]
923+ for i in range(len(self.bases)):
924+ try:
925+ if callable(self.bases[i]):
926+ self.bases[i] = self.bases[i]()
927+ except:
928+ log.warning("因模拟执行python代码时的输入参数未知以及所需系统环境的缺失导致的异常", log_file)
929+ return self
930+ 
931+ def get_parent_cls(self):
932+ if not self.bases and self.node.bases:
933+ self.bases = [self.exec_driver(self, base) for base in self.node.bases]
934+ return self.bases
935+ 
936+ def __str__(self) -> str:
937+ return f"Customized Class: {self.get_path()}"
938+ 
939+ 
940+class FuncEnv(Environment):
941+ """ 函数/方法的局部环境
942+ Attributes
943+ stack: 模拟了一个函数调用堆栈,对于多个函数之间的循环调用将直接退出,防止死循环
944+ """
945+ stack = []
946+ 
947+ def __init__(self, node: ast.FunctionDef, parent_env) -> None:
948+ super().__init__(node, parent_env)
949+ 
950+ def exec(self, args: list = [], keywords: dict = {}):
951+ """
952+ 需要将函数调用时传递的参数和函数定义时可接收的参数进行对应,将参数放到variables中,然后调用父类Environment中的方法进行执行
953+ """
954+ if self.scanned == Environment.UNSCAN:
955+ self.scanned = Environment.SCANNING
956+ if self in FuncEnv.stack:
957+ return
958+ FuncEnv.stack.append(self)
959+ arguments = self.node.args
960+ start = 0
961+ if len(arguments.args) > 0 and arguments.args[0].arg in ('self', 'cls'):
962+ start = 1
963+ j = 0
964+ for i in range(start, len(arguments.args)):
965+ arg = arguments.args[i].arg
966+ if arg in keywords:
967+ self.variables[arg] = keywords[arg]
968+ keywords.pop(arg)
969+ elif j < len(args):
970+ self.variables[arg] = args[j]
971+ j += 1
972+ else:
973+ k = i - len(arguments.args) + len(arguments.defaults)
974+ if k >= 0:
975+ self.variables[arg] = self.exec_driver(self, arguments.defaults[k])
976+ else:
977+ self.variables[arg] = None
978+ if arguments.vararg:
979+ arg = arguments.vararg.arg
980+ self.variables[arg] = args[j:] if j < len(args) else None
981+ for i in range(len(arguments.kwonlyargs)):
982+ arg = arguments.kwonlyargs[i].arg
983+ if arg in keywords:
984+ self.variables[arg] = keywords[arg]
985+ keywords.pop(arg)
986+ elif arguments.kw_defaults[i]:
987+ self.variables[arg] = self.exec_driver(
988+ self, arguments.kw_defaults[i])
989+ else:
990+ self.variables[arg] = None
991+ if arguments.kwarg:
992+ arg = arguments.kwarg.arg
993+ self.variables[arg] = keywords
994+ ret = super().exec(args, keywords)
995+ FuncEnv.stack.pop()
996+ return ret
997+ 
998+ def get_cls_env(self) -> Environment:
999+ return self.parent_env.get_cls_env()
1000+ 
1001+ def __str__(self) -> str:
1002+ return f"Customized Function: {self.get_path()}"
1003+ 
1004+ 
1005+class PyArgExtractor:
1006+ def __init__(self, commands: list[str], basedir: str, logfile: str):
1007+ """
1008+ 定位Python源码位置,初始化sitepackage对象,并将命令command对象传入到sitepackage中。但并非所有源码包都有存放Python包的sitepackage
1009+ """
1010+ global log_file
1011+ log_file = logfile
1012+ self.site_package = None
1013+ status, output = subprocess.getstatusoutput(f"find {basedir} -type d -name site-packages")
1014+ if status == 0 and output:
1015+ for line in output.split('\n'):
1016+ self.site_package = SitePackage(line)
1017+ break
1018+ self.commands = [Module(command, command[command.rfind('/') + 1:], self.site_package) for command in commands]
1019+ if self.site_package:
1020+ self.site_package.extend_commands(self.commands)
1021+ self.basedir = basedir
1022+ self.args = {command.name: [] for command in self.commands}
1023+ self.usages = {command.name: '' for command in self.commands}
1024+ self.nums = 0
1025+ 
1026+ def get_cmds_args(self):
1027+ """获取命令的参数信息
1028+ Returns:
1029+ content: usage帮助信息
1030+ commands: 格式化的参数
1031+ value_map: 可用的值的map,格式:'命令,需要赋值的参数':值的列表
1032+ help_map: 类型信息的map,格式:'命令,需要赋值的参数':类型
1033+ """
1034+ content, commands, value_map, help_map = '', [], {}, {}
1035+ for command in self.commands:
1036+ UniArgParser.CurCommand = command
1037+ command.boot = True
1038+ command.exec()
1039+ command.boot = False
1040+ if UniArgParser.finalParser:
1041+ num = UniArgParser.finalParser.get_num()
1042+ content = f"COMMAND: {command.name}\n{num}\n{UniArgParser.finalParser}"
1043+ self.nums += num
1044+ commands, value_map, help_map = UniArgParser.finalParser.getCmds_Vals()
1045+ UniArgParser.finalParser = None
1046+ return content, commands, value_map, help_map