模糊测试之书-十五-

模糊测试之书(十五)

原文:exploringjs.com/ts/book/index.html

译者:飞龙

协议:CC BY-NC-SA 4.0

切割单元测试

原文:www.fuzzingbook.org/html/Carver.html

到目前为止,我们总是生成系统输入,即程序通过其输入通道整体获得的数据。如果我们只对测试一小组功能感兴趣,必须通过系统进行测试可能会非常低效。本章介绍了一种称为切割的技术,它给定一个系统测试,自动提取一组单元测试,这些测试复制了系统测试期间看到的调用。关键思想是记录这样的调用,以便我们可以在以后回放它们——整体或选择性地。此外,我们还探讨了如何从切割单元测试中合成 API 语法;这意味着我们可以合成 API 测试而无需编写任何语法

先决条件

  • 切割技术利用了函数调用和变量的动态跟踪,如配置模糊测试章节中所述。

  • 使用语法测试单元在 API 模糊测试章节中介绍。

import [bookutils.setup](https://github.com/uds-se/fuzzingbook//tree/master/notebooks/shared/bookutils) 
import APIFuzzer 

概述

要使用本章提供的代码导入,请编写

>>> from fuzzingbook.Carver import <identifier> 

然后利用以下功能。

本章提供了在系统测试期间记录和回放函数调用的方法。由于单个函数调用比整个系统运行快得多,这种“切割”机制有潜力使测试运行得更快。

记录调用

CallCarver类在活动期间记录所有发生的调用。它与with子句一起使用:

>>> with CallCarver() as carver:
>>>     y = my_sqrt(2)
>>>     y = my_sqrt(4) 

执行后,called_functions()列出遇到的功能名称:

>>> carver.called_functions()
['my_sqrt', '__exit__'] 

arguments()方法列出为函数记录的参数。这是一个将功能名称映射到参数列表的列表的映射;每个参数是一个参数名和值的对。

>>> carver.arguments('my_sqrt')
[[('x', 2)], [('x', 4)]] 

复杂的参数被正确地序列化,这样它们可以很容易地恢复。

合成调用

虽然这样的记录参数已经可以转换为参数和调用,但一个更好的选择是创建一个语法来记录调用。这允许合成任意组合的参数,同时也为调用进一步定制提供了一个基础。

CallGrammarMiner类将切割执行的列表转换成一个语法。

>>> my_sqrt_miner = CallGrammarMiner(carver)
>>> my_sqrt_grammar = my_sqrt_miner.mine_call_grammar()
>>> my_sqrt_grammar
{'<start>': ['<call>'],
 '<call>': ['<my_sqrt>'],
 '<my_sqrt-x>': ['2', '4'],
 '<my_sqrt>': ['my_sqrt(<my_sqrt-x>)']} 

这个语法可以用来合成调用。

>>> fuzzer = GrammarCoverageFuzzer(my_sqrt_grammar)
>>> fuzzer.fuzz()
'my_sqrt(4)' 

这些调用可以单独执行,有效地从系统测试中提取单元测试:

>>> eval(fuzzer.fuzz())
1.414213562373095 

系统测试与单元测试

记得为语法模糊测试引入的 URL 语法吗?有了这样的语法,我们可以愉快地再次测试 Web 浏览器,检查它对任意页面请求的反应。

让我们定义一个非常简单的“网络浏览器”,它会根据 URL 下载内容。

import [urllib.parse](https://docs.python.org/3/library/urllib.parse.html) 
def webbrowser(url):
  """Download the http/https resource given by the URL"""
    import [requests](http://docs.python-requests.org/en/master/)  # Only import if needed

    r = requests.get(url)
    return r.text 

让我们在fuzzingbook.org上应用这个方法并测量时间,使用计时器类:

from Timer import Timer 
with Timer() as webbrowser_timer:
    fuzzingbook_contents = webbrowser(
        "http://www.fuzzingbook.org/html/Fuzzer.html")

print("Downloaded %d bytes in %.2f seconds" %
      (len(fuzzingbook_contents), webbrowser_timer.elapsed_time())) 
Downloaded 474839 bytes in 0.48 seconds

fuzzingbook_contents[:100] 
'\n<!-- A html document -->\n<!-- \nwith standard nbconvert css layout\nwith standard nbconvert input/out'

当然,一个完整的网络浏览器也会渲染 HTML 内容。我们可以使用这些命令(但我们不这样做,因为我们不想在这里复制整个网页):

from [IPython.display](https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html) import HTML, display
HTML(fuzzingbook_contents) 

不得不一次又一次地启动整个浏览器(或让它渲染一个网页)意味着有很多开销,尤其是如果我们只想测试其功能的一个子集。特别是,在代码更改后,我们更愿意只测试受更改影响的函数子集,而不是反复运行经过良好测试的函数。

让我们假设我们更改了处理解析给定 URL 并将其分解为各个元素(方案“http”)、网络位置("www.fuzzingbook.com")或路径("/html/Fuzzer.html")的函数——这个函数名为urlparse()

from [urllib.parse](https://docs.python.org/3/library/urllib.parse.html) import urlparse 
urlparse('https://www.fuzzingbook.com/html/Carver.html') 
ParseResult(scheme='https', netloc='www.fuzzingbook.com', path='/html/Carver.html', params='', query='', fragment='')

您可以看到 URL 的各个组成部分——方案"http")、网络位置"www.fuzzingbook.com")或路径("//html/Carver.html")都被正确地识别。其他元素(如paramsqueryfragment)为空,因为它们不是我们输入的一部分。

有趣的是,仅执行urlparse()比运行整个webbrowser()快得多。让我们测量这个因子:

runs = 1000
with Timer() as urlparse_timer:
    for i in range(runs):
        urlparse('https://www.fuzzingbook.com/html/Carver.html')

avg_urlparse_time = urlparse_timer.elapsed_time() / 1000
avg_urlparse_time 
1.4512089546769858e-06

将其与网络浏览器所需的时间进行比较

webbrowser_timer.elapsed_time() 
0.48406379198422655

时间上的差异巨大:

webbrowser_timer.elapsed_time() / avg_urlparse_time 
333558.98916153726

因此,在运行webbrowser()一次所需的时间内,我们可以执行urlparse()——数十万次——而且这还不包括浏览器渲染下载的 HTML、运行包含的脚本以及网页加载时发生的其他事情所需的时间。因此,允许我们在单元级别进行测试的策略非常有前景,因为它们可以节省大量开销。

切割单元测试

在单元级别测试方法和函数需要非常了解要测试的各个单元以及它们与其他单元的交互。因此,设置适当的基础设施并手动编写单元测试既具有挑战性,又具有回报。然而,手动编写单元测试有一个有趣的替代方案。通过记录和回放函数调用的切割技术自动将系统测试转换为单元测试:

  1. 在系统测试(给定或生成的)过程中,我们记录所有对函数的调用,包括函数读取的所有参数和其他变量。

  2. 从这些中,我们合成一个自包含的单元测试,该测试重建了包含所有参数的函数调用。

  3. 这个单元测试可以随时以高效率执行(回放)。

在本章剩余部分,让我们探索这些步骤。

记录调用

我们的首要挑战是记录函数调用及其参数。(为了简单起见,我们限制自己只记录参数,忽略函数读取的任何全局变量或其他非参数。)为了记录调用和参数,我们使用我们为覆盖率引入的机制:通过设置跟踪函数,我们跟踪所有进入单个函数的调用,同时也保存它们的参数。就像 Coverage 对象一样,我们希望使用 Carver 对象能够与 with 语句一起使用,这样我们就可以跟踪特定的代码块:

with Carver() as carver:
    function_to_be_traced()
c = carver.calls() 

初始定义支持这种结构:

\todo{从 动态不变性 获取跟踪器}

import [sys](https://docs.python.org/3/library/sys.html) 
class Carver:
    def __init__(self, log=False):
        self._log = log
        self.reset()

    def reset(self):
        self._calls = {}

    # Start of `with` block
    def __enter__(self):
        self.original_trace_function = sys.gettrace()
        sys.settrace(self.traceit)
        return self

    # End of `with` block
    def __exit__(self, exc_type, exc_value, tb):
        sys.settrace(self.original_trace_function) 

实际工作发生在 traceit() 方法中,它记录了所有调用到 _calls 属性中。首先,我们定义两个辅助函数:

import [inspect](https://docs.python.org/3/library/inspect.html) 
def get_qualified_name(code):
  """Return the fully qualified name of the current function"""
    name = code.co_name
    module = inspect.getmodule(code)
    if module is not None:
        name = module.__name__ + "." + name
    return name 
def get_arguments(frame):
  """Return call arguments in the given frame"""
    # When called, all arguments are local variables
    local_variables = frame.f_locals.copy()
    arguments = [(var, frame.f_locals[var])
                 for var in local_variables]
    arguments.reverse()  # Want same order as call
    return arguments 
class CallCarver(Carver):
    def add_call(self, function_name, arguments):
  """Add given call to list of calls"""
        if function_name not in self._calls:
            self._calls[function_name] = []
        self._calls[function_name].append(arguments)

    # Tracking function: Record all calls and all args
    def traceit(self, frame, event, arg):
        if event != "call":
            return None

        code = frame.f_code
        function_name = code.co_name
        qualified_name = get_qualified_name(code)
        arguments = get_arguments(frame)

        self.add_call(function_name, arguments)
        if qualified_name != function_name:
            self.add_call(qualified_name, arguments)

        if self._log:
            print(simple_call_string(function_name, arguments))

        return None 

最后,我们需要一些便利函数来访问调用:

class CallCarver(CallCarver):
    def calls(self):
  """Return a dictionary of all calls traced."""
        return self._calls

    def arguments(self, function_name):
  """Return a list of all arguments of the given function
 as (VAR, VALUE) pairs.
 Raises an exception if the function was not traced."""
        return self._calls[function_name]

    def called_functions(self, qualified=False):
  """Return all functions called."""
        if qualified:
            return [function_name for function_name in self._calls.keys()
                    if function_name.find('.') >= 0]
        else:
            return [function_name for function_name in self._calls.keys()
                    if function_name.find('.') < 0] 

记录 my_sqrt()

让我们尝试我们的新 Carver 类——首先是一个非常简单的函数:

from Intro_Testing import my_sqrt 
with CallCarver() as sqrt_carver:
    my_sqrt(2)
    my_sqrt(4) 

我们可以检索所有看到的调用...

sqrt_carver.calls() 
{'my_sqrt': [[('x', 2)], [('x', 4)]],
 'Intro_Testing.my_sqrt': [[('x', 2)], [('x', 4)]],
 '__exit__': [[('tb', None),
   ('exc_value', None),
   ('exc_type', None),
   ('self', <__main__.CallCarver at 0x164d98e20>)]]}

sqrt_carver.called_functions() 
['my_sqrt', '__exit__']

...以及特定函数的参数:

sqrt_carver.arguments("my_sqrt") 
[[('x', 2)], [('x', 4)]]

我们定义了一个便利函数,以便更好地打印这些列表:

def simple_call_string(function_name, argument_list):
  """Return function_name(arg[0], arg[1], ...) as a string"""
    return function_name + "(" + \
        ", ".join([var + "=" + repr(value)
                   for (var, value) in argument_list]) + ")" 
for function_name in sqrt_carver.called_functions():
    for argument_list in sqrt_carver.arguments(function_name):
        print(simple_call_string(function_name, argument_list)) 
my_sqrt(x=2)
my_sqrt(x=4)
__exit__(tb=None, exc_value=None, exc_type=None, self=<__main__.CallCarver object at 0x164d98e20>)

这是一个可以直接使用的语法来再次调用 my_sqrt()

eval("my_sqrt(x=2)") 
1.414213562373095

雕刻 urlparse()

如果我们将此应用于 webbrowser() 会发生什么?

with CallCarver() as webbrowser_carver:
    webbrowser("https://www.fuzzingbook.org") 

我们看到从网络检索 URL 需要相当多的功能:

function_list = webbrowser_carver.called_functions(qualified=True)
len(function_list) 
361

print(function_list[:50]) 
['requests.api.get', 'requests.api.request', 'requests.sessions.__init__', 'requests.utils.default_headers', 'requests.utils.default_user_agent', 'requests.structures.__init__', 'collections.abc.update', 'abc.__instancecheck__', 'requests.structures.__setitem__', 'requests.hooks.default_hooks', 'requests.hooks.<dictcomp>', 'requests.cookies.cookiejar_from_dict', 'http.cookiejar.__init__', 'threading.RLock', 'http.cookiejar.__iter__', 'requests.cookies.<listcomp>', 'http.cookiejar.deepvalues', 'http.cookiejar.vals_sorted_by_key', 'requests.adapters.__init__', 'urllib3.util.retry.__init__', 'urllib3.util.retry.<listcomp>', 'requests.adapters.init_poolmanager', 'urllib3.poolmanager.__init__', 'urllib3.request.__init__', 'urllib3._collections.__init__', 'requests.sessions.mount', 'requests.sessions.<listcomp>', 'requests.sessions.__enter__', 'requests.sessions.request', 'requests.models.__init__', 'requests.sessions.prepare_request', 'requests.cookies.merge_cookies', 'requests.cookies.update', 'requests.utils.get_netrc_auth', 'collections.abc.get', 'os.__getitem__', 'os.encode', 'requests.utils.<genexpr>', 'posixpath.expanduser', 'posixpath._get_sep', 'collections.abc.__contains__', 'os.decode', 'genericpath.exists', 'urllib.parse.urlparse', 'urllib.parse._coerce_args', 'urllib.parse.urlsplit', 'urllib.parse._splitnetloc', 'urllib.parse._checknetloc', 'urllib.parse._noop', 'netrc.__init__']

在许多其他函数中,我们还有一个对 urlparse() 的调用:

urlparse_argument_list = webbrowser_carver.arguments("urllib.parse.urlparse")
urlparse_argument_list 
[[('allow_fragments', True),
  ('scheme', ''),
  ('url', 'https://www.fuzzingbook.org')],
 [('allow_fragments', True),
  ('scheme', ''),
  ('url', 'https://www.fuzzingbook.org/')],
 [('allow_fragments', True),
  ('scheme', ''),
  ('url', 'https://www.fuzzingbook.org/')],
 [('allow_fragments', True),
  ('scheme', ''),
  ('url', 'https://www.fuzzingbook.org/')],
 [('allow_fragments', True),
  ('scheme', ''),
  ('url', 'https://www.fuzzingbook.org/')],
 [('allow_fragments', True),
  ('scheme', ''),
  ('url', 'https://www.fuzzingbook.org/')],
 [('allow_fragments', True),
  ('scheme', ''),
  ('url', 'https://www.fuzzingbook.org/')],
 [('allow_fragments', True),
  ('scheme', ''),
  ('url', 'https://www.fuzzingbook.org/')],
 [('allow_fragments', True),
  ('scheme', ''),
  ('url', 'https://www.fuzzingbook.org/')],
 [('allow_fragments', True),
  ('scheme', ''),
  ('url', 'https://www.fuzzingbook.org/')],
 [('allow_fragments', True),
  ('scheme', ''),
  ('url', 'https://www.fuzzingbook.org/')]]

再次,我们可以将其转换为格式良好的调用:

urlparse_call = simple_call_string("urlparse", urlparse_argument_list[0])
urlparse_call 
"urlparse(allow_fragments=True, scheme='', url='https://www.fuzzingbook.org')"

再次,我们可以重新执行这个调用:

eval(urlparse_call) 
ParseResult(scheme='https', netloc='www.fuzzingbook.org', path='', params='', query='', fragment='')

现在,我们已经成功地从 webbrowser() 执行中雕刻出了对 urlparse() 的调用。

回放调用

完整且普遍地回放调用很棘手,因为有几个挑战需要解决。这包括:

  1. 我们需要能够 访问 单个函数。如果我们通过名称访问一个函数,该名称必须在作用域内。如果名称不可见(例如,因为它是一个模块内的名称),我们必须使其可见。

  2. 任何在参数外部访问的 资源 必须被记录并重建以供回放。如果变量引用外部资源(如文件或网络资源),这可能很困难。

  3. 复杂对象 也必须重建。

这些约束使得在函数与它的环境有大量交互时,雕刻变得困难甚至不可能。为了说明这些问题,考虑在 webbrowser() 中调用的 email.parser.parse() 方法:

email_parse_argument_list = webbrowser_carver.arguments("email.parser.parse") 

对该方法的调用看起来像这样:

email_parse_call = simple_call_string(
    "email.parser.Parser.parse",
    email_parse_argument_list[0])
email_parse_call 
'email.parser.Parser.parse(headersonly=False, fp=<_io.StringIO object at 0x165160040>, self=<email.parser.Parser object at 0x164d9a3b0>)'

我们看到 email.parser.Parser.parse()email.parser.Parser 对象(self)的一部分,并且它接收一个 StringIO 对象(fp)。这两个都是非原始值。我们如何可能重建它们?

对象序列化

复杂对象问题的答案在于创建一个持久的表示,可以在以后的某个时间点重建。这个过程被称为序列化;在 Python 中,它也被称为picklepickle模块提供了创建对象序列化表示的方法。让我们将此应用于我们刚刚找到的email.parser.Parser对象:

import [pickle](https://docs.python.org/3/library/pickle.html) 
email_parse_argument_list 
[[('headersonly', False),
  ('fp', <_io.StringIO at 0x165160040>),
  ('self', <email.parser.Parser at 0x164d9a3b0>)]]

parser_object = email_parse_argument_list[0][2][1]
parser_object 
<email.parser.Parser at 0x164d9a3b0>

pickled = pickle.dumps(parser_object)
pickled 
b'\x80\x04\x95w\x00\x00\x00\x00\x00\x00\x00\x8c\x0cemail.parser\x94\x8c\x06Parser\x94\x93\x94)\x81\x94}\x94(\x8c\x06_class\x94\x8c\x0bhttp.client\x94\x8c\x0bHTTPMessage\x94\x93\x94\x8c\x06policy\x94\x8c\x11email._policybase\x94\x8c\x08Compat32\x94\x93\x94)\x81\x94ub.'

从表示序列化的email.parser.Parser对象的字符串中,我们可以在任何时间重新创建 Parser 对象:

unpickled_parser_object = pickle.loads(pickled)
unpickled_parser_object 
<email.parser.Parser at 0x1653cc430>

序列化机制使我们能够为所有作为参数传递的对象(假设它们可以被 pickle,即)生成表示。现在我们可以扩展simple_call_string()函数,使其自动序列化对象。此外,我们将其设置为,如果第一个参数名为self(即,它是一个类方法),我们将其作为self对象的方法。

def call_value(value):
    value_as_string = repr(value)
    if value_as_string.find('<') >= 0:
        # Complex object
        value_as_string = "pickle.loads(" + repr(pickle.dumps(value)) + ")"
    return value_as_string 
def call_string(function_name, argument_list):
  """Return function_name(arg[0], arg[1], ...) as a string, pickling complex objects"""
    if len(argument_list) > 0:
        (first_var, first_value) = argument_list[0]
        if first_var == "self":
            # Make this a method call
            method_name = function_name.split(".")[-1]
            function_name = call_value(first_value) + "." + method_name
            argument_list = argument_list[1:]

    return function_name + "(" + \
        ", ".join([var + "=" + call_value(value)
                   for (var, value) in argument_list]) + ")" 

让我们应用扩展的call_string()方法来创建对email.parser.parse()的调用,包括序列化的对象:

call = call_string("email.parser.Parser.parse", email_parse_argument_list[0])
print(call) 
email.parser.Parser.parse(headersonly=False, fp=pickle.loads(b'\x80\x04\x95\xc4\x02\x00\x00\x00\x00\x00\x00\x8c\x03_io\x94\x8c\x08StringIO\x94\x93\x94)\x81\x94(X\x9b\x02\x00\x00Connection: keep-alive\r\nContent-Length: 51336\r\nServer: GitHub.com\r\nContent-Type: text/html; charset=utf-8\r\nLast-Modified: Sat, 09 Nov 2024 16:09:36 GMT\r\nAccess-Control-Allow-Origin: *\r\nETag: W/"672f8940-4620a"\r\nexpires: Sat, 09 Nov 2024 17:02:19 GMT\r\nCache-Control: max-age=600\r\nContent-Encoding: gzip\r\nx-proxy-cache: MISS\r\nX-GitHub-Request-Id: 4FED:361A70:4094950:424934E:672F9343\r\nAccept-Ranges: bytes\r\nAge: 0\r\nDate: Sat, 09 Nov 2024 16:52:20 GMT\r\nVia: 1.1 varnish\r\nX-Served-By: cache-fra-eddf8230152-FRA\r\nX-Cache: MISS\r\nX-Cache-Hits: 0\r\nX-Timer: S1731171140.907105,VS0,VE105\r\nVary: Accept-Encoding\r\nX-Fastly-Request-ID: ca9f40b3c3e14ac63fadb8002a5b3b2d5be59d1b\r\n\r\n\x94\x8c\x01\n\x94M\x9b\x02Nt\x94b.'), self=pickle.loads(b'\x80\x04\x95w\x00\x00\x00\x00\x00\x00\x00\x8c\x0cemail.parser\x94\x8c\x06Parser\x94\x93\x94)\x81\x94}\x94(\x8c\x06_class\x94\x8c\x0bhttp.client\x94\x8c\x0bHTTPMessage\x94\x93\x94\x8c\x06policy\x94\x8c\x11email._policybase\x94\x8c\x08Compat32\x94\x93\x94)\x81\x94ub.'))

使用涉及序列化对象的这个调用,我们现在可以重新运行原始调用并获得有效结果:

import [email](https://docs.python.org/3/library/email.html) 
eval(call) 
<http.client.HTTPMessage at 0x1653cd720>

所有调用

到目前为止,我们只看到了一次webbrowser()的调用。在webbrowser()内部,我们实际上可以雕刻和重放多少次调用?让我们尝试一下并计算数字。

import [traceback](https://docs.python.org/3/library/traceback.html) 
import [enum](https://docs.python.org/3/library/enum.html)
import [socket](https://docs.python.org/3/library/socket.html) 
all_functions = set(webbrowser_carver.called_functions(qualified=True))
call_success = set()
run_success = set() 
exceptions_seen = set()

for function_name in webbrowser_carver.called_functions(qualified=True):
    for argument_list in webbrowser_carver.arguments(function_name):
        try:
            call = call_string(function_name, argument_list)
            call_success.add(function_name)

            result = eval(call)
            run_success.add(function_name)

        except Exception as exc:
            exceptions_seen.add(repr(exc))
            # print("->", call, file=sys.stderr)
            # traceback.print_exc()
            # print("", file=sys.stderr)
            continue 
print("%d/%d calls (%.2f%%) successfully created and %d/%d calls (%.2f%%) successfully ran" % (
    len(call_success), len(all_functions), len(
        call_success) * 100 / len(all_functions),
    len(run_success), len(all_functions), len(run_success) * 100 / len(all_functions))) 
240/361 calls (66.48%) successfully created and 49/361 calls (13.57%) successfully ran

大约四分之一的调用成功。让我们看看我们得到的一些错误信息:

for i in range(10):
    print(list(exceptions_seen)[i]) 
NameError("name 'logging' is not defined")
TypeError("cannot pickle 'SSLSocket' object")
AttributeError("module 'enum' has no attribute '__call__'")
AttributeError("'NoneType' object has no attribute 'readline'")
NameError("name 'codecs' is not defined")
SyntaxError('invalid syntax', ('<string>', 1, 17, "requests.models.<genexpr>(.0=pickle.loads(b'\\x80\\x04\\x95\\x1b\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x08builtins\\x94\\x8c\\x04iter\\x94\\x93\\x94]\\x94\\x85\\x94R\\x94.'))", 1, 18))
SyntaxError('invalid syntax', ('<string>', 1, 18, "urllib3.util.url.<genexpr>(.0=pickle.loads(b'\\x80\\x04\\x95\\x1c\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x08builtins\\x94\\x8c\\x04iter\\x94\\x93\\x94\\x8c\\x00\\x94\\x85\\x94R\\x94.'))", 1, 19))
AttributeError("module 'email.parser' has no attribute 'parsestr'")
SyntaxError('invalid syntax', ('<string>', 1, 16, "requests.utils.<genexpr>(f='.netrc', .0=pickle.loads(b'\\x80\\x04\\x950\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x08builtins\\x94\\x8c\\x04iter\\x94\\x93\\x94\\x8c\\x06.netrc\\x94\\x8c\\x06_netrc\\x94\\x86\\x94\\x85\\x94R\\x94K\\x01b.'))", 1, 17))
AttributeError("module 'email.message' has no attribute 'get'")

我们看到:

  • 大多数调用都可以转换为调用字符串。如果不是这种情况,这主要是因为传递了非序列化对象。

  • 大约四分之一的调用可以执行。失败运行的错误信息各不相同;最常见的是调用了一个不在作用域内的内部名称。

我们的雕刻机制应该谨慎对待:我们仍然没有涵盖访问外部变量和值(例如全局变量)的情况,序列化机制无法重新创建外部资源。尽管如此,如果感兴趣的函数属于那些可以雕刻和重放的那些函数,我们可以非常有效地使用它们的原始参数重新运行其调用。

从雕刻调用中挖掘 API 语法

到目前为止,我们使用雕刻调用来重放最初遇到的完全相同的调用。然而,我们也可以变异雕刻调用,以有效地使用先前记录的参数模糊 API。

一般思路如下:

  1. 首先,我们记录程序给定执行中特定函数的所有调用。

  2. 第二,我们创建一个语法,它包含所有这些调用,为每个参数提供单独的规则,并为每个找到的值提供替代方案;这允许我们产生任意重新组合这些参数的调用。

让我们在以下章节中探索这些步骤。

从调用到语法

让我们从例子开始。power(x, y) 函数返回 \(x^y\);它只是 math.pow() 函数的包装器。(由于 power() 在 Python 中定义,我们可以跟踪它——与在 C 中实现的 math.pow() 相比。)

import [math](https://docs.python.org/3/library/math.html) 
def power(x, y):
    return math.pow(x, y) 

让我们调用 power() 并记录其参数:

with CallCarver() as power_carver:
    z = power(1, 2)
    z = power(3, 4) 
power_carver.arguments("power") 
[[('y', 2), ('x', 1)], [('y', 4), ('x', 3)]]

从这个记录的参数列表中,我们现在可以创建一个用于 power() 调用的语法,其中 xy 扩展为看到的值:

from Grammars import START_SYMBOL, is_valid_grammar, new_symbol
from Grammars import extend_grammar, Grammar 
POWER_GRAMMAR: Grammar = {
    "<start>": ["power(<x>, <y>)"],
    "<x>": ["1", "3"],
    "<y>": ["2", "4"]
}

assert is_valid_grammar(POWER_GRAMMAR) 

当使用此语法进行模糊测试时,我们得到 xy 的任意组合;目标是确保所有值至少被测试一次:

from GrammarCoverageFuzzer import GrammarCoverageFuzzer 
power_fuzzer = GrammarCoverageFuzzer(POWER_GRAMMAR)
[power_fuzzer.fuzz() for i in range(5)] 
['power(1, 2)', 'power(3, 4)', 'power(1, 2)', 'power(3, 4)', 'power(3, 4)']

我们需要一种方法,将 power_carver 中看到的参数自动转换为 POWER_GRAMMAR 中看到的语法。这就是我们在下一节中定义的内容。

调用语法挖掘器

我们引入了一个名为 CallGrammarMiner 的类,它接受一个 Carver 对象,并自动从看到的调用中生成语法。为了初始化,我们传递 carver 对象:

class CallGrammarMiner:
    def __init__(self, carver, log=False):
        self.carver = carver
        self.log = log 

初始语法

初始语法产生一个单一的调用。可能的 <call> 扩展将在以后构建:

import [copy](https://docs.python.org/3/library/copy.html) 
class CallGrammarMiner(CallGrammarMiner):
    CALL_SYMBOL = "<call>"

    def initial_grammar(self):
        return extend_grammar(
            {START_SYMBOL: [self.CALL_SYMBOL],
                self.CALL_SYMBOL: []
             }) 
m = CallGrammarMiner(power_carver)
initial_grammar = m.initial_grammar()
initial_grammar 
{'<start>': ['<call>'], '<call>': []}

参数语法

让我们先从一个参数列表中创建一个语法。mine_arguments_grammar() 方法为 carving 过程中看到的参数创建一个语法,例如这些:

arguments = power_carver.arguments("power")
arguments 
[[('y', 2), ('x', 1)], [('y', 4), ('x', 3)]]

mine_arguments_grammar() 方法遍历看到的变量,并为每个变量名创建一个映射 variables,将变量名映射到一组看到的值(作为字符串,通过 call_value())。在第二步中,它然后为每个变量名创建一个规则,扩展为看到的值。

class CallGrammarMiner(CallGrammarMiner):
    def var_symbol(self, function_name, var, grammar):
        return new_symbol(grammar, "<" + function_name + "-" + var + ">")

    def mine_arguments_grammar(self, function_name, arguments, grammar):
        var_grammar = {}

        variables = {}
        for argument_list in arguments:
            for (var, value) in argument_list:
                value_string = call_value(value)
                if self.log:
                    print(var, "=", value_string)

                if value_string.find("<") >= 0:
                    var_grammar["<langle>"] = ["<"]
                    value_string = value_string.replace("<", "<langle>")

                if var not in variables:
                    variables[var] = set()
                variables[var].add(value_string)

        var_symbols = []
        for var in variables:
            var_symbol = self.var_symbol(function_name, var, grammar)
            var_symbols.append(var_symbol)
            var_grammar[var_symbol] = list(variables[var])

        return var_grammar, var_symbols 
m = CallGrammarMiner(power_carver)
var_grammar, var_symbols = m.mine_arguments_grammar(
    "power", arguments, initial_grammar) 
var_grammar 
{'<power-y>': ['2', '4'], '<power-x>': ['3', '1']}

额外返回的 var_symbols 是调用中参数符号的列表:

var_symbols 
['<power-y>', '<power-x>']

调用语法

要获取单个函数的语法(mine_function_grammar()),我们向函数添加一个调用:

class CallGrammarMiner(CallGrammarMiner):
    def function_symbol(self, function_name, grammar):
        return new_symbol(grammar, "<" + function_name + ">")

    def mine_function_grammar(self, function_name, grammar):
        arguments = self.carver.arguments(function_name)

        if self.log:
            print(function_name, arguments)

        var_grammar, var_symbols = self.mine_arguments_grammar(
            function_name, arguments, grammar)

        function_grammar = var_grammar
        function_symbol = self.function_symbol(function_name, grammar)

        if len(var_symbols) > 0 and var_symbols[0].find("-self") >= 0:
            # Method call
            function_grammar[function_symbol] = [
                var_symbols[0] + "." + function_name + "(" + ", ".join(var_symbols[1:]) + ")"]
        else:
            function_grammar[function_symbol] = [
                function_name + "(" + ", ".join(var_symbols) + ")"]

        if self.log:
            print(function_symbol, "::=", function_grammar[function_symbol])

        return function_grammar, function_symbol 
m = CallGrammarMiner(power_carver)
function_grammar, function_symbol = m.mine_function_grammar(
    "power", initial_grammar)
function_grammar 
{'<power-y>': ['2', '4'],
 '<power-x>': ['3', '1'],
 '<power>': ['power(<power-y>, <power-x>)']}

额外返回的 function_symbol 包含刚刚添加的函数调用的名称:

function_symbol 
'<power>'

所有调用的语法

现在我们重复上述步骤,以所有在 carving 过程中看到的函数调用。为此,我们只需遍历所有看到的函数调用:

power_carver.called_functions() 
['power', '__exit__']

class CallGrammarMiner(CallGrammarMiner):
    def mine_call_grammar(self, function_list=None, qualified=False):
        grammar = self.initial_grammar()
        fn_list = function_list
        if function_list is None:
            fn_list = self.carver.called_functions(qualified=qualified)

        for function_name in fn_list:
            if function_list is None and (function_name.startswith("_") or function_name.startswith("<")):
                continue  # Internal function

            # Ignore errors with mined functions
            try:
                function_grammar, function_symbol = self.mine_function_grammar(
                    function_name, grammar)
            except:
                if function_list is not None:
                    raise

            if function_symbol not in grammar[self.CALL_SYMBOL]:
                grammar[self.CALL_SYMBOL].append(function_symbol)
            grammar.update(function_grammar)

        assert is_valid_grammar(grammar)
        return grammar 

mine_call_grammar() 方法是客户端可以且应该使用的方法——首先用于挖掘...

m = CallGrammarMiner(power_carver)
power_grammar = m.mine_call_grammar()
power_grammar 
{'<start>': ['<call>'],
 '<call>': ['<power>'],
 '<power-y>': ['2', '4'],
 '<power-x>': ['3', '1'],
 '<power>': ['power(<power-y>, <power-x>)']}

...然后进行模糊测试:

power_fuzzer = GrammarCoverageFuzzer(power_grammar)
[power_fuzzer.fuzz() for i in range(5)] 
['power(4, 3)', 'power(2, 1)', 'power(4, 3)', 'power(4, 3)', 'power(2, 3)']

通过这种方式,我们已经成功地从一个记录的执行中提取了一个语法;与“简单”的 carving 相比,我们的语法允许我们 重新组合 参数,从而在 API 层面上进行模糊测试。

模糊测试 Web 函数

现在我们将我们的语法挖掘器应用于更大的 API——我们在 carving 过程中已经遇到的 urlparse() 函数。

with CallCarver() as webbrowser_carver:
    webbrowser("https://www.fuzzingbook.org") 

我们可以从遇到的调用中挖掘一个语法:

m = CallGrammarMiner(webbrowser_carver)
webbrowser_grammar = m.mine_call_grammar() 

这是一个相当大的语法:

call_list = webbrowser_grammar['<call>']
len(call_list) 
136

print(call_list[:20]) 
['<webbrowser>', '<default_headers>', '<default_user_agent>', '<update>', '<default_hooks>', '<cookiejar_from_dict>', '<RLock>', '<deepvalues>', '<vals_sorted_by_key>', '<init_poolmanager>', '<mount>', '<prepare_request>', '<merge_cookies>', '<get_netrc_auth>', '<encode>', '<expanduser>', '<decode>', '<exists>', '<urlparse>', '<urlsplit>']

这是 urlparse() 函数的规则:

webbrowser_grammar["<urlparse>"] 
['urlparse(<urlparse-allow_fragments>, <urlparse-scheme>, <urlparse-url>)']

这里是参数。

webbrowser_grammar["<urlparse-url>"] 
["'https://www.fuzzingbook.org'", "'https://www.fuzzingbook.org/'"]

如果我们现在对这些规则应用模糊器,我们将系统地覆盖所有看到的参数变体,包括当然在 carving 过程中没有看到的组合。再次强调,我们在这里在 API 层面上进行模糊测试。

urlparse_fuzzer = GrammarCoverageFuzzer(
    webbrowser_grammar, start_symbol="<urlparse>")
for i in range(5):
    print(urlparse_fuzzer.fuzz()) 
urlparse(True, '', 'https://www.fuzzingbook.org')
urlparse(True, '', 'https://www.fuzzingbook.org/')
urlparse(True, '', 'https://www.fuzzingbook.org')
urlparse(True, '', 'https://www.fuzzingbook.org')
urlparse(True, '', 'https://www.fuzzingbook.org')

正如 carving 所看到的,在 API 级别运行测试比执行系统测试快得多。因此,这需要方法级别的模糊测试手段:

from [urllib.parse](https://docs.python.org/3/library/urllib.parse.html) import urlsplit 
from Timer import Timer 
with Timer() as urlsplit_timer:
    urlsplit('http://www.fuzzingbook.org/', 'http', True)
urlsplit_timer.elapsed_time() 
1.2375006917864084e-05

with Timer() as webbrowser_timer:
    webbrowser("http://www.fuzzingbook.org")
webbrowser_timer.elapsed_time() 
0.31702329200925305

webbrowser_timer.elapsed_time() / urlsplit_timer.elapsed_time() 
25618.029477754102

但另一方面,在 carving 过程中遇到的问题也适用,特别是需要重新创建原始函数环境的要求。如果我们还更改或重新组合参数,我们还会面临 违反隐含先决条件 的额外风险——即调用一个从未为这些参数设计过的函数。这种由于调用错误而不是实现错误而产生的 误报 必须被识别(通常是手动)并排除(例如,通过更改或限制语法)。然而,在 API 级别的巨大速度提升可能很好地证明这种额外投资的合理性。

经验教训

  • Carving 允许在系统测试期间记录的功能调用进行有效的回放。

  • 函数调用可以比系统调用快 几个数量级

  • 序列化 允许创建复杂对象的持久表示。

  • 与其环境高度交互或访问外部资源的函数难以进行 carving。

  • 从 carved 调用中,可以生成任意组合 carved 参数的 API 语法。

下一步

在下一章中,我们将讨论 如何减少导致失败的输入。

背景

Carving 被 Elbaum 等人发明 [Elbaum et al, 2006],最初是为 Java 实现的。在本章中,我们遵循了他们的一些设计选择(包括仅记录和序列化方法参数)。

Carving 和 API 级别的模糊测试的组合在 [Kampmann et al, 2018] 中进行了描述。

练习

练习 1:用于回归测试的 Carving

到目前为止,在 carving 过程中,我们只关注了重现 调用,但并未检查这些调用的 结果。这对于 回归测试 非常重要——即检查代码的更改是否不会妨碍现有功能。我们可以通过记录不仅 调用,还包括 返回值 来实现这一点——然后稍后比较相同的调用是否产生相同的结果。这可能在所有情况下都不适用;依赖于时间、随机性或其他外部因素的价值可能不同。然而,对于抽象这些细节的功能,检查没有任何变化是测试的重要部分。

我们的目标是设计一个 ResultCarver 类,它通过记录调用和返回值来扩展 CallCarver

在第一步中,创建一个 traceit() 方法,通过扩展 traceit() 方法来跟踪返回值。traceit() 事件类型是 "return"arg 参数是返回值。以下是一个仅打印返回值的原型:

使用笔记本(Use the notebook)来练习题目并查看解决方案。

class ResultCarver(CallCarver):
    def traceit(self, frame, event, arg):
        if event == "return":
            if self._log:
                print("Result:", arg)

        super().traceit(frame, event, arg)
        # Need to return traceit function such that it is invoked for return
        # events
        return self.traceit 
with ResultCarver(log=True) as result_carver:
    my_sqrt(2) 
my_sqrt(x=2)
Result: 1.414213562373095
__exit__(tb=None, exc_value=None, exc_type=None, self=<__main__.ResultCarver object at 0x1653ccf10>)

第一部分:存储函数结果

扩展上述代码,以便以将结果与当前返回的函数(或方法)关联的方式存储。为此,您需要跟踪当前调用的函数的 调用栈

使用笔记本(Use the notebook)来练习题目并查看解决方案。

第二部分:访问结果

为它提供一个 result() 方法,该方法返回特定函数名称和结果的记录值:

class ResultCarver(CallCarver):
    def result(self, function_name, argument):
  """Returns the result recorded for function_name(argument""" 

使用笔记本(Use the notebook)来练习题目并查看解决方案。

第三部分:生成断言

对于在 webbrowser() 执行期间调用的函数,创建一组 断言 来检查返回的结果是否仍然相同。为此测试 urllib.parse.urlparse()

使用笔记本(Use the notebook)来练习题目并查看解决方案。

练习 2:抽象参数

当从执行中挖掘 API 语法时,设置一个抽象方案以扩大测试期间使用的参数范围。如果一个参数的所有值都符合某种类型 T,则将其抽象为 <T>。例如,如果已经看到了对 foo(1)foo(2)foo(3) 的调用,则语法应将其调用抽象为 foo(<int>),其中 <int> 被适当地定义。

对多种常见类型执行此操作:整数、正数、浮点数、主机名、URL、电子邮件地址等。

使用笔记本(Use the notebook)来练习题目并查看解决方案。

Creative Commons License 本项目的内容受 Creative Commons Attribution-NonCommercial-ShareAlike 4.0 国际许可协议 的许可。作为内容一部分的源代码,以及用于格式化和显示该内容的源代码,受 MIT 许可协议 的许可。 最后更改:2023-11-11 18:18:05+01:00 • 引用 • 印记

如何引用本作品

Andreas Zeller, Rahul Gopinath, Marcel Böhme, Gordon Fraser 和 Christian Holler: "切割单元测试". 在 Andreas Zeller, Rahul Gopinath, Marcel Böhme, Gordon Fraser 和 Christian Holler 的 "模糊测试书籍", www.fuzzingbook.org/html/Carver.html. 获取时间:2023-11-11 18:18:05+01:00.

@incollection{fuzzingbook2023:Carver,
    author = {Andreas Zeller and Rahul Gopinath and Marcel B{\"o}hme and Gordon Fraser and Christian Holler},
    booktitle = {The Fuzzing Book},
    title = {Carving Unit Tests},
    year = {2023},
    publisher = {CISPA Helmholtz Center for Information Security},
    howpublished = {\url{https://www.fuzzingbook.org/html/Carver.html}},
    note = {Retrieved 2023-11-11 18:18:05+01:00},
    url = {https://www.fuzzingbook.org/html/Carver.html},
    urldate = {2023-11-11 18:18:05+01:00}
}

测试编译器

原文:www.fuzzingbook.org/html/PythonFuzzer.html

在本章中,我们将利用 grammars and grammar-based testing 系统地生成 程序代码 - 例如,测试编译器或解释器。不出所料,我们使用 PythonPython 解释器 作为我们的领域。

我们选择 Python 不仅是因为本书的其余部分也是基于 Python。最重要的是,Python 带来了许多我们可以利用的内置基础设施,特别是

  • parsers,它将 Python 代码转换为抽象语法树(AST)表示。

  • unparsers,它接受一个 AST 并将其转换回 Python 代码。

这使我们能够利用在 AST 上操作的语法,而不是具体语法,从而大大降低复杂性。

from [bookutils](https://github.com/uds-se/fuzzingbook//tree/master/notebooks/shared/bookutils) import YouTubeVideo
YouTubeVideo('Nr1xbKj_WRQ') 

先决条件

  • 您必须阅读关于 Fuzzing with Grammars 的章节,以了解语法和基于语法的测试是如何工作的。

概述

要使用本章提供的代码[Importing.html],请编写

>>> from fuzzingbook.PythonFuzzer import <identifier> 

然后利用以下功能。

本章提供了一个 PythonFuzzer 类,允许生成任意的 Python 代码元素:

>>> fuzzer = PythonFuzzer()
>>> print(fuzzer.fuzz())
def R():
    break 

默认情况下,PythonFuzzer 生成一个 函数定义 - 即,如上所示的一组语句。您可以通过传递 start_symbol 参数来指定您想要的 Python 元素:

>>> fuzzer = PythonFuzzer('<While>')
>>> print(fuzzer.fuzz())
while {set()[set():set():set()]}:
    C = set()
    D @= set()
    break
else:
    return 

这里是一个所有可能的起始符号列表。它们的名称反映了 Python ast 模块文档中的非终结符。

>>> sorted(list(PYTHON_AST_GRAMMAR.keys()))
['<Assert>',
 '<Assign>',
 '<Attribute>',
 '<AugAssign>',
 '<BinOp>',
 '<BoolOp>',
 '<Break>',
 '<Call>',
 '<Compare>',
 '<Constant>',
 '<Continue>',
 '<Delete>',
 '<Dict>',
 '<EmptySet>',
 '<Expr>',
 '<For>',
 '<FunctionDef>',
 '<If>',
 '<List>',
 '<Module>',
 '<Name>',
 '<Pass>',
 '<Return>',
 '<Set>',
 '<Slice>',
 '<Starred>',
 '<Subscript>',
 '<Tuple>',
 '<UnaryOp>',
 '<While>',
 '<With>',
 '<arg>',
 '<arg_list>',
 '<args>',
 '<args_param>',
 '<arguments>',
 '<bool>',
 '<boolop>',
 '<cmpop>',
 '<cmpop_list>',
 '<cmpops>',
 '<decorator_list_param>',
 '<defaults_param>',
 '<digit>',
 '<digits>',
 '<expr>',
 '<expr_list>',
 '<exprs>',
 '<float>',
 '<func>',
 '<id>',
 '<id_continue>',
 '<id_start>',
 '<identifier>',
 '<integer>',
 '<keyword>',
 '<keyword_list>',
 '<keywords>',
 '<keywords_param>',
 '<kw_defaults_param>',
 '<kwarg>',
 '<kwonlyargs_param>',
 '<lhs_Attribute>',
 '<lhs_List>',
 '<lhs_Name>',
 '<lhs_Starred>',
 '<lhs_Subscript>',
 '<lhs_Tuple>',
 '<lhs_expr>',
 '<lhs_exprs>',
 '<literal>',
 '<mod>',
 '<none>',
 '<nonempty_expr_list>',
 '<nonempty_lhs_expr_list>',
 '<nonempty_stmt_list>',
 '<nonzerodigit>',
 '<not_double_quotes>',
 '<not_single_quotes>',
 '<operator>',
 '<orelse_param>',
 '<posonlyargs_param>',
 '<returns>',
 '<start>',
 '<stmt>',
 '<stmt_list>',
 '<stmts>',
 '<string>',
 '<type_comment>',
 '<type_ignore>',
 '<type_ignore_list>',
 '<type_ignore_param>',
 '<type_ignores>',
 '<unaryop>',
 '<vararg>',
 '<withitem>',
 '<withitem_list>',
 '<withitems>'] 

如果您想对 Python 代码生成有更多控制,以下是幕后发生的事情。EBNF 语法 PYTHON_AST_GRAMMAR 可以解析并生成 Python 的 抽象语法树。要生成没有 PythonFuzzer 的 Python 模块,您需要采取以下步骤:

步骤 1: 创建一个适合 ISLaSolver(或任何其他语法模糊器)的非 EBNF 语法:

>>> python_ast_grammar = convert_ebnf_grammar(PYTHON_AST_GRAMMAR) 

步骤 2: 将生成的语法输入到语法模糊器(如 ISLa)中:

>>> solver = ISLaSolver(python_ast_grammar, start_symbol='<FunctionDef>') 

步骤 3: 让语法模糊器生成一个字符串。这个字符串代表一个 AST。

>>> ast_string = str(solver.solve())
>>> ast_string
'FunctionDef(name=\'y\', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])])' 

步骤 4: 将 AST 转换为实际的 Python AST 数据结构。

>>> from [ast](https://docs.python.org/3/library/ast.html) import *
>>> abstract_syntax_tree = eval(ast_string) 

步骤 5: 最后,将 AST 结构转换回可读的 Python 代码:

>>> ast.fix_missing_locations(abstract_syntax_tree)
>>> print(ast.unparse(abstract_syntax_tree))
@set()
def y():
    return 

本章有许多其他应用,包括解析和修改 Python 代码、进化模糊测试等。

这里是 PythonFuzzer 构造函数的详细信息:

PythonFuzzer(self, start_symbol: Optional[str] = None, *, grammar: Optional[Dict[str, List[Union[str, Tuple[str, Dict[str, Any]]]]]] = None, constraint: Optional[str] = None, **kw_params) -> None

生成 Python 代码。参数包括:

  • start_symbol:要生成的语法实体(默认:<FunctionDef>

  • grammar:要使用的 EBNF 语法(默认:PYTHON__AST_GRAMMAR);并且

  • constraint 一个 ISLa 约束(如果有)。

额外的关键字参数传递给 ISLaSolver 超类。

PythonFuzzer <a xlink:href="#" xlink:title="class PythonFuzzer:

生成 Python 代码。">PythonFuzzer <a xlink:href="#" xlink:title="init(self, start_symbol: Optional[str] = None, *, grammar: Optional[Dict[str, List[Union[str, Tuple[str, Dict[str, Any]]]]]] = None, constraint: Optional[str] = None, **kw_params) -> None:

生成 Python 代码。参数包括:

  • start_symbol: 要生成的语法实体(默认: <FunctionDef>

  • grammar: 要使用的 EBNF 语法(默认: PYTHON__AST_GRAMMAR);以及

  • constraint 一个 ISLa 约束(如果有)。

额外的关键字参数传递给 ISLaSolver 超类。">init() <a xlink:href="#" xlink:title="fuzz(self) -> str:

生成 Python 代码字符串。">fuzz() ISLaSolver <a xlink:href="isla.solver.ipynb" xlink:title="class ISLaSolver:

ISLa 公式/约束的求解器类。其顶级方法包括

:meth:~isla.solver.ISLaSolver.solve

用于为 ISLa 约束生成解决方案。

:meth:~isla.solver.ISLaSolver.check

用于检查给定输入是否满足 ISLa 约束。

:meth:~isla.solver.ISLaSolver.parse

用于解析和验证输入。

:meth:~isla.solver.ISLaSolver.repair

用于修复输入,使其满足约束。

:meth:~isla.solver.ISLaSolver.mutate

使用它来变异输入,使得结果满足一个约束。《ISLaSolver》ISLa 求解器 <a xlink:href="isla.solver.ipynb" xlink:title="init(self, grammar: Union[Dict[str, List[str]], str], formula: Union[isla.language.Formula, str, NoneType] = None, structural_predicates: Set[isla.language.StructuralPredicate] = frozenset({StructuralPredicate(name='nth', arity=3, eval_fun=), StructuralPredicate(name='inside', arity=2, eval_fun=), StructuralPredicate(name='same_position', arity=2, eval_fun=), StructuralPredicate(name='consecutive', arity=2, eval_fun=), StructuralPredicate(name='different_position', arity=2, eval_fun=), StructuralPredicate(name='before', arity=2, eval_fun=), StructuralPredicate(name='level', arity=4, eval_fun=), StructuralPredicate(name='direct_child', arity=2, eval_fun=), StructuralPredicate(name='after', arity=2, eval_fun=)}), semantic_predicates: Set[isla.language.SemanticPredicate] = frozenset({SemanticPredicate(count, 3)}), max_number_free_instantiations: int = 10, max_number_smt_instantiations: int = 10, max_number_tree_insertion_results: int = 5, enforce_unique_trees_in_queue: bool = False, debug: bool = False, cost_computer: Optional[ForwardRef('CostComputer')] = None, timeout_seconds: Optional[int] = None, global_fuzzer: bool = False, predicates_unique_in_int_arg: Tuple[isla.language.SemanticPredicate, ...] = (SemanticPredicate(count, 3),), fuzzer_factory: Callable[[Dict[str, List[str]]], isla.fuzzer.GrammarFuzzer] = <function SolverDefaults.>, tree_insertion_methods: Optional[int] = None, activate_unsat_support: bool = False, grammar_unwinding_threshold: int = 4, initial_tree: isla.helpers.Maybe[isla.derivation_tree.DerivationTree] = Maybe(a=None), enable_optimized_z3_queries: bool = True, start_symbol: Optional[str] = None):>

:class:~isla.solver.ISLaSolver的构造函数接受大量的

参数。然而,除了第一个参数:code:grammar之外,其他都是可选的。

构建 ISLa 求解器的最简单方法就是只向它提供一个

仅语法;然后它就像一个语法模糊器。

import random

random.seed(1)

import string

LANG_GRAMMAR = {

...     “”:

...         [""],

...     “”:

...         [" ; ", ""],

...     “”:

...         [" := "],

...     "":

...         ["", ""],

...     "": list(string.ascii_lowercase),

...     "": list(string.digits)

... }

from isla.solver import ISLaSolver

solver = ISLaSolver(LANG_GRAMMAR)

str(solver.solve())

'd := 9'

str(solver.solve())

'v := n ; s := r'

:param grammar: 基础语法;可以是 "Fuzzing Book" 字典

或在 BNF 语法中。

:param formula: 要解决的公式;可以是字符串或易于解析的

公式。如果没有给出公式,则假定默认的 true 约束,并且

当解算器回退到语法 fuzzer 时。产生的解决方案的数量

然后将由 max_number_free_instantiations 绑定。

:param structural_predicates: 解析公式时使用的结构谓词

公式。

:param semantic_predicates: 解析公式时使用的语义谓词

:param max_number_free_instantiations: 非终结符实例化的次数

应该由基于覆盖率的 fuzzer 展开,不受任何公式约束。

:param max_number_smt_instantiations: SMT 公式的解的数量

应该被产生。

:param max_number_tree_insertion_results: 使用树插入方法时的最大结果数

通过树插入解决存在量词。

:param enforce_unique_trees_in_queue: 如果为真,则队列中与同一树相同的

已经存在的树在队列中被丢弃,无论其

约束。

:param debug: 如果为真,则关于状态演变的调试信息

收集,特别是在状态树字段。树的根在

字段 state_tree_root。字段 costs 存储计算的成本值

所有新节点。

:param cost_computer: 用于计算相关成本的 CostComputer

到放置状态在 ISLa 的队列中。

:param timeout_seconds: 解算器将在多少秒后终止。

:param global_fuzzer: 如果设置为 True,则仅使用一个覆盖率引导的语法 fuzzer

对象用于完成无约束的开放推导树。

整个生成时间。这可能对某些目标有益;例如,我们

经验表明,使用 CSV 可以显著提高速度。然而,实现的 k-path

覆盖率可能会降低。

:param predicates_unique_in_int_arg: 在某些情况下需要此参数

实例化全称整数量词。提供的谓词应该

恰好有一个整数参数,并且对于恰好一个整数值

一次所有其他参数都固定。

:param fuzzer_factory: 用于实例化的 fuzzer 构造函数

"free" 非终结符。

:param tree_insertion_methods: 要使用的存在量词树插入方法的组合

通过树插入消除量词。全选:`DIRECT_EMBEDDING &

SELF_EMBEDDING & CONTEXT_ADDITION`.

:param activate_unsat_support: 如果假设公式可能

触发针对不可满足性的附加测试。这

减少输入生成性能,但可能确保终止(带有

对于不可满足的问题,如果求解器可以

否则可能会发散。

:param grammar_unwinding_threshold: 当查询 SMT 求解器时,ISLa 传递一个

定义的正规表达式用于涉及的非终结符的语法。如果这个

语法不是正则的,我们在参考语法中展开相应的部分

深度达到grammar_unwinding_threshold。如果这个深度太浅,它可能会

发生方程等无法解决的情况;如果它太深,它可能会

对性能产生负面影响(并且非常巨大)。

:param initial_tree: 如果求解器应该使用队列的初始输入树

不会从树(<start>, None)开始。

:param enable_optimized_z3_queries: 启用 Z3 查询的预处理(主要是

与长度等事物相关的数值问题。这可以提高性能

显著;然而,可能存在某些问题无法解决

不再需要。在这种情况下,这个选项可以被/应该被禁用。

:param start_symbol: 这是initial_tree的替代方案,用于以

一个不同于<start>的起始符号。如果提供了start_symbol,则树

由一个具有start_symbol值的单个根节点组成的树被选择为

初始树。">init() PythonFuzzer->ISLaSolver 图例 图例 •  public_method() •  private_method() •  overloaded_method() 将鼠标悬停在名称上以查看文档

具体代码的语法

生成代码,用具体语法编写语法规则相当容易。如果我们想生成,比如说,算术表达式,我们可以轻松地创建一个具体的语法规则,它正好能完成这个任务。

import [bookutils.setup](https://github.com/uds-se/fuzzingbook//tree/master/notebooks/shared/bookutils) 
from Grammars import Grammar
from Grammars import is_valid_grammar, convert_ebnf_grammar, extend_grammar, trim_grammar 
from [typing](https://docs.python.org/3/library/typing.html) import Optional 

我们使用Fuzzingbook 格式定义语法,其中语法被表示为从符号到展开备选列表的字典。

EXPR_GRAMMAR: Grammar = {
    "<start>":
        ["<expr>"],

    "<expr>":
        ["<term> + <expr>", "<term> - <expr>", "<term>"],

    "<term>":
        ["<factor> * <term>", "<factor> / <term>", "<factor>"],

    "<factor>":
        ["+<factor>",
         "-<factor>",
         "(<expr>)",
         "<integer>.<integer>",
         "<integer>"],

    "<integer>":
        ["<digit><integer>", "<digit>"],

    "<digit>":
        ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
} 
assert is_valid_grammar(EXPR_GRAMMAR) 

我们可以使用这个语法生成语法上有效的算术表达式。我们使用 ISLa 求解器作为我们的生成器,因为它是功能最强大的;但在这个阶段,我们也可以使用任何其他我们的语法模糊器,比如 GrammarFuzzer。

from [isla.solver](https://rindphi.github.io/isla/) import ISLaSolver 

这里是从语法中产生的具体输入:

expr_solver = ISLaSolver(EXPR_GRAMMAR)
for _ in range(10):
    print(expr_solver.solve()) 
4.3 + 512 / -(7 / 6 - 0 / 9 * 1 * 1) * +8.3 / 7 * 4 / 6
(4 / 7 + 1) / (4) / 9 / 8 + 4 / (3 + 6 - 7)
+--(--(-9) * (4 * 7 + (4) + 4) + --(+(3)) - 6 + 0 / 7 + 7)
(2 * 6 + 0 - 5) * 4 - +1 * (2 - 2) / 8 / 6
(+-(0 - (1) * 7 / 3)) / ((1 * 3 + 8) + 9 - +1 / --0) - 5 * (-+939.491)
+2.9 * 0 / 501.19814 / --+--(6.05002)
+-8.8 / (1) * -+1 + -8 + 9 - 3 / 8 * 6 + 4 * 3 * 5
(+(8 / 9 - 1 - 7)) + ---06.30 / +4.39
8786.82 - +01.170 / 9.2 - +(7) + 1 * 9 - 0
+-6 * 0 / 5 * (-(1.7 * +(-1 / +4.9 * 5 * 1 * 2) + -4.2 + (6 + -5) / (4 * 3 + 4)))

我们可以将语法进一步扩展,使其也能生成赋值和其他语句,逐步覆盖编程语言的整个语法。然而,这并不是一个好主意。为什么?

问题在于,当测试编译器时,你不仅想要能够生成代码,还想要能够解析代码,这样你就可以随意对其进行变异和操作。这正是我们的“具体”语法会给我们带来问题的地方。虽然我们可以轻松解析严格遵循语法的代码(或表达式)...

expr_solver.check('2 + 2') 
True

...一个空格就足以让它失败...

expr_solver.check('2 +  2') 
Error parsing "2 +  2" starting with "<start>"

False

...以及空格的缺失:

expr_solver.check('2+2') 
Error parsing "2+2" starting with "<start>"

False

事实上,在大多数编程语言中,空格是可选的。我们可以更新我们的语法规则,使其能够始终处理可选的空格(引入一个<space>非终结符)。但这样,还有其他特性,比如注释...

expr_solver.check('2 + 2    # should be 4') 
Error parsing "2 + 2    # should be 4" starting with "<start>"

False

...或者续行...

expr_solver.check('2 + \\\n2')  # An expression split over two lines 
Error parsing "2 + \
2" starting with "<start>"

False

我们语法需要覆盖的内容。

此外,还有一些语言特性甚至无法在上下文无关语法中正确表示:

  • 例如,在 C 编程语言中,解析器需要知道一个标识符是否被定义为类型

  • 在 Python 中,缩进级别不能用上下文无关语法表示。

因此,通常一个好的做法是使用专门的解析器(或预处理器)将输入转换为更抽象的表示——通常是结构。在编程语言中,这样的树被称为抽象语法树(AST);它是编译器操作的数据结构。

抽象语法树

表示程序代码的抽象语法树(ASTs)是世界上(如果不是最复杂的数据结构)最复杂的数据结构之一——这主要是因为它们反映了编程语言及其特性的所有复杂性。好消息是,在 Python 中,处理 AST 特别容易——可以使用标准语言特性来处理它们。

让我们用一个例子来说明 AST。这是我们想要处理的代码片段:

def main():
    print("Hello, world!")  # A simple example 
main() 
Hello, world!

让我们获取这个函数的源代码:

import [inspect](https://docs.python.org/3/library/inspect.html) 
main_source = inspect.getsource(main)
print(main_source) 
def main():
    print("Hello, world!")  # A simple example

我们使用Python AST 模块将这段代码字符串转换为 AST 并返回。

import [ast](https://docs.python.org/3/library/ast.html) 

使用 ast.parse(),我们可以将 main() 源代码解析成 AST:

main_tree = ast.parse(main_source) 

这就是这个树看起来像什么:

from [bookutils](https://github.com/uds-se/fuzzingbook//tree/master/notebooks/shared/bookutils) import show_ast 
show_ast(main_tree) 

0 FunctionDef 1 "main" 0--1 2 arguments 0--2 3 Expr 0--3 4 Call 3--4 5 Name 4--5 8 Constant 4--8 6 "print" 5--6 7 Load 5--7 9 "Hello, world!" 8--9

我们看到函数定义已经变成了 FunctionDef 节点,其第三个子节点是一个 Expr 节点,它又变成了一个 Call ——调用 "print" 函数,参数为 "Hello, world!"

这些 AST 节点每个都是一个 构造函数 ——也就是说,我们可以调用 FunctionDef() 来获取一个函数定义节点,或者调用 Call() 来获取一个调用节点。这些构造函数将 AST 子节点 作为参数,但也可以接受很多 可选 参数(我们迄今为止还没有使用)。将 AST 转储 到字符串中会显示每个构造函数的所有参数:

print(ast.dump(main_tree, indent=4)) 
Module(
    body=[
        FunctionDef(
            name='main',
            args=arguments(
                posonlyargs=[],
                args=[],
                kwonlyargs=[],
                kw_defaults=[],
                defaults=[]),
            body=[
                Expr(
                    value=Call(
                        func=Name(id='print', ctx=Load()),
                        args=[
                            Constant(value='Hello, world!')],
                        keywords=[]))],
            decorator_list=[])],
    type_ignores=[])

Python ast 文档 列出了所有这些构造函数,它们构成了抽象语法。有超过 100 个单独的构造函数!(我们说过 AST 是复杂的,对吧?)

上述字符串表示的不错之处在于我们可以 直接 使用它并将其转换成树:

from [ast](https://docs.python.org/3/library/ast.html) import * 
my_main_tree = Module(
    body=[
        FunctionDef(
            name='main',
            args=arguments(
                posonlyargs=[],
                args=[],
                kwonlyargs=[],
                kw_defaults=[],
                defaults=[]),
            body=[
                Expr(
                    value=Call(
                        func=Name(id='print', ctx=Load()),
                        args=[
                            Constant(value='Hello, world!')],
                        keywords=[]))],
            decorator_list=[])],
    type_ignores=[]) 

我们可以将这个树编译成可执行代码:

my_main_tree = fix_missing_locations(my_main_tree)  # required for trees built from constructors
my_main_code = compile(my_main_tree, filename='<unknown>', mode='exec') 
del main  # This deletes the definition of main() 
exec(my_main_code)  # This defines main() again from `code` 
main() 
Hello, world!

我们还可以 反解析 树(即再次将其转换为源代码)。(注意在解析过程中注释是如何丢失的。)

print(ast.unparse(my_main_tree)) 
def main():
    print('Hello, world!')

因此,我们可以

  1. 解析 具体代码到 AST(使用 ast.parse()

  2. 生成 新的 AST 和 修改 现有的 AST

  3. 反解析 AST 以获得具体的代码(使用 ast.unparse()

为了 生成修改 AST(如上所述的第二步),我们需要产生 正确 的 AST 的手段,调用所有构造函数并使用正确的参数。因此,我们有一个 AST 语法,它可以(并解析)生成我们想要的 AST。

ASTs 的语法

程序设计语言的语法是围绕中最复杂的形式语法之一,AST 反映了其中很多复杂性。我们将使用 Python 文档中指定的 抽象 AST 语法 作为基础,并逐步构建形式上下文无关语法。

常量

我们将从一个简单的常量开始——字符串和整数。同样,我们使用 fuzzingbook 语法,因为它允许更容易的扩展。

import [string](https://docs.python.org/3/library/string.html) 
ANYTHING_BUT_DOUBLE_QUOTES_AND_BACKSLASH = (string.digits + string.ascii_letters + string.punctuation + ' ').replace('"', '').replace('\\', '')
ANYTHING_BUT_SINGLE_QUOTES_AND_BACKSLASH = (string.digits + string.ascii_letters + string.punctuation + ' ').replace("'", '').replace('\\', '') 
ANYTHING_BUT_DOUBLE_QUOTES_AND_BACKSLASH 
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'()*+,-./:;<=>?@[]^_`{|}~ "

ANYTHING_BUT_SINGLE_QUOTES_AND_BACKSLASH 
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&()*+,-./:;<=>?@[]^_`{|}~ '

PYTHON_AST_CONSTANTS_GRAMMAR: Grammar = {
    '<start>': [ '<expr>' ],

    # Expressions
    '<expr>': [ '<Constant>', '<Expr>' ],
    '<Expr>': [ 'Expr(value=<expr>)' ],

    # Constants
    '<Constant>': [ 'Constant(value=<literal>)' ],
    '<literal>': [ '<string>', '<integer>', '<float>', '<bool>', '<none>' ],

    # Strings
    '<string>': [ '"<not_double_quotes>*"', "'<not_single_quotes>*'" ],
    '<not_double_quotes>': list(ANYTHING_BUT_DOUBLE_QUOTES_AND_BACKSLASH),
    '<not_single_quotes>': list(ANYTHING_BUT_SINGLE_QUOTES_AND_BACKSLASH),
    # FIXME: The actual rules for Python strings are also more complex:
    # https://docs.python.org/3/reference/lexical_analysis.html#numeric-literals

    # Numbers
    '<integer>': [ '<digit>', '<nonzerodigit><digits>' ],
    '<float>': [ '<integer>.<integer>' ],
    '<nonzerodigit>': ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
    '<digits>': [ '<digit><digits>', '<digit>' ],
    '<digit>': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
    # FIXME: There are _many_ more ways to express numbers in Python; see
    # https://docs.python.org/3/reference/lexical_analysis.html#numeric-literals

    # More
    '<bool>': [ 'True', 'False' ],
    '<none>': [ 'None' ],

    # FIXME: Not supported: bytes, format strings, regex strings...
} 

注意,我们在语法中使用 扩展的巴科斯-诺尔范式(这里:<string>):

  • <elem>+ 表示 <elem> 的一个或多个实例;

  • <elem>* 表示 <elem> 的零个或多个实例;

  • <elem>? 表示 <elem> 的一个或零个实例。

调用 is_valid_grammar() 确保我们的语法没有常见的错误。不要在没有它的情况下编写语法!

assert is_valid_grammar(PYTHON_AST_CONSTANTS_GRAMMAR) 
constants_grammar = convert_ebnf_grammar(PYTHON_AST_CONSTANTS_GRAMMAR)
constants_solver = ISLaSolver(constants_grammar)
constants_tree_str = str(constants_solver.solve())
print(constants_tree_str) 
Expr(value=Constant(value=None))

我们可以从这个表达式创建一个 AST,并将其转换为 Python 代码(好吧,一个字面量):

constants_tree = eval(constants_tree_str)
ast.unparse(constants_tree) 
'None'

让我们做几次:

def test_samples(grammar: Grammar, iterations: int = 10, start_symbol = None, log: bool = True):
    g = convert_ebnf_grammar(grammar)
    solver = ISLaSolver(g, start_symbol=start_symbol, max_number_free_instantiations=iterations)
    for i in range(iterations):
        tree_str = str(solver.solve())
        tree = eval(tree_str)
        ast.fix_missing_locations(tree)
        if log:
            code = ast.unparse(tree)
            print(f'{code:40} # {tree_str}') 
test_samples(PYTHON_AST_CONSTANTS_GRAMMAR) 
False                                    # Expr(value=Constant(value=False))
2                                        # Constant(value=2)
None                                     # Constant(value=None)
'#'                                      # Constant(value="#")
550.81                                   # Constant(value=550.81)
True                                     # Constant(value=True)
'.'                                      # Constant(value='.')
467                                      # Constant(value=467)
7894                                     # Constant(value=7894)
263                                      # Constant(value=263)

我们的语法也可以 解析 从具体代码中获得的 AST。

sample_constant_code = "4711"
sample_constant_ast = ast.parse(sample_constant_code).body[0]  # get the `Expr` node
sample_constant_ast_str = ast.dump(sample_constant_ast)
print(sample_constant_ast_str) 
Expr(value=Constant(value=4711))

constant_solver = ISLaSolver(constants_grammar)
constant_solver.check(sample_constant_ast_str) 
True

让我们提出一个测验问题:我们的语法支持负数吗? 为了这个,我们首先找出 Constant() 构造函数是否也接受一个 负数 作为参数?结果是它可以:

ast.unparse(Constant(value=-1)) 
'-1'

但如果我们解析一个负数,比如 -1,会发生什么?有人可能会假设这仅仅会得到一个 Constant(-1),对吧?自己试试看!

from [bookutils](https://github.com/uds-se/fuzzingbook//tree/master/notebooks/shared/bookutils) import quiz 

习题

如果我们解析一个负数,我们会得到

答案是解析 -1 会得到一个一元减号 USub() 应用到一个正数上:

print(ast.dump(ast.parse('-1'))) 
Module(body=[Expr(value=UnaryOp(op=USub(), operand=Constant(value=1)))], type_ignores=[])

由于一元运算符(目前)不是我们语法的组成部分,它无法处理负数:

sample_constant_code = "-1"
sample_constant_ast = ast.parse(sample_constant_code).body[0]  # get the `Expr` node
sample_constant_ast_str = ast.dump(sample_constant_ast)
constant_solver = ISLaSolver(constants_grammar)
constant_solver.check(sample_constant_ast_str) 
Error parsing "Expr(value=UnaryOp(op=USub(), operand=Constant(value=1)))" starting with "<start>"

False

在接下来的几节中,我们将逐步扩展我们的语法,加入越来越多的 Python 特性,最终覆盖(几乎)整个语言。

复合结构

让我们添加复合常量——列表、字典、元组等。这些在 AST 中的表示如下:

print(ast.dump(ast.parse("{ 'a': set() }"), indent=4)) 
Module(
    body=[
        Expr(
            value=Dict(
                keys=[
                    Constant(value='a')],
                values=[
                    Call(
                        func=Name(id='set', ctx=Load()),
                        args=[],
                        keywords=[])]))],
    type_ignores=[])

让我们将这些编码到语法中,再次使用来自抽象 AST 语法的定义。所有这些结构也接受上下文,其中标识符被使用——Load() 如果它们用于评估,Store() 如果它们出现在赋值的左侧(是的,在 Python 中,你可以在赋值的左侧有一个元组,比如 (x, y) = (1, 2)),以及 Del() 如果它们用作 del 语句的操作数。目前,我们只交替使用 Load()Del()

PYTHON_AST_COMPOSITES_GRAMMAR: Grammar = extend_grammar(
    PYTHON_AST_CONSTANTS_GRAMMAR, {
    '<expr>': PYTHON_AST_CONSTANTS_GRAMMAR['<expr>'] + [
        '<Dict>', '<Set>', '<List>', '<Tuple>'
    ],

    '<Dict>': [ 'Dict(keys=<expr_list>, values=<expr_list>)' ],
    '<Set>': [ 'Set(elts=<nonempty_expr_list>)', '<EmptySet>' ],
    '<EmptySet>': [ 'Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])' ],
    '<List>': [
        'List(elts=<expr_list>, ctx=Load())',
        'List(elts=<expr_list>, ctx=Del())',
    ],
    '<Tuple>': [
        'Tuple(elts=<expr_list>, ctx=Load())',
        'Tuple(elts=<expr_list>, ctx=Del())',
    ],

    # Lists of expressions
    '<expr_list>': [ '[<exprs>?]' ],
    '<nonempty_expr_list>': [ '[<exprs>]' ],
    '<exprs>': [ '<expr>', '<exprs>, <expr>' ],
}) 
assert is_valid_grammar(PYTHON_AST_COMPOSITES_GRAMMAR) 
for elt in [ '<Constant>', '<Dict>', '<Set>', '<List>', '<Tuple>' ]:
    print(elt)
    test_samples(PYTHON_AST_COMPOSITES_GRAMMAR, start_symbol=elt)
    print() 
<Constant>
'c'                                      # Constant(value='c')
96.7                                     # Constant(value=96.7)
None                                     # Constant(value=None)
False                                    # Constant(value=False)
505                                      # Constant(value=505)
'U'                                      # Constant(value="U")
True                                     # Constant(value=True)
41398                                    # Constant(value=41398)
24                                       # Constant(value=24)
72                                       # Constant(value=72)

<Dict>
{}                                       # Dict(keys=[], values=[List(elts=[Dict(keys=[List(elts=[Constant(value=9.63)], ctx=Load())], values=[Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Set(elts=[Constant(value=True), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])])], ctx=Load())]), Constant(value=2), Tuple(elts=[Constant(value=''), Constant(value=False), Set(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), Expr(value=List(elts=[Constant(value=None)], ctx=Load())), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del())], ctx=Del())])
{577: ''}                                # Dict(keys=[Constant(value=577), Constant(value=34), Constant(value=286), Constant(value=7051)], values=[Constant(value="")])
{90: 14}                                 # Dict(keys=[Constant(value=90)], values=[Constant(value=14), Constant(value=88), Constant(value=435)])
{"nF}[ (^{bXBrwzf-P@geW'.]~G>;O2i&/t7Cc5:QU1jR4q_8VJ)Hsxd#o*aT3Sv!$ku?IhMpmA,EL0ZN=`9yK|<Y6lD+%I": 'Gym]A&K;70{jJLC"DV)/Y S.eNMEQq^%?i+-b!hz|gcUBvW485O#pPu~d:(F>_<a}kI2norf9H[T,lXt=w6@Z*1$xs`"R3'} # Dict(keys=[Constant(value="nF}[ (^{bXBrwzf-P@geW'.]~G>;O2i&/t7Cc5:QU1jR4q_8VJ)Hsxd#o*aT3Sv!$ku?IhMpmA,EL0ZN=`9yK|<Y6lD+%I")], values=[Constant(value='Gym]A&K;70{jJLC"DV)/Y S.eNMEQq^%?i+-b!hz|gcUBvW485O#pPu~d:(F>_<a}kI2norf9H[T,lXt=w6@Z*1$xs`"R3')])
{}                                       # Dict(keys=[], values=[])
{}                                       # Dict(keys=[], values=[])
{}                                       # Dict(keys=[], values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Constant(value=True), Constant(value=687596.53), Dict(keys=[Set(elts=[Expr(value=Set(elts=[Set(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), Constant(value=34.676)]))])], values=[Set(elts=[Set(elts=[List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Set(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Load())], ctx=Del())])])]), List(elts=[], ctx=Load())])
{}                                       # Dict(keys=[], values=[])
{}                                       # Dict(keys=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], values=[])
{}                                       # Dict(keys=[Tuple(elts=[], ctx=Del())], values=[])

<Set>
{
[], 79.2}                              # Set(elts=[Expr(value=List(elts=[], ctx=Del())), Constant(value=79.2)])
set()                                    # Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])
{{{False: [set()], None: []}, (({20: set()},),)}} # Set(elts=[Set(elts=[Dict(keys=[Constant(value=False), Constant(value=None)], values=[List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Load()), List(elts=[], ctx=Del())]), Tuple(elts=[Tuple(elts=[Dict(keys=[Constant(value=20)], values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Constant(value=True), List(elts=[], ctx=Del())])], ctx=Load())], ctx=Del())])])
{'Z'}                                    # Set(elts=[Constant(value='Z')])
{3763, ''}                               # Set(elts=[Constant(value=3763), Constant(value="")])
{475, 136, 95, 841, 58}                  # Set(elts=[Constant(value=475), Constant(value=136), Constant(value=95), Constant(value=841), Constant(value=58)])
{"F3Ye]1UZz&sPrG:D-R`k?5d+SM,/4b!uE fW;L$)@oQ'h^qI[(lXgN0wmt=~Jav86|Vp%72CcOBj_nHK<9A*#i}yTx>{."} # Set(elts=[Constant(value="F3Ye]1UZz&sPrG:D-R`k?5d+SM,/4b!uE fW;L$)@oQ'h^qI[(lXgN0wmt=~Jav86|Vp%72CcOBj_nHK<9A*#i}yTx>{.")])
{66, 7}                                  # Set(elts=[Constant(value=66), Constant(value=7)])
{set(), '', None, '_P[', 'L}w,6'}        # Set(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Constant(value=''), Constant(value=None), Constant(value='_P['), Constant(value='L}w,6')])
{'51I{Ef&u;kThXbRo]cV/8)Q@W>4|=J7lHge"+^y%(rv<q.DM:najxi9OUG?!KS zsd2t-Fm3NApB#0$~C`*PY'} # Set(elts=[Constant(value='51I{Ef&u;kThXbRo]cV/8)Q@W>4|=J7lHge"+^y%(rv<q.DM:najxi9OUG?!KS zsd2t-Fm3NApB#0$~C`*PY')])

<List>
[[], {831.3: (7, set(), {('1',)})}]      # List(elts=[List(elts=[], ctx=Load()), Dict(keys=[Constant(value=831.30), Constant(value=None), Expr(value=Tuple(elts=[Constant(value=""), Constant(value=True), Constant(value=False)], ctx=Del()))], values=[Tuple(elts=[Constant(value=7), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Set(elts=[Tuple(elts=[Constant(value='1')], ctx=Load())])], ctx=Load())])], ctx=Del())
[22]                                     # List(elts=[Constant(value=22)], ctx=Load())
[64]                                     # List(elts=[Constant(value=64)], ctx=Load())
[56]                                     # List(elts=[Constant(value=56)], ctx=Del())
[9589]                                   # List(elts=[Constant(value=9589)], ctx=Load())
[780]                                    # List(elts=[Constant(value=780)], ctx=Del())
[164, 47]                                # List(elts=[Constant(value=164), Constant(value=47)], ctx=Load())
["^dG@0 N26zE73qSfX,>xhPlW#j.1cQO4bF+A:LZR'CT=$i_", 'tJI`]gD_M/8yu!%<n~&H|9w*)Ur5sk(e}[vap?V-oK{BYm;eccmO'] # List(elts=[Constant(value="^dG@0 N26zE73qSfX,>xhPlW#j.1cQO4bF+A:LZR'CT=$i_"), Constant(value="tJI`]gD_M/8yu!%<n~&H|9w*)Ur5sk(e}[vap?V-oK{BYm;eccmO")], ctx=Load())
['e]@JX9LBnA:0Ha³KVf OWuFT%*8ZGtp/x`Cw"li|Mq?_UI45$)zNh#gDcs;!-d[,(~{>bYrE<.RQ27}&moSk+vjP=6y9'] # List(elts=[Constant(value='e]@JX9LBnA:0Ha³KVf OWuFT%*8ZGtp/x`Cw"li|Mq?_UI45$)zNh#gDcs;!-d[,(~{>bYrE<.RQ27}&moSk+vjP=6y9')], ctx=Load())
[set(), set()]                           # List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Load())

<Tuple>
()                                       # Tuple(elts=[], ctx=Load())
(set(),)                                 # Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del())
(set(), [], 
1.4, [[None], True], {set(): (False, {set()})}) # Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), List(elts=[], ctx=Del()), Expr(value=Constant(value=1.4)), List(elts=[List(elts=[Constant(value=None)], ctx=Load()), Constant(value=True)], ctx=Load()), Dict(keys=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Set(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), Expr(value=Constant(value=False))], values=[Tuple(elts=[Constant(value=False), Set(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])])], ctx=Load())])], ctx=Del())
('',)                                    # Tuple(elts=[Constant(value="")], ctx=Load())
(93,)                                    # Tuple(elts=[Constant(value=93)], ctx=Load())
(28371613, 51, 892, 45, 10678, '')       # Tuple(elts=[Constant(value=28371613), Constant(value=51), Constant(value=892), Constant(value=45), Constant(value=10678), Constant(value='')], ctx=Del())
(72, 632)                                # Tuple(elts=[Constant(value=72), Constant(value=632)], ctx=Load())
('p[R#U', '5JRh~3', 'aAI>V+LBk60Ogp')    # Tuple(elts=[Constant(value='p[R#U'), Constant(value="5JRh~3"), Constant(value="aAI>V+LBk60Ogp")], ctx=Load())
(363,)                                   # Tuple(elts=[Constant(value=363)], ctx=Del())
('a*wyz!$CcJ.TDj?<8Q`o}|fG~3%FX/O:r@YW5dK,MqLt^l&B(PbH1_ZInkimvSV4x> u{+2gs)h"e9NA;76]=E-0;',) # Tuple(elts=[Constant(value='a*wyz!$CcJ.TDj?<8Q`o}|fG~3%FX/O:r@YW5dK,MqLt^l&B(PbH1_ZInkimvSV4x> u{+2gs)h"e9NA;76]=E-0;')], ctx=Load())

你可能会遇到一些不常见的表达式。例如:

  1. () 是一个空元组。

  2. (1,) 是一个只有一个元素的元组。

  3. {} 是一个空字典;{1} 是一个只有一个元素的集合。

  4. 空集合用 set() 表示。

我们使用 set() 来表示空集的事实实际上是我们的 PYTHON_AST_COMPOSITES_GRAMMAR 语法的一个特性。如果我们不提供任何元素就调用 Set() AST 构造函数,我们就能得到这个漂亮的表达式...

print(ast.unparse(Set(elts=[]))) 
{*()}

...这确实会评估为一个空集。

{*()} 
set()

从技术上来说,这些都是正确的,但我们希望坚持(某种程度上)更易读的代码。如果你想让你程序员的伙伴们感到困惑,总是使用 {*()} 而不是 set()

表达式

让我们通过添加表达式来扩展我们的语法。Python 解析器已经处理了优先级规则,因此我们可以以类似的方式处理所有一元和二元运算符。

print(ast.dump(ast.parse("2 + 2 is not False"), indent=4)) 
Module(
    body=[
        Expr(
            value=Compare(
                left=BinOp(
                    left=Constant(value=2),
                    op=Add(),
                    right=Constant(value=2)),
                ops=[
                    IsNot()],
                comparators=[
                    Constant(value=False)]))],
    type_ignores=[])

PYTHON_AST_EXPRS_GRAMMAR: Grammar = extend_grammar(PYTHON_AST_COMPOSITES_GRAMMAR, {
    '<expr>': PYTHON_AST_COMPOSITES_GRAMMAR['<expr>'] + [
        '<BoolOp>', '<BinOp>', '<UnaryOp>', '<Compare>',
    ],

    # Booleans: and or
    '<BoolOp>': [ 'BoolOp(op=<boolop>, values=<expr_list>)' ],
    '<boolop>': [ 'And()', 'Or()' ],

    # Binary operators: + - * ...
    '<BinOp>': [ 'BinOp(left=<expr>, op=<operator>, right=<expr>)' ],
    '<operator>': [ 'Add()', 'Sub()', 'Mult()', 'MatMult()',
                   'Div()', 'Mod()', 'Pow()',
                   'LShift()', 'RShift()', 'BitOr()', 'BitXor()', 'BitAnd()',
                   'FloorDiv()' ],

    # Unary operators: not + - ...
    '<UnaryOp>': [ 'UnaryOp(op=<unaryop>, operand=<expr>)'],
    '<unaryop>': [ 'Invert()', 'Not()', 'UAdd()', 'USub()' ],

    # Comparisons: == != < <= > >= is in ...
    '<Compare>': [ 'Compare(left=<expr>, ops=<cmpop_list>, comparators=<expr_list>)'],
    '<cmpop_list>': [ '[<cmpops>?]' ],
    '<cmpops>': [ '<cmpop>', '<cmpop>, <cmpops>' ],
    '<cmpop>': [ 'Eq()', 'NotEq()', 'Lt()', 'LtE()', 'Gt()', 'GtE()',
                 'Is()', 'IsNot()', 'In()', 'NotIn()' ],

    # FIXME: There's a few more expressions: GeneratorExp, Await, YieldFrom, ...
}) 
assert is_valid_grammar(PYTHON_AST_EXPRS_GRAMMAR) 
for elt in [ '<BoolOp>', '<BinOp>', '<UnaryOp>', '<Compare>' ]:
    print(elt)
    test_samples(PYTHON_AST_EXPRS_GRAMMAR, start_symbol=elt)
    print() 
<BoolOp>
() and {-([]) / (set(), set()), {
True: set()}} # BoolOp(op=And(), values=[BoolOp(op=Or(), values=[]), Set(elts=[BinOp(left=UnaryOp(op=USub(), operand=Compare(left=List(elts=[], ctx=Del()), ops=[], comparators=[])), op=Div(), right=Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Load())), Dict(keys=[Expr(value=Constant(value=True))], values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), List(elts=[], ctx=Load())])])])
(set(), set(), set() @ set() | set() + set()) and set() ** (set() ^ set()) * set() # BoolOp(op=And(), values=[Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=MatMult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=BitOr(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))], ctx=Del()), BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Pow(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitXor(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), op=Mult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
set() % (set() >> set()) - (set() << set()) or set() & set() # BoolOp(op=Or(), values=[BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mod(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=RShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), op=Sub(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=LShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitAnd(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
'8' or 6                                 # BoolOp(op=Or(), values=[Constant(value='8'), Constant(value=6)])
~+123.95                                 # BoolOp(op=Or(), values=[UnaryOp(op=Invert(), operand=UnaryOp(op=UAdd(), operand=Constant(value=123.95)))])
not False // None                        # BoolOp(op=Or(), values=[UnaryOp(op=Not(), operand=BinOp(left=Constant(value=False), op=FloorDiv(), right=Constant(value=None)))])
'S' and 6180 in 397494                   # BoolOp(op=And(), values=[Constant(value="S"), Compare(left=Constant(value=6180), ops=[In()], comparators=[Constant(value=397494)])])
41                                       # BoolOp(op=And(), values=[Constant(value=41)])
214                                      # BoolOp(op=Or(), values=[Constant(value=214)])
5818 and "N1qoR6ak 2UJTWyh>!B)/#YKe0]=w{E.-Q`F[5'&⁹cA~<V+M$bnLu%H8I3;g*D?rz7Xj:}pPvif_GOtx4,(ZCdmls|@YiT" and 70 and 884 # BoolOp(op=And(), values=[Constant(value=5818), Constant(value="N1qoR6ak 2UJTWyh>!B)/#YKe0]=w{E.-Q`F[5'&⁹cA~<V+M$bnLu%H8I3;g*D?rz7Xj:}pPvif_GOtx4,(ZCdmls|@YiT"), Constant(value=70), Constant(value=884)])

<BinOp>
{} - 33                                  # BinOp(left=Expr(value=Dict(keys=[], values=[Tuple(elts=[UnaryOp(op=Invert(), operand=List(elts=[List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Load()), Tuple(elts=[], ctx=Del()), BoolOp(op=And(), values=[Set(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mod(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], ctx=Load())])], ctx=Del())), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Pow(), right=Compare(left=Tuple(elts=[], ctx=Load()), ops=[], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]))], ctx=Del())])), op=Sub(), right=Constant(value=33))
set() / (set() << set()) * (set() >> set()) // (set() @ set() & set()) # BinOp(left=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Div(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=LShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), op=Mult(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=RShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), op=FloorDiv(), right=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=MatMult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=BitAnd(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))
None ^ False                             # BinOp(left=Constant(value=None), op=BitXor(), right=Constant(value=False))
-'' + +7719.5                            # BinOp(left=UnaryOp(op=USub(), operand=Constant(value="")), op=Add(), right=UnaryOp(op=UAdd(), operand=Constant(value=7719.5)))
(set() or 906) >> ('F') | (not (True)) % ((set() and set()) << False) # BinOp(left=BinOp(left=BoolOp(op=Or(), values=[Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), Constant(value=906)]), op=RShift(), right=BoolOp(op=Or(), values=[Constant(value='F')])), op=BitOr(), right=BinOp(left=UnaryOp(op=Not(), operand=BoolOp(op=Or(), values=[Constant(value=True)])), op=Mod(), right=BinOp(left=BoolOp(op=And(), values=[Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[])]), op=LShift(), right=Constant(value=False))))
((set()) > None != set()) | ((set()))    # BinOp(left=Compare(left=Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), ops=[Gt(), NotEq()], comparators=[Constant(value=None), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), op=BitOr(), right=Compare(left=Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), ops=[], comparators=[Constant(value=True)]))
524 - 188                                # BinOp(left=Constant(value=524), op=Sub(), right=Constant(value=188))
6214 / 81                                # BinOp(left=Constant(value=6214), op=Div(), right=Constant(value=81))
26 / 43                                  # BinOp(left=Constant(value=26), op=Div(), right=Constant(value=43))
"s85;3Rw?ST!NI]_-eJ(x7'kG|z}C^&fWLnY[Z,rV*Qj.`Ed%:4<t" ^ '/$ao6 U{2cim@hHtF>b+vX)KBg1l=qyMDp~O0#A9uPa+l' # BinOp(left=Constant(value="s85;3Rw?ST!NI]_-eJ(x7'kG|z}C^&fWLnY[Z,rV*Qj.`Ed%:4<t"), op=BitXor(), right=Constant(value="/$ao6 U{2cim@hHtF>b+vX)KBg1l=qyMDp~O0#A9uPa+l"))

<UnaryOp>
+(set(), 
[])                            # UnaryOp(op=UAdd(), operand=Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Expr(value=List(elts=[], ctx=Del()))], ctx=Del()))
~(None)                                  # UnaryOp(op=Invert(), operand=BoolOp(op=Or(), values=[Constant(value=None)]))
-(((not {set(), set()})) & {set(): set(), (): set()}) # UnaryOp(op=USub(), operand=BinOp(left=Compare(left=UnaryOp(op=Not(), operand=Set(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])])), ops=[], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), op=BitAnd(), right=Dict(keys=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Tuple(elts=[], ctx=Load())], values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Load())])))
-((set() + set()) % ((set() << set()) / (set() ^ set()))) ** (set() // set() >> set() * set()) # UnaryOp(op=USub(), operand=BinOp(left=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Mod(), right=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=LShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Div(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitXor(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))), op=Pow(), right=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=FloorDiv(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=RShift(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))))
-True                                    # UnaryOp(op=USub(), operand=Constant(value=True))
+(4.3 @ (823 - '&') | '')                # UnaryOp(op=UAdd(), operand=BinOp(left=BinOp(left=Constant(value=4.30), op=MatMult(), right=BinOp(left=Constant(value=823), op=Sub(), right=Constant(value='&'))), op=BitOr(), right=Constant(value="")))
~(False <= 51 not in 959)                # UnaryOp(op=Invert(), operand=BoolOp(op=And(), values=[Compare(left=Constant(value=False), ops=[LtE(), NotIn()], comparators=[Constant(value=51), Constant(value=959)])]))
~17                                      # UnaryOp(op=Invert(), operand=Constant(value=17))
not 26                                   # UnaryOp(op=Not(), operand=Constant(value=26))
-68                                      # UnaryOp(op=USub(), operand=Constant(value=68))

<Compare>
()                                       # Compare(left=BoolOp(op=Or(), values=[]), ops=[], comparators=[Expr(value=Constant(value=8)), Tuple(elts=[List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del()), Dict(keys=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], values=[]), Set(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Tuple(elts=[], ctx=Del())]), UnaryOp(op=UAdd(), operand=BinOp(left=Compare(left=Tuple(elts=[], ctx=Load()), ops=[], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), op=Add(), right=List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Load())))], ctx=Del())])
(set() & set()) / (set() @ (set() - set())) not in set() ^ set() # Compare(left=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitAnd(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Div(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=MatMult(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Sub(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))), ops=[NotIn()], comparators=[BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitXor(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
set() % set() // None << (set() - set() >> set() ** set()) <= set() | set() > set() + set() # Compare(left=BinOp(left=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mod(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=FloorDiv(), right=Constant(value=None)), op=LShift(), right=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Sub(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=RShift(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Pow(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))), ops=[LtE(), Gt()], comparators=[BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitOr(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Constant(value=True)])
-632.86 != (not ~(not ('')))             # Compare(left=UnaryOp(op=USub(), operand=Constant(value=632.860)), ops=[NotEq()], comparators=[UnaryOp(op=Not(), operand=UnaryOp(op=Invert(), operand=UnaryOp(op=Not(), operand=BoolOp(op=And(), values=[Constant(value='')]))))])
'' >= 717 is not False                   # Compare(left=Constant(value=""), ops=[GtE(), IsNot(), Eq()], comparators=[Constant(value=717), Constant(value=False)])
15 < 39                                  # Compare(left=Constant(value=15), ops=[Lt(), Is(), Gt(), In()], comparators=[Constant(value=39)])
548 != 934688                            # Compare(left=Constant(value=548), ops=[NotEq(), LtE()], comparators=[Constant(value=934688)])
"w-xSGA8TI{%pRcq6e!_E:}P]9LM/&b1+7*lBDnvu)[o`3dY|Oj~JU<#Z'rH;g,f>@Q0tKk4N$iVaFhzW52y=(C.? sXm^{ " in 425 # Compare(left=Constant(value="w-xSGA8TI{%pRcq6e!_E:}P]9LM/&b1+7*lBDnvu)[o`3dY|Oj~JU<#Z'rH;g,f>@Q0tKk4N$iVaFhzW52y=(C.? sXm^{ "), ops=[In()], comparators=[Constant(value=425), Constant(value=21270)])
'H]3Ky.2p-:#6F%9V{X⁸)lMD[;7Otk/hgImvcJf& E`uG}w?PY:' >= 'nCALds|1zjq4BZ$"ab*@_(e<!rT=iUW~05+,Q>oNSxRVpF' # Compare(left=Constant(value='H]3Ky.2p-:#6F%9V{X⁸)lMD[;7Otk/hgImvcJf& E`uG}w?PY:'), ops=[GtE(), Gt(), NotIn(), IsNot(), GtE()], comparators=[Constant(value='nCALds|1zjq4BZ$"ab*@_(e<!rT=iUW~05+,Q>oNSxRVpF')])
6.3                                      # Compare(left=Constant(value=6.3), ops=[], comparators=[])

并非所有这些表达式都是类型正确的。例如,set() * set() 在运行时会引发类型错误。尽管如此,它们可以被正确解析

到目前为止,我们的语法有多好?让我们创建 20 个表达式并检查其中有多少是类型正确的

  1. 解析时没有SyntaxError

  2. 评估时没有 TypeError

expr_iterations = 20
bad_syntax = 0
bad_type = 0
ast_exprs_grammar = convert_ebnf_grammar(PYTHON_AST_EXPRS_GRAMMAR)
expr_solver = ISLaSolver(ast_exprs_grammar, max_number_free_instantiations=expr_iterations)
for i in range(expr_iterations):
    expr_tree = eval(str(expr_solver.solve()))
    expr_tree = fix_missing_locations(expr_tree)
    expr_str = ast.unparse(expr_tree)
    print(i, expr_str)
    try:
        ...  # insert parsing code here
    except SyntaxError:
        bad_syntax += 1
    except TypeError:
        bad_type += 1

    try:
        ...  # <-- insert evaluation code here
    except TypeError:
        bad_type += 1
    except SyntaxError:
        bad_syntax += 1

print(f"Bad syntax: {bad_syntax}/{expr_iterations}")
print(f"Bad type: {bad_type}/{expr_iterations}") 
0 set()
1 
2 [
~(False,) >> {635: (set() @ set() & set(),), 99.1 not in set() ** set(): {[set() ^ set()]}}]
3 (set() * set() - (set() + set())) / ((set() ^ set()) % set() | set() << set() % set())
4 not None
5 -+(True and '#' and 'x')
6 (8876 > 46 in 36 != 50)
7 24
8 "LfDW-kSM|tpB&+V*RgQ7U]3xq)zh~n^`wTdie5jvPN: A2K?$ZGJ(X;%@sr9mcIu!}OC/1><b=y'0H8o_.4lFYa{6[,>E?"
9 'o,awXihgeM[581Bln"RA60^k2N_L=d$C`7U~f)(&ZG]#m+DqF|PjpIQ<.4ur@ T!-W}Vs:Y{*zOEJb3StHK>?y%c/;iv9'
10 ((set()) < set() is set()) >= ((set()))
11 ((set())) == ((set()) <= set())
12 
13 
14 set()
15 set()
16 []
17 
18 ((() or 'k5'))
19 () | []
Bad syntax: 0/20
Bad type: 0/20

我们在这里做得还不错。在原则上,可以使用 ISLa 约束,使得生成的代码能够正确地类型化——但这可能需要数百到数千条规则。我们将把这个练习留给读者。

注意,一旦标识符出现,你不应该重复这个实验。有很小的可能性,fuzzer 会合成一个像os.remove("/")这样的调用——然后你的文件系统就消失了!

名称和函数调用

让我们添加一些标识符,这样我们就可以调用函数

ID_START = string.ascii_letters + '_'
ID_CONTINUE = ID_START + string.digits 
ID_CONTINUE 
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789'

print(ast.dump(ast.parse("xyzzy(a, b=c)"), indent=4)) 
Module(
    body=[
        Expr(
            value=Call(
                func=Name(id='xyzzy', ctx=Load()),
                args=[
                    Name(id='a', ctx=Load())],
                keywords=[
                    keyword(
                        arg='b',
                        value=Name(id='c', ctx=Load()))]))],
    type_ignores=[])

PYTHON_AST_IDS_GRAMMAR: Grammar = extend_grammar(PYTHON_AST_EXPRS_GRAMMAR, {
    '<expr>': PYTHON_AST_EXPRS_GRAMMAR['<expr>'] + [
        '<Name>', '<Call>'
    ],

    # Identifiers
    '<Name>': [
        'Name(id=<identifier>, ctx=Load())',
        'Name(id=<identifier>, ctx=Del())'
    ],
    '<identifier>': [ "'<id>'" ],
    '<id>': [ '<id_start><id_continue>*' ],
    '<id_start>': list(ID_START),
    '<id_continue>': list(ID_CONTINUE),
    # FIXME: Actual rules are a bit more complex; see
    # https://docs.python.org/3/reference/lexical_analysis.html#identifiers

    # Function Calls
    '<Call>': [ 'Call(func=<func><args_param><keywords_param>)' ],
    '<args_param>': [ ', args=<expr_list>' ],
    '<keywords_param>': [ ', keywords=<keyword_list>' ],
    '<func>': [ '<expr>' ],  # Actually <Expr>, but this is more readable and parses 90%
    '<keyword_list>': [ '[<keywords>?]' ],
    '<keywords>': [ '<keyword>', '<keyword>, <keywords>' ],
    '<keyword>': [ 'keyword(arg=<identifier>, value=<expr>)' ]
}) 
# do import this unconditionally
if sys.version_info >= (3, 13):
    PYTHON_AST_IDS_GRAMMAR: Grammar = extend_grammar(PYTHON_AST_IDS_GRAMMAR, {
        # As of 3.13, args and keywords parameters are optional
        '<Call>': [ 'Call(func=<func><args_param>?<keywords_param>?)' ],
    }) 
assert is_valid_grammar(PYTHON_AST_IDS_GRAMMAR) 
for elt in [ '<Name>', '<Call>' ]:
    print(elt)
    test_samples(PYTHON_AST_IDS_GRAMMAR, start_symbol=elt)
    print() 
<Name>
n                                        # Name(id='n', ctx=Load())
vmGtKyT3Oq1gBC_srAIRaeQw6Dh8V5oLdj9FcvHfb4MpPZiNuEJ27WYU0lnkSxX9Lz # Name(id='vmGtKyT3Oq1gBC_srAIRaeQw6Dh8V5oLdj9FcvHfb4MpPZiNuEJ27WYU0lnkSxX9Lz', ctx=Del())
h                                        # Name(id='h', ctx=Load())
L                                        # Name(id='L', ctx=Del())
M                                        # Name(id='M', ctx=Load())
g                                        # Name(id='g', ctx=Del())
P                                        # Name(id='P', ctx=Del())
It                                       # Name(id='It', ctx=Del())
jGn7g                                    # Name(id='jGn7g', ctx=Load())
psj                                      # Name(id='psj', ctx=Del())

<Call>
{{set(): set()}(+set())}(m7K, (), u=[set() // set()]) # Call(func=Set(elts=[Call(func=Dict(keys=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), args=[UnaryOp(op=UAdd(), operand=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], keywords=[])]), args=[Name(id='m7K', ctx=Del()), Tuple(elts=[], ctx=Load())], keywords=[keyword(arg='u', value=List(elts=[BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=FloorDiv(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], ctx=Load()))])
(())(set(), None, 
U, j=False, i=set())  # Call(func=Compare(left=BoolOp(op=Or(), values=[]), ops=[], comparators=[BoolOp(op=And(), values=[])]), args=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Constant(value=None), Expr(value=Name(id='U', ctx=Load()))], keywords=[keyword(arg='j', value=Constant(value=False)), keyword(arg='i', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
set(), set(), set(), (set(),), T=set(), L=set(), y=set()) # Call(func=List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del()), args=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del())], keywords=[keyword(arg='T', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), keyword(arg='L', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), keyword(arg='y', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
(set() - set() ** set() % (set() @ set()))(set() * set(), set() << set(), W=set() / set()) # Call(func=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Sub(), right=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Pow(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Mod(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=MatMult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))), args=[BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=LShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], keywords=[keyword(arg='W', value=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Div(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))])
(set() >> set())((set() | set()) ^ set(), g=set() & set(), B=set() + set()) # Call(func=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=RShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), args=[BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitOr(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=BitXor(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], keywords=[keyword(arg='g', value=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitAnd(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), keyword(arg='B', value=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))])
''(-(not 48.9), Q=~70, FmD=True, h=set()) # Call(func=Constant(value=''), args=[UnaryOp(op=USub(), operand=UnaryOp(op=Not(), operand=Constant(value=48.9)))], keywords=[keyword(arg='Q', value=UnaryOp(op=Invert(), operand=Constant(value=70))), keyword(arg='FmD', value=Constant(value=True)), keyword(arg='h', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
((set() in set()) > set())(None, v=set()) # Call(func=Compare(left=Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[In()], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), ops=[Gt()], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), args=[Compare(left=Constant(value=None), ops=[], comparators=[])], keywords=[keyword(arg='v', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
''(set(), V, l, t, _, zM=H)              # Call(func=Constant(value=""), args=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Name(id='V', ctx=Load()), Name(id='l', ctx=Load()), Name(id='t', ctx=Del()), Name(id='_', ctx=Load())], keywords=[keyword(arg='zM', value=Name(id='H', ctx=Load()))])
xTzqJe5gQ(n80d, qkw=b)                   # Call(func=Name(id='xTzqJe5gQ', ctx=Del()), args=[Name(id='n80d', ctx=Load())], keywords=[keyword(arg='qkw', value=Name(id='b', ctx=Del()))])
k(set(), set(), set(), E, o=c)           # Call(func=Name(id='k', ctx=Load()), args=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Name(id='E', ctx=Load())], keywords=[keyword(arg='o', value=Name(id='c', ctx=Load()))])

ast_ids_grammar = convert_ebnf_grammar(PYTHON_AST_IDS_GRAMMAR) 
id_solver = ISLaSolver(ast_ids_grammar, start_symbol='<id>')
assert id_solver.check('open') 
name_solver = ISLaSolver(ast_ids_grammar)
assert name_solver.check("Name(id='open', ctx=Load())") 
call_solver = ISLaSolver(ast_ids_grammar, start_symbol='<keyword_list>')
assert call_solver.check('[]') 
call_str = ast.dump(ast.parse('open("foo.txt", "r")').body[0].value)
print(call_str)
call_solver = ISLaSolver(ast_ids_grammar)
assert call_solver.check(call_str) 
Call(func=Name(id='open', ctx=Load()), args=[Constant(value='foo.txt'), Constant(value='r')], keywords=[])

```</details> <details id="Excursion:-Attributes-and-Subscripts"><summary>属性和下标</summary>

让我们添加属性和下标。

```py
print(ast.dump(ast.parse("a[b].c"), indent=4)) 
Module(
    body=[
        Expr(
            value=Attribute(
                value=Subscript(
                    value=Name(id='a', ctx=Load()),
                    slice=Name(id='b', ctx=Load()),
                    ctx=Load()),
                attr='c',
                ctx=Load()))],
    type_ignores=[])

PYTHON_AST_ATTRS_GRAMMAR: Grammar = extend_grammar(PYTHON_AST_IDS_GRAMMAR, {
    '<expr>': PYTHON_AST_IDS_GRAMMAR['<expr>'] + [
        '<Attribute>', '<Subscript>', '<Starred>',
    ],

    # Attributes
    '<Attribute>': [
        'Attribute(value=<expr>, attr=<identifier>, ctx=Load())',
        'Attribute(value=<expr>, attr=<identifier>, ctx=Del())',
    ],

    # Subscripts
    '<Subscript>': [
        'Subscript(value=<expr>, slice=<Slice>, ctx=Load())',
        'Subscript(value=<expr>, slice=<Slice>, ctx=Del())',
    ],
    '<Slice>': [
        'Slice()',
        'Slice(<expr>)',
        'Slice(<expr>, <expr>)',
        'Slice(<expr>, <expr>, <expr>)',
    ],

    # Starred
    '<Starred>': [
        'Starred(value=<expr>, ctx=Load())',
        'Starred(value=<expr>, ctx=Del())',
    ],

    # We're extending the set of callers a bit
    '<func>': [ '<Name>', '<Attribute>', '<Subscript>' ],
}) 
assert is_valid_grammar(PYTHON_AST_ATTRS_GRAMMAR) 
for elt in [ '<Attribute>', '<Subscript>', '<Starred>' ]:
    print(elt)
    test_samples(PYTHON_AST_ATTRS_GRAMMAR, start_symbol=elt)
    print() 
<Attribute>
{}.zZ                                    # Attribute(value=Dict(keys=[BoolOp(op=Or(), values=[Expr(value=UnaryOp(op=UAdd(), operand=Call(func=Name(id='e', ctx=Del()), args=[], keywords=[]))), BinOp(left=Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), op=Sub(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))]), Starred(value=Attribute(value=Tuple(elts=[Set(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del())])], ctx=Load()), attr='HV', ctx=Load()), ctx=Del())], values=[]), attr='zZ', ctx=Del())
OON6Q9X8m1yqSkYJtGPI_bADfjMTaIhp._Rr5dHs2n7UwzFoLulcei3KCgW4EvxB60jmPP # Attribute(value=Name(id='OON6Q9X8m1yqSkYJtGPI_bADfjMTaIhp', ctx=Load()), attr='_Rr5dHs2n7UwzFoLulcei3KCgW4EvxB60jmPP', ctx=Del())
175 .M                                   # Attribute(value=Constant(value=175), attr='M', ctx=Del())
*[set() * set() + set() / set()][(set() << set(), set() % set(), set() ** (set() & set())):].Wn # Attribute(value=Starred(value=Subscript(value=List(elts=[BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Add(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Div(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))], ctx=Load()), slice=Slice(Tuple(elts=[BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=LShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mod(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Pow(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitAnd(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))], ctx=Del())), ctx=Load()), ctx=Load()), attr='Wn', ctx=Del())
((-+set()[:]()[set():set():set()] | (not ~set().E())) @ '' // (None ^ False)).B # Attribute(value=BinOp(left=BinOp(left=BinOp(left=UnaryOp(op=USub(), operand=UnaryOp(op=UAdd(), operand=Subscript(value=Call(func=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), args=[], keywords=[]), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Del()))), op=BitOr(), right=UnaryOp(op=Not(), operand=UnaryOp(op=Invert(), operand=Call(func=Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='E', ctx=Del()), args=[], keywords=[])))), op=MatMult(), right=Constant(value='')), op=FloorDiv(), right=BinOp(left=Constant(value=None), op=BitXor(), right=Constant(value=False))), attr='B', ctx=Load())
((99.8) >> (True)['HAVsYE|,]@bXz!hguQimRwL0)2=W-8PteTK<{c~*3}f$OandqF1%&4IJ"MjZ>^k`pv;/U_?B.7[+y#(G 9S5CDoNrlx:6Z':'S']).yM # Attribute(value=BinOp(left=BoolOp(op=And(), values=[Constant(value=99.8)]), op=RShift(), right=Subscript(value=BoolOp(op=And(), values=[Constant(value=True)]), slice=Slice(Constant(value='HAVsYE|,]@bXz!hguQimRwL0)2=W-8PteTK<{c~*3}f$OandqF1%&4IJ"MjZ>^k`pv;/U_?B.7[+y#(G 9S5CDoNrlx:6Z'), Constant(value="S")), ctx=Del())), attr='yM', ctx=Del())
(((set()) < set() == set()) not in set()).l # Attribute(value=Compare(left=Compare(left=Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), ops=[Lt(), Eq()], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), ops=[NotIn()], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), Name(id='c', ctx=Del())]), attr='l', ctx=Del())
vlL.CZ                                   # Attribute(value=Name(id='vlL', ctx=Load()), attr='CZ', ctx=Del())
w.nyuCk                                  # Attribute(value=Name(id='w', ctx=Del()), attr='nyuCk', ctx=Load())
Js.Za                                    # Attribute(value=Name(id='Js', ctx=Load()), attr='Za', ctx=Load())

<Subscript>
{279.0 >> [], -*set()[:]:, set(), ())}[{}:] # Subscript(value=Set(elts=[BinOp(left=Constant(value=279.0), op=RShift(), right=List(elts=[BoolOp(op=And(), values=[])], ctx=Del())), UnaryOp(op=USub(), operand=Call(func=Subscript(value=Subscript(value=Starred(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ctx=Load()), slice=Slice(), ctx=Del()), slice=Slice(), ctx=Load()), args=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Tuple(elts=[], ctx=Load())], keywords=[]))]), slice=Slice(Dict(keys=[], values=[Name(id='U', ctx=Load())])), ctx=Del())
(set()).y[():b:
set()]                   # Subscript(value=Attribute(value=Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), attr='y', ctx=Load()), slice=Slice(Tuple(elts=[], ctx=Del()), Name(id='b', ctx=Del()), Expr(value=Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]))), ctx=Del())
(set() << set() - set()).c[[set() @ set() // set()]:*(set() & set()).z] # Subscript(value=Attribute(value=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=LShift(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Sub(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), attr='c', ctx=Load()), slice=Slice(List(elts=[BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=MatMult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=FloorDiv(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], ctx=Load()), Starred(value=Attribute(value=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitAnd(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), attr='z', ctx=Del()), ctx=Del())), ctx=Load())
((set() | set()) ^ set() ** set())[set() * set():(set() + set()) / set()] # Subscript(value=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitOr(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=BitXor(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Pow(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), slice=Slice(BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Div(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), ctx=Load())
None['t':]                               # Subscript(value=Constant(value=None), slice=Slice(Constant(value="t")), ctx=Load())
(not set().H(~set(), N=set()))[M():n():set()] # Subscript(value=UnaryOp(op=Not(), operand=Call(func=Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='H', ctx=Del()), args=[UnaryOp(op=Invert(), operand=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], keywords=[keyword(arg='N', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])), slice=Slice(Call(func=Name(id='M', ctx=Load()), args=[], keywords=[]), Call(func=Name(id='n', ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Del())
False[1632:]                             # Subscript(value=Constant(value=False), slice=Slice(Constant(value=1632)), ctx=Del())
(('') % +(94 or True))[True or ((t)) is set() <= Q:] # Subscript(value=BinOp(left=BoolOp(op=Or(), values=[Constant(value='')]), op=Mod(), right=UnaryOp(op=UAdd(), operand=BoolOp(op=Or(), values=[Constant(value=94), Constant(value=True)]))), slice=Slice(BoolOp(op=Or(), values=[Constant(value=True), Compare(left=Compare(left=Compare(left=Name(id='t', ctx=Load()), ops=[], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), ops=[], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), ops=[Is(), LtE()], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Name(id='Q', ctx=Load()), Name(id='r', ctx=Del())])])), ctx=Load())
l7oUAETh5yCvxmRcgJ8[vtk3XeH:midn6Wa4]    # Subscript(value=Name(id='l7oUAETh5yCvxmRcgJ8', ctx=Load()), slice=Slice(Name(id='vtk3XeH', ctx=Load()), Name(id='midn6Wa4', ctx=Load())), ctx=Load())
JN0GQSzfYw1MLI2up6[gD9VZbsK_lqjrPOFB:]   # Subscript(value=Name(id='JN0GQSzfYw1MLI2up6', ctx=Load()), slice=Slice(Name(id='gD9VZbsK_lqjrPOFB', ctx=Del())), ctx=Load())

<Starred>
*[]                                      # Starred(value=List(elts=[], ctx=Del()), ctx=Load())
*(
{{set().j(K.J, Q=set()): (+*(set())[set():set():set()],)}, 440.7}) >> i # Starred(value=BinOp(left=BoolOp(op=And(), values=[Expr(value=Set(elts=[Dict(keys=[Call(func=Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='j', ctx=Del()), args=[Attribute(value=Name(id='K', ctx=Del()), attr='J', ctx=Load())], keywords=[keyword(arg='Q', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])], values=[Tuple(elts=[UnaryOp(op=UAdd(), operand=Starred(value=Subscript(value=Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Load()), ctx=Load()))], ctx=Del())]), Constant(value=440.7)]))]), op=RShift(), right=Name(id='i', ctx=Load())), ctx=Del())
*[set(), set(), set() @ set()][(set(), set() - set(), set() % set() / set() ** set()):] # Starred(value=Subscript(value=List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=MatMult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], ctx=Load()), slice=Slice(Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Sub(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mod(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Div(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Pow(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))], ctx=Load())), ctx=Del()), ctx=Load())
*(set() ^ set()) & (set() << set()) + set() | set() * set() # Starred(value=BinOp(left=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitXor(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=BitAnd(), right=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=LShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), op=BitOr(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), ctx=Del())
*'g'                                     # Starred(value=Constant(value='g'), ctx=Load())
*-None                                   # Starred(value=UnaryOp(op=USub(), operand=Constant(value=None)), ctx=Del())
*9523:, -set(), not ~set(), (not not set())[-(set() // set()):]) # Starred(value=Call(func=Subscript(value=Constant(value=9523), slice=Slice(), ctx=Del()), args=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), UnaryOp(op=USub(), operand=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), UnaryOp(op=Not(), operand=UnaryOp(op=Invert(), operand=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), Subscript(value=UnaryOp(op=Not(), operand=UnaryOp(op=Not(), operand=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), slice=Slice(UnaryOp(op=USub(), operand=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=FloorDiv(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), BoolOp(op=Or(), values=[])), ctx=Load())], keywords=[]), ctx=Del())
*False                                   # Starred(value=Constant(value=False), ctx=Load())
*X(Y(q=set()), I=U(), D=set())           # Starred(value=Call(func=Name(id='X', ctx=Load()), args=[Call(func=Name(id='Y', ctx=Load()), args=[], keywords=[keyword(arg='q', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])], keywords=[keyword(arg='I', value=Call(func=Name(id='U', ctx=Load()), args=[], keywords=[])), keyword(arg='D', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))]), ctx=Del())
*'#'                                     # Starred(value=Constant(value="#"), ctx=Del())

```</details> <details id="Excursion:-Variable-Assignments"><summary>变量赋值</summary>

现在是变量赋值的时候了。这些使事情变得更加复杂,因为我们有一个限制性的表达式集合在赋值的左侧。

```py
PYTHON_AST_ASSIGNMENTS_GRAMMAR: Grammar = extend_grammar(PYTHON_AST_ATTRS_GRAMMAR, {
    '<start>': [ '<stmt>' ],

    '<stmt>': [
        '<Assign>', '<AugAssign>',
        '<Expr>'
    ],

    # Assignments
    '<Assign>': [
        'Assign(targets=<nonempty_lhs_expr_list>, value=<expr><type_comment>?)',
    ],
    '<type_comment>': [ ', type_comment=<string>' ],
    '<AugAssign>': [
        'AugAssign(target=<lhs_expr>, op=<operator>, value=<expr>)',
    ],

    # Lists of left-hand side expressions
    # '<lhs_expr_list>': [ '[<lhs_exprs>?]' ],
    '<nonempty_lhs_expr_list>': [ '[<lhs_exprs>]' ],
    '<lhs_exprs>': [ '<lhs_expr>', '<lhs_exprs>, <lhs_expr>' ],

    # On the left-hand side of assignments, we allow a number of structures
    '<lhs_expr>': [
        '<lhs_Name>',  # Most common
        '<lhs_List>', '<lhs_Tuple>',
        '<lhs_Attribute>',
        '<lhs_Subscript>',
        '<lhs_Starred>',
    ],

    '<lhs_Name>': [ 'Name(id=<identifier>, ctx=Store())', ],

    '<lhs_List>': [
        'List(elts=<nonempty_lhs_expr_list>, ctx=Store())',
    ],
    '<lhs_Tuple>': [
        'Tuple(elts=<nonempty_lhs_expr_list>, ctx=Store())',
    ],
    '<lhs_Attribute>': [
        'Attribute(value=<lhs_expr>, attr=<identifier>, ctx=Store())',
    ],
    '<lhs_Subscript>': [
        'Subscript(value=<lhs_expr>, slice=<Slice>, ctx=Store())',
    ],
    '<lhs_Starred>': [
        'Starred(value=<lhs_expr>, ctx=Store())',
    ],
}) 
assert is_valid_grammar(PYTHON_AST_ASSIGNMENTS_GRAMMAR) 
for elt in ['<Assign>', '<AugAssign>']:
    print(elt)
    test_samples(PYTHON_AST_ASSIGNMENTS_GRAMMAR, start_symbol=elt)
    print() 
<Assign>
*[(r,), (V, C[set():set()]), Z[set():].WDY3i] = () # type: * # Assign(targets=[Starred(value=List(elts=[Tuple(elts=[Name(id='r', ctx=Store())], ctx=Store()), Tuple(elts=[Name(id='V', ctx=Store()), Subscript(value=Name(id='C', ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store())], ctx=Store()), Attribute(value=Subscript(value=Name(id='Z', ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store()), attr='WDY3i', ctx=Store())], ctx=Store()), ctx=Store())], value=Tuple(elts=[], ctx=Load()), type_comment='*')
h[set():set():set()][set():*set():set()[:]()][:] = [set()].Yzt # Assign(targets=[Subscript(value=Subscript(value=Subscript(value=Name(id='h', ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Starred(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ctx=Load()), Call(func=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Load()), args=[], keywords=[])), ctx=Store()), slice=Slice(), ctx=Store())], value=Attribute(value=List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del()), attr='Yzt', ctx=Load()))
N[:][:][set():set():set()][{}:][:] = 
ExcXjv1h # type: R # Assign(targets=[Subscript(value=Subscript(value=Subscript(value=Subscript(value=Subscript(value=Name(id='N', ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store()), slice=Slice(Dict(keys=[], values=[])), ctx=Store()), slice=Slice(BoolOp(op=Or(), values=[])), ctx=Store())], value=Expr(value=Name(id='ExcXjv1h', ctx=Del())), type_comment="R")
H[:][:][set():set():set()] = -set() # type: y{ # Assign(targets=[Subscript(value=Subscript(value=Subscript(value=Name(id='H', ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store())], value=UnaryOp(op=USub(), operand=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), type_comment='y{')
K[:][:] = a[:][set():set()] = False # type: USsF # Assign(targets=[Subscript(value=Subscript(value=Name(id='K', ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(), ctx=Store()), Subscript(value=Subscript(value=Name(id='a', ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store())], value=Constant(value=False), type_comment="USsF")
B[set():set()] = set()[:] << (set()[:]) # type: K # Assign(targets=[Subscript(value=Name(id='B', ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store())], value=BinOp(left=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), op=LShift(), right=Compare(left=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), ops=[], comparators=[])), type_comment='K')
sKC = fm = (*set().y, *{set()}) # type: L^}3QF # Assign(targets=[Name(id='sKC', ctx=Store()), Name(id='fm', ctx=Store())], value=Tuple(elts=[Starred(value=Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='y', ctx=Del()), ctx=Del()), Starred(value=Set(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), ctx=Load())], ctx=Del()), type_comment='L^}3QF')
S = n = I = [set(), set(), F] # type: 8-h # Assign(targets=[Name(id='S', ctx=Store()), Name(id='n', ctx=Store()), Name(id='I', ctx=Store())], value=List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Name(id='F', ctx=Load())], ctx=Load()), type_comment="8-h")
gy = set() % set() @ set() - (set() & set()) # type: .~ # Assign(targets=[Name(id='gy', ctx=Store())], value=BinOp(left=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mod(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=MatMult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Sub(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitAnd(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), type_comment=".~")
cnoOWRu = set() * (set() >> set() ^ set() + set()) # type: ['Ox# # Assign(targets=[Name(id='cnoOWRu', ctx=Store())], value=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mult(), right=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=RShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=BitXor(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))), type_comment="['Ox#")

<AugAssign>
K <<= set()                              # AugAssign(target=Name(id='K', ctx=Store()), op=LShift(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))
(_, [A]).H ^= (*{}.a[set() | set():set():], False) # AugAssign(target=Attribute(value=Tuple(elts=[Name(id='_', ctx=Store()), List(elts=[Name(id='A', ctx=Store())], ctx=Store())], ctx=Store()), attr='H', ctx=Store()), op=BitXor(), value=Tuple(elts=[Subscript(value=Attribute(value=Starred(value=Dict(keys=[], values=[]), ctx=Del()), attr='a', ctx=Del()), slice=Slice(BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitOr(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), BoolOp(op=Or(), values=[])), ctx=Load()), Constant(value=False)], ctx=Load()))
*i[:][:][y():set()] -= [~(
set())]       # AugAssign(target=Subscript(value=Starred(value=Subscript(value=Subscript(value=Name(id='i', ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(), ctx=Store()), ctx=Store()), slice=Slice(Call(func=Name(id='y', ctx=Del()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store()), op=Sub(), value=List(elts=[UnaryOp(op=Invert(), operand=Compare(left=Expr(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ops=[], comparators=[]))], ctx=Load()))
t3lmH[(set(), set()):] //= oxNerA8       # AugAssign(target=Subscript(value=Name(id='t3lmH', ctx=Store()), slice=Slice(Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del())), ctx=Store()), op=FloorDiv(), value=Name(id='oxNerA8', ctx=Load()))
pdnk2WaQFLs @= {*[set()].Qc[set().x:]}   # AugAssign(target=Name(id='pdnk2WaQFLs', ctx=Store()), op=MatMult(), value=Set(elts=[Subscript(value=Starred(value=Attribute(value=List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del()), attr='Qc', ctx=Load()), ctx=Load()), slice=Slice(Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='x', ctx=Load())), ctx=Del())]))
YMy **= (set() + (set() & set())) / (None % (set() >> set())) # AugAssign(target=Name(id='YMy', ctx=Store()), op=Pow(), value=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitAnd(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), op=Div(), right=BinOp(left=Constant(value=None), op=Mod(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=RShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))))
rXvE0_VP7puUYSIJwg4qDZt9z6RjiChKGTofbBO15 *= +'h' # AugAssign(target=Name(id='rXvE0_VP7puUYSIJwg4qDZt9z6RjiChKGTofbBO15', ctx=Store()), op=Mult(), value=UnaryOp(op=UAdd(), operand=Constant(value="h")))
PFUN += not Trueset(): # AugAssign(target=Name(id='PFUN', ctx=Store()), op=Add(), value=UnaryOp(op=Not(), operand=Call(func=Subscript(value=Constant(value=True), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Del()), args=[Constant(value=991.2)], keywords=[keyword(arg='J', value=Constant(value=None)), keyword(arg='k', value=Constant(value=False))])))
g ^= (-set()).m(set(), , u=-set(), h=set()) # AugAssign(target=Name(id='g', ctx=Store()), op=BitXor(), value=Call(func=Attribute(value=UnaryOp(op=USub(), operand=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), attr='m', ctx=Load()), args=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), BoolOp(op=And(), values=[])], keywords=[keyword(arg='u', value=UnaryOp(op=USub(), operand=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), keyword(arg='h', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))]))
Ce |= 448                                # AugAssign(target=Name(id='Ce', ctx=Store()), op=BitOr(), value=Constant(value=448))

```</details> <details id="Excursion:-Statements"><summary>语句</summary>

现在是语句。这里有很多。

```py
PYTHON_AST_STMTS_GRAMMAR: Grammar = extend_grammar(PYTHON_AST_ASSIGNMENTS_GRAMMAR, {
    '<start>': [ '<stmt>' ],

    '<stmt>': PYTHON_AST_ASSIGNMENTS_GRAMMAR['<stmt>'] + [
        '<For>', '<While>', '<If>',
        '<Return>', '<Delete>', '<Assert>',
        '<Pass>', '<Break>', '<Continue>',
        '<With>'
    ],

    # Control structures
    '<For>': [
        'For(target=<lhs_expr>, iter=<expr>, body=<nonempty_stmt_list>, orelse=<stmt_list><type_comment>)'
    ],
    '<stmt_list>': [ '[<stmts>?]' ],
    '<nonempty_stmt_list>': [ '[<stmts>]' ],
    '<stmts>': [ '<stmt>', '<stmt>, <stmts>' ],

    '<While>': [
        'While(test=<expr>, body=<nonempty_stmt_list>, orelse=<stmt_list>)'
    ],

    '<If>': [
        'If(test=<expr>, body=<nonempty_stmt_list><orelse_param>)'
    ],
    '<orelse_param>': [
        ', orelse=<stmt_list>'
    ],

    '<With>': [
        'With(items=<withitem_list>, body=<nonempty_stmt_list><type_comment>?)'
    ],
    '<withitem_list>': [ '[<withitems>?]' ],
    '<withitems>': [ '<withitem>', '<withitems>, <withitem>' ],
    '<withitem>': [
        'withitem(context_expr=<expr>)',
        'withitem(context_expr=<expr>, optional_vars=<lhs_expr>)',
    ],

    # Other statements
    '<Return>': [
        'Return()',
        'Return(value=<expr>)'
    ],
    '<Delete>': [
        'Delete(targets=<expr_list>)'
    ],
    '<Assert>': [
        'Assert(test=<expr>)',
        'Assert(test=<expr>, msg=<expr>)'
    ],
    '<Pass>': [ 'Pass()'],
    '<Break>': [ 'Break()' ],
    '<Continue>': [ 'Continue()']

    # FIXME: A few more: AsyncFor, AsyncWith, Match, Try, TryStar
    # Import, ImportFrom, Global, Nonlocal...
}) 
# do import this unconditionally
if sys.version_info >= (3, 13):
    PYTHON_AST_STMTS_GRAMMAR: Grammar = \
        extend_grammar(PYTHON_AST_STMTS_GRAMMAR, {
        # As of 3.13, orelse is optional
        '<If>': [
            'If(test=<expr>, body=<nonempty_stmt_list><orelse_param>?)'
        ],
    }) 
assert is_valid_grammar(PYTHON_AST_STMTS_GRAMMAR) 
for elt in PYTHON_AST_STMTS_GRAMMAR['<stmt>']:
    print(elt)
    test_samples(PYTHON_AST_STMTS_GRAMMAR, start_symbol=elt)
    print() 
<Assign>
*[v[:][:][:]][{}:+*set()[:]()] = (XDBoW_Av,).L4 = (32.6,) # type:  # Assign(targets=[Starred(value=Subscript(value=List(elts=[Subscript(value=Subscript(value=Subscript(value=Name(id='v', ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(), ctx=Store())], ctx=Store()), slice=Slice(Dict(keys=[], values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), UnaryOp(op=UAdd(), operand=Starred(value=Call(func=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Load()), args=[], keywords=[]), ctx=Load()))), ctx=Store()), ctx=Store()), Attribute(value=Tuple(elts=[Name(id='XDBoW_Av', ctx=Store())], ctx=Store()), attr='L4', ctx=Store())], value=Tuple(elts=[Constant(value=32.6)], ctx=Load()), type_comment="")
g[:][set():][[]::set()] = set()[:]       # Assign(targets=[Subscript(value=Subscript(value=Subscript(value=Name(id='g', ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store()), slice=Slice(List(elts=[], ctx=Del()), BoolOp(op=And(), values=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store())], value=Compare(left=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), ops=[], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]))
W[:] = y[:][set():set():set()] = 
K18E # type: N # Assign(targets=[Subscript(value=Name(id='W', ctx=Store()), slice=Slice(), ctx=Store()), Subscript(value=Subscript(value=Name(id='y', ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store())], value=Expr(value=Name(id='K18E', ctx=Load())), type_comment='N')
V = _ = (set() | set()).E # type: i0     # Assign(targets=[Name(id='V', ctx=Store()), Name(id='_', ctx=Store())], value=Attribute(value=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitOr(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), attr='E', ctx=Del()), type_comment="i0")
cZIujm3gC = eMePLrNVy9z2 # type: Wd~OC6+v02ey # Assign(targets=[Name(id='cZIujm3gC', ctx=Store())], value=Name(id='eMePLrNVy9z2', ctx=Del()), type_comment='Wd~OC6+v02ey')
Yf0lcOSaT = *[{set()}.b, (set(), set())] # type: *H<u&~|  # Assign(targets=[Name(id='Yf0lcOSaT', ctx=Store())], value=Starred(value=List(elts=[Attribute(value=Set(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), attr='b', ctx=Load()), Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del())], ctx=Load()), ctx=Del()), type_comment="*H<u&~| ")
N = i = ((set() ^ set()) & set()) * (set() + set()) # type: +ps # Assign(targets=[Name(id='N', ctx=Store()), Name(id='i', ctx=Store())], value=BinOp(left=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitXor(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=BitAnd(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Mult(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), type_comment="+ps")
m = P = set() @ set() << set() // set() # type: ]J # Assign(targets=[Name(id='m', ctx=Store()), Name(id='P', ctx=Store())], value=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=MatMult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=LShift(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=FloorDiv(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), type_comment=']J')
j = O = 18 % (set() / set()) # type: R?$6 # Assign(targets=[Name(id='j', ctx=Store()), Name(id='O', ctx=Store())], value=BinOp(left=Constant(value=18), op=Mod(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Div(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), type_comment="R?$6")
TGFJsqKdXkwb65xYnpHRh7UtQi4u = False # type: q(Q>GHPBsa!|bUV9&$w`Su.8-hAi3}7)#=LDx@5"?Kgjkz,pt_r%XT1m/f{c*;^ZlIE: YRnoM4[F< # Assign(targets=[Name(id='TGFJsqKdXkwb65xYnpHRh7UtQi4u', ctx=Store())], value=Constant(value=False), type_comment='q(Q>GHPBsa!|bUV9&$w`Su.8-hAi3}7)#=LDx@5"?Kgjkz,pt_r%XT1m/f{c*;^ZlIE: YRnoM4[F<')

<AugAssign>
(*krT_.qL2x,)[~[
set(), None, {}[:]].L:] //= (,) # AugAssign(target=Subscript(value=Tuple(elts=[Attribute(value=Starred(value=Name(id='krT_', ctx=Store()), ctx=Store()), attr='qL2x', ctx=Store())], ctx=Store()), slice=Slice(UnaryOp(op=Invert(), operand=Attribute(value=List(elts=[Expr(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Constant(value=None), Subscript(value=Dict(keys=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], values=[]), slice=Slice(), ctx=Del())], ctx=Load()), attr='L', ctx=Load()))), ctx=Store()), op=FloorDiv(), value=Tuple(elts=[BoolOp(op=And(), values=[])], ctx=Del()))
[h[:], F[:], l[:][set():Z]] -= U(*set() | set()) # AugAssign(target=List(elts=[Subscript(value=Name(id='h', ctx=Store()), slice=Slice(), ctx=Store()), Subscript(value=Name(id='F', ctx=Store()), slice=Slice(), ctx=Store()), Subscript(value=Subscript(value=Name(id='l', ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), Name(id='Z', ctx=Load())), ctx=Store())], ctx=Store()), op=Sub(), value=Call(func=Name(id='U', ctx=Del()), args=[BinOp(left=Starred(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ctx=Del()), op=BitOr(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], keywords=[]))
Q[[]:set()][[]:set()[:]:set()[:]] &= *(set(),).b # AugAssign(target=Subscript(value=Subscript(value=Name(id='Q', ctx=Store()), slice=Slice(List(elts=[], ctx=Del()), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store()), slice=Slice(List(elts=[], ctx=Del()), Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Load()), Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Load())), ctx=Store()), op=BitAnd(), value=Starred(value=Attribute(value=Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Load()), attr='b', ctx=Del()), ctx=Load()))
wnBzQMG <<= {set() @ set() ^ set() ** set() / set()} # AugAssign(target=Name(id='wnBzQMG', ctx=Store()), op=LShift(), value=Set(elts=[BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=MatMult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=BitXor(), right=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Pow(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Div(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))]))
Psdhpk1YVICRcN0J4wDPjqZmE856iFUbKf9oASWlXgvtyH7Oeua3Lyt6 *= 48.5 # AugAssign(target=Name(id='Psdhpk1YVICRcN0J4wDPjqZmE856iFUbKf9oASWlXgvtyH7Oeua3Lyt6', ctx=Store()), op=Mult(), value=Constant(value=48.5))
a %= ''                                  # AugAssign(target=Name(id='a', ctx=Store()), op=Mod(), value=Constant(value=""))
J += -True                               # AugAssign(target=Name(id='J', ctx=Store()), op=Add(), value=UnaryOp(op=USub(), operand=Constant(value=True)))
oU >>= set()set():set():set(), H=set()) # AugAssign(target=Name(id='oU', ctx=Store()), op=RShift(), value=Call(func=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Del()), args=[], keywords=[keyword(arg='E', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), keyword(arg='H', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))]))
N //= not +(not 7927416330)              # AugAssign(target=Name(id='N', ctx=Store()), op=FloorDiv(), value=UnaryOp(op=Not(), operand=UnaryOp(op=UAdd(), operand=UnaryOp(op=Not(), operand=Constant(value=7927416330)))))
s += '' or False or 8888 .W((set()), v=set(), Y=set()) # AugAssign(target=Name(id='s', ctx=Store()), op=Add(), value=BoolOp(op=Or(), values=[Constant(value=''), Constant(value=False), Call(func=Attribute(value=Constant(value=8888), attr='W', ctx=Load()), args=[Compare(left=Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), ops=[], comparators=[])], keywords=[keyword(arg='v', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), keyword(arg='Y', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])]))

<Expr>

*{(None, n, [] << +R()[{}.r::set().I])} # Expr(value=Expr(value=Starred(value=Set(elts=[Tuple(elts=[Constant(value=None), Name(id='n', ctx=Load()), BinOp(left=List(elts=[], ctx=Del()), op=LShift(), right=UnaryOp(op=UAdd(), operand=Subscript(value=Call(func=Name(id='R', ctx=Del()), args=[], keywords=[]), slice=Slice(Attribute(value=Dict(keys=[], values=[]), attr='r', ctx=Del()), BoolOp(op=Or(), values=[]), Compare(left=Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='I', ctx=Load()), ops=[], comparators=[])), ctx=Del())))], ctx=Load())]), ctx=Del())))
(*((set() ^ set()) - (set() | set())) / (set() % set()), [set(), set() >> set(), set() // set(), set() + set()])[:] # Expr(value=Subscript(value=Tuple(elts=[Starred(value=BinOp(left=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitXor(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Sub(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitOr(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), op=Div(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mod(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), ctx=Load()), List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=RShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=FloorDiv(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], ctx=Load())], ctx=Del()), slice=Slice(), ctx=Load()))
851648.62 * True & 0                     # Expr(value=BinOp(left=BinOp(left=Constant(value=851648.62), op=Mult(), right=Constant(value=True)), op=BitAnd(), right=Constant(value=0)))
not -((set() @ set()) ** set()[:]())[set().z(_=set()):] # Expr(value=UnaryOp(op=Not(), operand=UnaryOp(op=USub(), operand=Subscript(value=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=MatMult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Pow(), right=Call(func=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Load()), args=[], keywords=[])), slice=Slice(Call(func=Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='z', ctx=Del()), args=[], keywords=[keyword(arg='_', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])), ctx=Load()))))
'N'                                      # Expr(value=BoolOp(op=And(), values=[Constant(value="N")]))
(not ~'f')[~False:+(17 == ZAEPSYo_lKJHf6my8xTR2wg9b3d71qBeC5Mj6)] # Expr(value=Subscript(value=UnaryOp(op=Not(), operand=UnaryOp(op=Invert(), operand=Constant(value='f'))), slice=Slice(UnaryOp(op=Invert(), operand=Constant(value=False)), UnaryOp(op=UAdd(), operand=Compare(left=Constant(value=17), ops=[Eq()], comparators=[Name(id='ZAEPSYo_lKJHf6my8xTR2wg9b3d71qBeC5Mj6', ctx=Load()), Name(id='FcVkWZ0hQsONnpzGLrXut4vFIDiUBa', ctx=Load())]))), ctx=Del()))
i                                        # Expr(value=Name(id='i', ctx=Load()))
o2                                       # Expr(value=Name(id='o2', ctx=Del()))
AR                                       # Expr(value=Name(id='AR', ctx=Load()))
Y                                        # Expr(value=Name(id='Y', ctx=Del()))

<For>
for U, [D, I] in []: # type: j
    set()
    m /= set() # For(target=Tuple(elts=[Name(id='U', ctx=Store()), List(elts=[Name(id='D', ctx=Store()), Name(id='I', ctx=Store())], ctx=Store())], ctx=Store()), iter=Compare(left=List(elts=[], ctx=Load()), ops=[Eq()], comparators=[]), body=[Expr(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), AugAssign(target=Name(id='m', ctx=Store()), op=Div(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], orelse=[], type_comment="j")
for *O.s in {}: # type: }
    with :
        break
    assert set()
else:
    pass
    return # For(target=Starred(value=Attribute(value=Name(id='O', ctx=Store()), attr='s', ctx=Store()), ctx=Store()), iter=Dict(keys=[], values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), body=[With(items=[], body=[Break()]), Assert(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], orelse=[Pass(), Return()], type_comment='}')
for q[:][set():set():set()] in *set(): # type: 
    return
    return
else:
    continue
    continue # For(target=Subscript(value=Subscript(value=Name(id='q', ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store()), iter=Starred(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ctx=Load()), body=[Return(), Return()], orelse=[Continue(), Continue()], type_comment="")
for g[:][set():] in 
set(): # type: 
    return
else:
    l = set()
    return # For(target=Subscript(value=Subscript(value=Name(id='g', ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store()), iter=Expr(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), body=[Return()], orelse=[Assign(targets=[Name(id='l', ctx=Store())], value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Return()], type_comment="")
for v in set().F(): # type: 
    if set():
        return
    return # For(target=Name(id='v', ctx=Store()), iter=Call(func=Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='F', ctx=Load()), args=[], keywords=[]), body=[If(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), body=[Return()], orelse=[]), Return()], orelse=[], type_comment="")
for Z[:] in +set(): # type: 
    del 
    return
else:
    while set():
        return # For(target=Subscript(value=Name(id='Z', ctx=Store()), slice=Slice(), ctx=Store()), iter=UnaryOp(op=UAdd(), operand=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), body=[Delete(targets=[]), Return()], orelse=[While(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), body=[Return()], orelse=[])], type_comment="")
for z[set():set()] in (): # type: L
    for o in set(): # type: 
        return
else:
    return
    return # For(target=Subscript(value=Name(id='z', ctx=Store()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Store()), iter=Tuple(elts=[], ctx=Load()), body=[For(target=Name(id='o', ctx=Store()), iter=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), body=[Return()], orelse=[], type_comment='')], orelse=[Return(), Return()], type_comment="L")
for G[:] in True .KA: # type: 
    assert set(), set()
else:
    return # For(target=Subscript(value=Name(id='G', ctx=Store()), slice=Slice(), ctx=Store()), iter=Attribute(value=Constant(value=True), attr='KA', ctx=Del()), body=[Assert(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), msg=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], orelse=[Return()], type_comment='')
for b[:][set() ^ set():] in e[set():]: # type: #
    return
else:
    return # For(target=Subscript(value=Subscript(value=Name(id='b', ctx=Store()), slice=Slice(), ctx=Store()), slice=Slice(BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitXor(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BoolOp(op=And(), values=[])), ctx=Store()), iter=Subscript(value=Name(id='e', ctx=Load()), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Load()), body=[Return()], orelse=[Return()], type_comment='#')
for LNx in *wu: # type: ckM<v
    return k
else:
    return # For(target=Name(id='LNx', ctx=Store()), iter=Starred(value=Name(id='wu', ctx=Del()), ctx=Del()), body=[Return(value=Name(id='k', ctx=Del()))], orelse=[Return()], type_comment="ckM<v")

<While>
while 
k:
    pass                       # While(test=BoolOp(op=Or(), values=[Expr(value=Name(id='k', ctx=Load()))]), body=[Pass()], orelse=[])
while *set()[set().e:]:
    del 
    with :
        return
    return
    continue
else:
    break
    return # While(test=Subscript(value=Starred(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ctx=Load()), slice=Slice(Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='e', ctx=Load())), ctx=Load()), body=[Delete(targets=[]), With(items=[], body=[Return()]), Return(), Continue()], orelse=[Break(), Return()])
while {}:
    for H[:] in set(): # type: 
        return
    else:
        return
else:
    l |= set()
    while set():
        return # While(test=Dict(keys=[], values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), body=[For(target=Subscript(value=Name(id='H', ctx=Store()), slice=Slice(), ctx=Store()), iter=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), body=[Return()], orelse=[Return()], type_comment='')], orelse=[AugAssign(target=Name(id='l', ctx=Store()), op=BitOr(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), While(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), body=[Return()], orelse=[])])
while 'C':
    set()
    return
else:
    t = set()
    if set():
        return
    return
    return # While(test=Constant(value="C"), body=[Expr(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Return()], orelse=[Assign(targets=[Name(id='t', ctx=Store())], value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), If(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), body=[Return()], orelse=[]), Return(), Return()])
while (not set()) == set():
    assert set()
    return
else:
    assert set(), set()
    return # While(test=Compare(left=UnaryOp(op=Not(), operand=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ops=[Eq()], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), body=[Assert(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Return()], orelse=[Assert(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), msg=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Return()])
while () @ set():
    return [set(), set()]
    return
    return
    return
else:
    return set()[:]() # While(test=BinOp(left=Tuple(elts=[], ctx=Del()), op=MatMult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), body=[Return(value=List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del())), Return(), Return(), Return()], orelse=[Return(value=Call(func=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), args=[], keywords=[]))])
while *(set(),):
    (h,) //= X
    E <<= set()
else:
    P *= set().W # While(test=Starred(value=Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Load()), ctx=Del()), body=[AugAssign(target=Tuple(elts=[Name(id='h', ctx=Store())], ctx=Store()), op=FloorDiv(), value=Name(id='X', ctx=Del())), AugAssign(target=Name(id='E', ctx=Store()), op=LShift(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], orelse=[AugAssign(target=Name(id='P', ctx=Store()), op=Mult(), value=Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='W', ctx=Del()))])
while [{set() + set()}]:
    *[u] ^= {set() & set() >> set(), set() / set()}
else:
    s.N %= set()
    m **= set() # While(test=List(elts=[Set(elts=[BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])], ctx=Load()), body=[AugAssign(target=Starred(value=List(elts=[Name(id='u', ctx=Store())], ctx=Store()), ctx=Store()), op=BitXor(), value=Set(elts=[BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitAnd(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=RShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Div(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))]))], orelse=[AugAssign(target=Attribute(value=Name(id='s', ctx=Store()), attr='N', ctx=Store()), op=Mod(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), AugAssign(target=Name(id='m', ctx=Store()), op=Pow(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
while False:
    with set(), set(): # type: ^B
        x -= set()
else:
    v = +5 # type: % # While(test=Constant(value=False), body=[With(items=[withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], body=[AugAssign(target=Name(id='x', ctx=Store()), op=Sub(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], type_comment='^B')], orelse=[Assign(targets=[Name(id='v', ctx=Store())], value=UnaryOp(op=UAdd(), operand=Constant(value=5)), type_comment='%')])
while ~Y():
    T = set()
    return
    return
else:
    p = set()[:]() # While(test=UnaryOp(op=Invert(), operand=Call(func=Name(id='Y', ctx=Del()), args=[], keywords=[])), body=[Assign(targets=[Name(id='T', ctx=Store())], value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Return(), Return()], orelse=[Assign(targets=[Name(id='p', ctx=Store())], value=Call(func=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Load()), args=[], keywords=[]))])

<If>
if :
    return
    for [a] in set(): # type: 
        break
    pass
    continue
    return
else:
    del set()[:] # If(test=BoolOp(op=Or(), values=[]), body=[Return(), For(target=List(elts=[Name(id='a', ctx=Store())], ctx=Store()), iter=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), body=[Break()], orelse=[], type_comment=""), Pass(), Continue(), Return()], orelse=[Delete(targets=[Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Load())])])
if set()[:]():
    set()
    u %= set()
    return
else:
    return
    return # If(test=Call(func=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), args=[], keywords=[]), body=[Expr(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), AugAssign(target=Name(id='u', ctx=Store()), op=Mod(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Return()], orelse=[Return(), Return()])
if None >= set():
    assert set(), set().q
    return
    return
else:
    return # If(test=Compare(left=Constant(value=None), ops=[GtE()], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), body=[Assert(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), msg=Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='q', ctx=Del())), Return(), Return()], orelse=[Return()])
if +va:
    while set():
        return
    if set():
        return
else:
    Z = *set() # If(test=UnaryOp(op=UAdd(), operand=Name(id='va', ctx=Load())), body=[While(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), body=[Return()], orelse=[]), If(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), body=[Return()], orelse=[])], orelse=[Assign(targets=[Name(id='Z', ctx=Store())], value=Starred(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ctx=Del()))])
if 
set() << []:
    with :
        return
    assert ()
else:
    j &= set()
    return set() # If(test=Expr(value=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=LShift(), right=List(elts=[], ctx=Load()))), body=[With(items=[], body=[Return()]), Assert(test=Tuple(elts=[], ctx=Del()))], orelse=[AugAssign(target=Name(id='j', ctx=Store()), op=BitAnd(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Return(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
if {set(): set()}:
    G[:] **= set()
else:
    (Q,) += set() # If(test=Dict(keys=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), body=[AugAssign(target=Subscript(value=Name(id='G', ctx=Store()), slice=Slice(), ctx=Store()), op=Pow(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], orelse=[AugAssign(target=Tuple(elts=[Name(id='Q', ctx=Store())], ctx=Store()), op=Add(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
if *(set(),):
    h |= set()
    D >>= set()
else:
    *W /= r # If(test=Starred(value=Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Load()), ctx=Load()), body=[AugAssign(target=Name(id='h', ctx=Store()), op=BitOr(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), AugAssign(target=Name(id='D', ctx=Store()), op=RShift(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], orelse=[AugAssign(target=Starred(value=Name(id='W', ctx=Store()), ctx=Store()), op=Div(), value=Name(id='r', ctx=Del()))])
if {[set(), set(), set()]}:
    w[:].N ^= set().F
else:
    L[:].z *= set().C # If(test=Set(elts=[List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del())]), body=[AugAssign(target=Attribute(value=Subscript(value=Name(id='w', ctx=Store()), slice=Slice(), ctx=Store()), attr='N', ctx=Store()), op=BitXor(), value=Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='F', ctx=Load()))], orelse=[AugAssign(target=Attribute(value=Subscript(value=Name(id='L', ctx=Store()), slice=Slice(), ctx=Store()), attr='z', ctx=Store()), op=Mult(), value=Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='C', ctx=Load()))])
if 192:
    return
    k //= set()
else:
    y -= set()
    d @= set() # If(test=Constant(value=192), body=[Return(), AugAssign(target=Name(id='k', ctx=Store()), op=FloorDiv(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], orelse=[AugAssign(target=Name(id='y', ctx=Store()), op=Sub(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), AugAssign(target=Name(id='d', ctx=Store()), op=MatMult(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
if not True[set():set()]:
    S = False
else:
    E = J = set() # If(test=UnaryOp(op=Not(), operand=Subscript(value=Constant(value=True), slice=Slice(Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Load())), body=[Assign(targets=[Name(id='S', ctx=Store())], value=Constant(value=False))], orelse=[Assign(targets=[Name(id='E', ctx=Store()), Name(id='J', ctx=Store())], value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])

<Return>
return ()                                # Return(value=Tuple(elts=[], ctx=Load()))
return                                   # Return()
return *[{set(): g(), set().k: set()[:], set(): False}, set()] # Return(value=Starred(value=List(elts=[Dict(keys=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='k', ctx=Load()), Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[])], values=[Call(func=Name(id='g', ctx=Load()), args=[], keywords=[]), Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), Constant(value=False)]), BoolOp(op=Or(), values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])])], ctx=Del()), ctx=Load()))
return O2sIF9wuGDe5hBzM10X7a >> (not 
{[*(set(), set())[set() ^ set():set() % set()]].idboHj}) # Return(value=BinOp(left=Name(id='O2sIF9wuGDe5hBzM10X7a', ctx=Del()), op=RShift(), right=UnaryOp(op=Not(), operand=Expr(value=Set(elts=[Attribute(value=List(elts=[Starred(value=Subscript(value=Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del()), slice=Slice(BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitXor(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mod(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), ctx=Load()), ctx=Del())], ctx=Load()), attr='idboHj', ctx=Del())])))))
return ((set() | set()) << set() - set()) @ (set() ** set() * (set() / set())) # Return(value=BinOp(left=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitOr(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=LShift(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Sub(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), op=MatMult(), right=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Pow(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Mult(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Div(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))))
return 7.33                              # Return(value=Constant(value=7.33))
return 'G'                               # Return(value=Constant(value="G"))
return ~-None & set():, X=set()) // +set()[:].c(set()[:], set()[:], l=set(), Q=set()) # Return(value=BinOp(left=UnaryOp(op=Invert(), operand=UnaryOp(op=USub(), operand=Constant(value=None))), op=BitAnd(), right=BinOp(left=Call(func=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Load()), args=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], keywords=[keyword(arg='X', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))]), op=FloorDiv(), right=UnaryOp(op=UAdd(), operand=Call(func=Attribute(value=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), attr='c', ctx=Del()), args=[Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Load())], keywords=[keyword(arg='l', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), keyword(arg='Q', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])))))
return (set() + set() and 24)[''[set() | set():set() % set():set()]:] # Return(value=Subscript(value=BoolOp(op=And(), values=[BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Constant(value=24)]), slice=Slice(Subscript(value=Constant(value=''), slice=Slice(BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitOr(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mod(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Load())), ctx=Load()))
return (oiLk < set() != set()) > 496 <= True # Return(value=Compare(left=Compare(left=Name(id='oiLk', ctx=Del()), ops=[Lt(), NotEq()], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Name(id='f8', ctx=Del())]), ops=[Gt(), LtE()], comparators=[Constant(value=496), Constant(value=True)]))

<Delete>
del ((not {(set()[:]().PTA2)[*set():
{}]})) // True, [] # Delete(targets=[BinOp(left=Compare(left=UnaryOp(op=Not(), operand=Set(elts=[Subscript(value=BoolOp(op=And(), values=[Attribute(value=Call(func=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), args=[], keywords=[]), attr='PTA2', ctx=Load())]), slice=Slice(Starred(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ctx=Del()), Expr(value=Dict(keys=[], values=[]))), ctx=Load())])), ops=[], comparators=[Tuple(elts=[Name(id='G', ctx=Load())], ctx=Load())]), op=FloorDiv(), right=Constant(value=True)), List(elts=[], ctx=Del())])
del [set(), set(), set() / set()], *().QD, y4iFkwX # Delete(targets=[List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Div(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], ctx=Load()), Attribute(value=Starred(value=Tuple(elts=[], ctx=Del()), ctx=Load()), attr='QD', ctx=Del()), Name(id='y4iFkwX', ctx=Del())])
del set(), set(), set() ^ set(), set() % set() >> set() - set() @ set() # Delete(targets=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitXor(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mod(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=RShift(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Sub(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=MatMult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))))])
del set() << set(), set() | set(), set() ** set(), set() * (set() + set()) # Delete(targets=[BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=LShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitOr(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Pow(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mult(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))])
del 6                                    # Delete(targets=[Constant(value=6)])
del '_'                                  # Delete(targets=[Constant(value='_')])
del ~50.413, +-set()[:][set() & set():].F_(set() + set(), set()[:], L=set(), Z=set()) # Delete(targets=[UnaryOp(op=Invert(), operand=Constant(value=50.413)), UnaryOp(op=UAdd(), operand=UnaryOp(op=USub(), operand=Call(func=Attribute(value=Subscript(value=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Load()), slice=Slice(BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitAnd(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), ctx=Load()), attr='F_', ctx=Del()), args=[BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Add(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del())], keywords=[keyword(arg='L', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), keyword(arg='Z', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])))])
del set() or None or z(), (set() and set())[False:c():T()] # Delete(targets=[BoolOp(op=Or(), values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Constant(value=None), Call(func=Name(id='z', ctx=Del()), args=[], keywords=[])]), Subscript(value=BoolOp(op=And(), values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), slice=Slice(Constant(value=False), Call(func=Name(id='c', ctx=Del()), args=[], keywords=[]), Call(func=Name(id='T', ctx=Load()), args=[], keywords=[])), ctx=Del())])
del ''                                   # Delete(targets=[Constant(value="")])
del k5vofh3xGZH == R1rc                  # Delete(targets=[Compare(left=Name(id='k5vofh3xGZH', ctx=Load()), ops=[Eq()], comparators=[Name(id='R1rc', ctx=Del()), Name(id='HPJup', ctx=Del())])])

<Assert>
assert {}                                # Assert(test=Dict(keys=[], values=[List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Constant(value=6.7), Attribute(value=BoolOp(op=Or(), values=[]), attr='Q', ctx=Del()), Compare(left=Expr(value=UnaryOp(op=Invert(), operand=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), ops=[NotIn()], comparators=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])])], ctx=Del())]))
assert Z4mcX(set(), o=set()), ICkz[*(set() ^ set(),):] # Assert(test=Call(func=Name(id='Z4mcX', ctx=Del()), args=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], keywords=[keyword(arg='o', value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))]), msg=Subscript(value=Name(id='ICkz', ctx=Load()), slice=Slice(Starred(value=Tuple(elts=[BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitXor(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], ctx=Load()), ctx=Load())), ctx=Load()))
assert [().H, {().h}, *(set(),)[*set() / set():set() // set()]] # Assert(test=List(elts=[Attribute(value=Tuple(elts=[], ctx=Del()), attr='H', ctx=Del()), Set(elts=[Attribute(value=Tuple(elts=[], ctx=Del()), attr='h', ctx=Load())]), Subscript(value=Starred(value=Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Load()), ctx=Del()), slice=Slice(Starred(value=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Div(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Load()), BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=FloorDiv(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), ctx=Del())], ctx=Load()))
assert set() ** set() % (set() - set()), (set() >> (set() & set())) + (set() | set()) * (set() << set()) # Assert(test=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Pow(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Mod(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Sub(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), msg=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=RShift(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitAnd(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), op=Add(), right=BinOp(left=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitOr(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), op=Mult(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=LShift(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))))
assert None                              # Assert(test=Constant(value=None))
assert True                              # Assert(test=Constant(value=True))
assert not 331                           # Assert(test=UnaryOp(op=Not(), operand=Constant(value=331)))
assert -set()[:].x(set(), set()), +(not (set()[:]())[:]) # Assert(test=UnaryOp(op=USub(), operand=Call(func=Attribute(value=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), attr='x', ctx=Load()), args=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], keywords=[])), msg=UnaryOp(op=UAdd(), operand=UnaryOp(op=Not(), operand=Subscript(value=BoolOp(op=And(), values=[Call(func=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Load()), args=[], keywords=[])]), slice=Slice(), ctx=Load()))))
assert 'X' @ (set())[False:set()]['':][False:][9:'Rbw':'m'] # Assert(test=BinOp(left=Constant(value='X'), op=MatMult(), right=Subscript(value=Subscript(value=Subscript(value=Subscript(value=Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), slice=Slice(Constant(value=False), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), ctx=Del()), slice=Slice(Constant(value="")), ctx=Load()), slice=Slice(Constant(value=False)), ctx=Load()), slice=Slice(Constant(value=9), Constant(value="Rbw"), Constant(value="m")), ctx=Del())))
assert bwxOpNKPEWF6yVnaubG5BIrJ2lt3AiD97QMsvf_LjYeSZHqohR0g81TUd # Assert(test=Name(id='bwxOpNKPEWF6yVnaubG5BIrJ2lt3AiD97QMsvf_LjYeSZHqohR0g81TUd', ctx=Del()))

<Pass>
pass                                     # Pass()
pass                                     # Pass()
pass                                     # Pass()
pass                                     # Pass()
pass                                     # Pass()
pass                                     # Pass()
pass                                     # Pass()
pass                                     # Pass()
pass                                     # Pass()
pass                                     # Pass()

<Break>
break                                    # Break()
break                                    # Break()
break                                    # Break()
break                                    # Break()
break                                    # Break()
break                                    # Break()
break                                    # Break()
break                                    # Break()
break                                    # Break()
break                                    # Break()

<Continue>
continue                                 # Continue()
continue                                 # Continue()
continue                                 # Continue()
continue                                 # Continue()
continue                                 # Continue()
continue                                 # Continue()
continue                                 # Continue()
continue                                 # Continue()
continue                                 # Continue()
continue                                 # Continue()

<With>
with :
    [c, (y,)] //= {}
    with set(), set(): # type: t
        return # With(items=[], body=[AugAssign(target=List(elts=[Name(id='c', ctx=Store()), Tuple(elts=[Name(id='y', ctx=Store())], ctx=Store())], ctx=Store()), op=FloorDiv(), value=Dict(keys=[], values=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='e', ctx=Load())])), With(items=[withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], body=[Return()], type_comment='t')])
with set() as C, *set() as *P: # type: 
    while (set())[:]:
        break # With(items=[withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), optional_vars=Name(id='C', ctx=Store())), withitem(context_expr=Starred(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ctx=Del()), optional_vars=Starred(value=Name(id='P', ctx=Store()), ctx=Store()))], body=[While(test=Subscript(value=Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[]), slice=Slice(), ctx=Load()), body=[Break()], orelse=[])], type_comment="")
with '' as G[:]._: # type: H!
    del set(), set(), set()
    set()
    pass
    continue # With(items=[withitem(context_expr=Constant(value=''), optional_vars=Attribute(value=Subscript(value=Name(id='G', ctx=Store()), slice=Slice(), ctx=Store()), attr='_', ctx=Store()))], body=[Delete(targets=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])]), Expr(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Pass(), Continue()], type_comment="H!")
with : # type: |S9vg
    for B in set(): # type: 
        return # With(items=[withitem(context_expr=BoolOp(op=And(), values=[]))], body=[For(target=Name(id='B', ctx=Store()), iter=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), body=[Return()], orelse=[], type_comment="")], type_comment='|S9vg')
with () as Y[:]: # type: t>A
    b = E = set() # With(items=[withitem(context_expr=Tuple(elts=[], ctx=Load()), optional_vars=Subscript(value=Name(id='Y', ctx=Store()), slice=Slice(), ctx=Store()))], body=[Assign(targets=[Name(id='b', ctx=Store()), Name(id='E', ctx=Store())], value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], type_comment="t>A")
with set(), set() as K[:]: # type: f
    if set():
        return
    return
    return # With(items=[withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), optional_vars=Subscript(value=Name(id='K', ctx=Store()), slice=Slice(), ctx=Store()))], body=[If(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), body=[Return()], orelse=[]), Return(), Return()], type_comment="f")
with set(), set(), [], set() as r[:]: # type: n
    assert set()
    return
    return # With(items=[withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), withitem(context_expr=List(elts=[], ctx=Del())), withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), optional_vars=Subscript(value=Name(id='r', ctx=Store()), slice=Slice(), ctx=Store()))], body=[Assert(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Return(), Return()], type_comment='n')
with set() as v: # type: $5a?@c
    assert set(), set()
    return
    return
    return # With(items=[withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), optional_vars=Name(id='v', ctx=Store()))], body=[Assert(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), msg=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Return(), Return(), Return()], type_comment="$5a?@c")
with set() as j: # type:  j
    return set()[:]()
    return
    return
    return # With(items=[withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), optional_vars=Name(id='j', ctx=Store()))], body=[Return(value=Call(func=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), args=[], keywords=[])), Return(), Return(), Return()], type_comment=' j')
with set(): # type: 
    J[:] &= set() * set()
    h /= I
    return # With(items=[withitem(context_expr=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], body=[AugAssign(target=Subscript(value=Name(id='J', ctx=Store()), slice=Slice(), ctx=Store()), op=BitAnd(), value=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), AugAssign(target=Name(id='h', ctx=Store()), op=Div(), value=Name(id='I', ctx=Load())), Return()], type_comment='')

让我们看看我们是否也能正确地解析代码。以下是一个示例:

with_tree = ast.parse("""
with open('foo.txt') as myfile:
 content = myfile.readlines()
 if content is not None:
 print(content)
""") 
python_ast_stmts_grammar = convert_ebnf_grammar(PYTHON_AST_STMTS_GRAMMAR)
with_tree_str = ast.dump(with_tree.body[0])  # get the `With(...)` subtree
print(with_tree_str)
with_solver = ISLaSolver(python_ast_stmts_grammar)
assert with_solver.check(with_tree_str) 
With(items=[withitem(context_expr=Call(func=Name(id='open', ctx=Load()), args=[Constant(value='foo.txt')], keywords=[]), optional_vars=Name(id='myfile', ctx=Store()))], body=[Assign(targets=[Name(id='content', ctx=Store())], value=Call(func=Attribute(value=Name(id='myfile', ctx=Load()), attr='readlines', ctx=Load()), args=[], keywords=[])), If(test=Compare(left=Name(id='content', ctx=Load()), ops=[IsNot()], comparators=[Constant(value=None)]), body=[Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Name(id='content', ctx=Load())], keywords=[]))], orelse=[])])

看起来我们的语法也能正确地解析非平凡代码。我们做得很好!

函数定义

现在是函数定义。这里没有太多惊喜。

print(ast.dump(ast.parse("""
def f(a, b=1):
 pass
"""
), indent=4)) 
Module(
    body=[
        FunctionDef(
            name='f',
            args=arguments(
                posonlyargs=[],
                args=[
                    arg(arg='a'),
                    arg(arg='b')],
                kwonlyargs=[],
                kw_defaults=[],
                defaults=[
                    Constant(value=1)]),
            body=[
                Pass()],
            decorator_list=[])],
    type_ignores=[])

PYTHON_AST_DEFS_GRAMMAR: Grammar = extend_grammar(PYTHON_AST_STMTS_GRAMMAR, {
    '<stmt>': PYTHON_AST_STMTS_GRAMMAR['<stmt>'] + [ '<FunctionDef>' ],

    '<FunctionDef>': [
        'FunctionDef(name=<identifier>, args=<arguments>, body=<nonempty_stmt_list><decorator_list_param><returns>?<type_comment>?)'
    ],
    '<decorator_list_param>': [
        ', decorator_list=<expr_list>'
    ],

    '<arguments>': [
        'arguments(<posonlyargs_param>args=<arg_list><vararg>?<kwonlyargs_param><kw_defaults_param><kwarg>?<defaults_param>)'
    ],
    '<posonlyargs_param>': [
        'posonlyargs=<arg_list>, '
    ],
    '<kwonlyargs_param>': [
        ', kwonlyargs=<arg_list>'
    ],
    '<kw_defaults_param>': [
        ', kw_defaults=<expr_list>'
    ],
    '<defaults_param>': [
        ', defaults=<expr_list>'
    ],

    '<arg_list>': [ '[<args>?]' ],
    '<args>': [ '<arg>', '<arg>, <arg>' ],
    '<arg>': [ 'arg(arg=<identifier>)' ],

    '<vararg>': [ ', vararg=<arg>' ],
    '<kwarg>': [ ', kwarg=<arg>' ],
    '<returns>': [ ', returns=<expr>' ],

    # FIXME: Not handled: AsyncFunctionDef, ClassDef
}) 

在 Python 3.12 及以后的版本中,函数定义也有一个type_param字段:

# do import this unconditionally
if sys.version_info >= (3, 12):
    PYTHON_AST_DEFS_GRAMMAR: Grammar = extend_grammar(PYTHON_AST_DEFS_GRAMMAR, {
    '<FunctionDef>': [
        'FunctionDef(name=<identifier>, args=<arguments>, body=<nonempty_stmt_list><decorator_list_param><returns>?<type_comment>?<type_params>?)'
    ],
    '<type_params>': [
        ', type_params=<type_param_list>',
    ],
    '<type_param_list>': [ '[<type_param>?]' ],
    '<type_param>': [ '<TypeVar>', '<ParamSpec>', '<TypeVarTuple>' ],
    '<TypeVar>': [
        'TypeVar(name=<identifier>(, bound=<expr>)?)'
    ],
    '<ParamSpec>': [
        'ParamSpec(name=<identifier>)'
    ],
    '<TypeVarTuple>': [
        'TypeVarTuple(name=<identifier>)'
    ]
    }) 

在 Python 3.13 及以后的版本中,几个<FunctionDef><arguments>属性是可选的:

# do import this unconditionally
if sys.version_info >= (3, 13):
    PYTHON_AST_DEFS_GRAMMAR: Grammar = extend_grammar(PYTHON_AST_DEFS_GRAMMAR, {
    '<FunctionDef>': [
        'FunctionDef(name=<identifier>, args=<arguments>, body=<nonempty_stmt_list><decorator_list_param>?<returns>?<type_comment>?<type_params>?)'
    ],
    '<arguments>': [
        'arguments(<posonlyargs_param>?args=<arg_list><vararg>?<kwonlyargs_param>?<kw_defaults_param>?<kwarg>?<defaults_param>?)'
    ],
    }) 
assert is_valid_grammar(PYTHON_AST_DEFS_GRAMMAR) 
for elt in [ '<arguments>', '<FunctionDef>' ]:
    print(elt)
    test_samples(PYTHON_AST_DEFS_GRAMMAR, start_symbol=elt)
    print() 
<arguments>
i, /, Wr, x                              # arguments(posonlyargs=[arg(arg='i')], args=[arg(arg='Wr'), arg(arg='x')], kwonlyargs=[], kw_defaults=[List(elts=[UnaryOp(op=UAdd(), operand=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Dict(keys=[], values=[]), Name(id='P', ctx=Load())], ctx=Load())], defaults=[])
G, /, h=, *e, u=set(), **R3              # arguments(posonlyargs=[arg(arg='G')], args=[arg(arg='h')], vararg=arg(arg='e'), kwonlyargs=[arg(arg='u')], kw_defaults=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Starred(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ctx=Del())], kwarg=arg(arg='R3'), defaults=[BoolOp(op=Or(), values=[])])
n, C, /, s, T=set(), *S, L=set(), **j    # arguments(posonlyargs=[arg(arg='n'), arg(arg='C')], args=[arg(arg='s'), arg(arg='T')], vararg=arg(arg='S'), kwonlyargs=[arg(arg='L')], kw_defaults=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], kwarg=arg(arg='j'), defaults=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])])
F, z, /, Q, N, *Y, X=set(), **g          # arguments(posonlyargs=[arg(arg='F'), arg(arg='z')], args=[arg(arg='Q'), arg(arg='N')], vararg=arg(arg='Y'), kwonlyargs=[arg(arg='X'), arg(arg='I')], kw_defaults=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], kwarg=arg(arg='g'), defaults=[])
A=set(), /, B=set(), f=set(), *O6, K=set(), **Z # arguments(posonlyargs=[arg(arg='A')], args=[arg(arg='B'), arg(arg='f')], vararg=arg(arg='O6'), kwonlyargs=[arg(arg='K')], kw_defaults=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], kwarg=arg(arg='Z'), defaults=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])])
p, y=set(), /, H=
set(), *l, Jo=set(), **V # arguments(posonlyargs=[arg(arg='p'), arg(arg='y')], args=[arg(arg='H')], vararg=arg(arg='l'), kwonlyargs=[arg(arg='Jo')], kw_defaults=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], kwarg=arg(arg='V'), defaults=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Expr(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
m, /, U, c, *w, **o                      # arguments(posonlyargs=[arg(arg='m')], args=[arg(arg='U'), arg(arg='c')], vararg=arg(arg='w'), kwonlyargs=[arg(arg='b'), arg(arg='q')], kw_defaults=[], kwarg=arg(arg='o'), defaults=[])
k, v, /, E, t=set() % set(), *_, **rR    # arguments(posonlyargs=[arg(arg='k'), arg(arg='v')], args=[arg(arg='E'), arg(arg='t')], vararg=arg(arg='_'), kwonlyargs=[], kw_defaults=[], kwarg=arg(arg='rR'), defaults=[BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mod(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))])
M, /, D, *d, a=set(), **Z                # arguments(posonlyargs=[arg(arg='M')], args=[arg(arg='D')], vararg=arg(arg='d'), kwonlyargs=[arg(arg='a')], kw_defaults=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='F', ctx=Del())], kwarg=arg(arg='Z'), defaults=[])
n, Y, /, g, y=set(), *z, **U             # arguments(posonlyargs=[arg(arg='n'), arg(arg='Y')], args=[arg(arg='g'), arg(arg='y')], vararg=arg(arg='z'), kwonlyargs=[], kw_defaults=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], kwarg=arg(arg='U'), defaults=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])])

<FunctionDef>
def U():
    return                      # FunctionDef(name='U', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[])
def F():
    pass                        # FunctionDef(name='F', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Pass()], decorator_list=[])
def u() -> set(): # type: 
    continue  # FunctionDef(name='u', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Continue()], decorator_list=[], returns=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), type_comment="")
def D() -> set(): # type: 
    break     # FunctionDef(name='D', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Break()], decorator_list=[], returns=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), type_comment='')
def w(): # type: 
    return             # FunctionDef(name='w', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[], type_comment='')
def g() -> set(): # type: 
    return    # FunctionDef(name='g', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[], returns=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), type_comment='')
def q() -> set(): # type: 
    return    # FunctionDef(name='q', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[], returns=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), type_comment='')
def W() -> set():
    return             # FunctionDef(name='W', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[], returns=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))
def I() -> set():
    return             # FunctionDef(name='I', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[], returns=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))
def n() -> set(): # type: 
    return    # FunctionDef(name='n', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[], returns=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), type_comment="")

```</details> <details id="Excursion:-Modules"><summary>模块</summary>

我们以*模块*结束——一系列的定义。在所有其他定义之后,这现在相当直接。

```py
PYTHON_AST_MODULE_GRAMMAR: Grammar = extend_grammar(PYTHON_AST_DEFS_GRAMMAR, {
    '<start>': [ '<mod>' ],
    '<mod>': [ '<Module>' ],
    '<Module>': [ 'Module(body=<nonempty_stmt_list><type_ignore_param>)'],

    '<type_ignore_param>': [ ', type_ignores=<type_ignore_list>' ],
    '<type_ignore_list>': [ '[<type_ignores>?]' ],
    '<type_ignores>': [ '<type_ignore>', '<type_ignore>, <type_ignore>' ],
    '<type_ignore>': [ 'TypeIgnore(lineno=<integer>, tag=<string>)' ],
}) 
# do import this unconditionally
if sys.version_info >= (3, 13):
    PYTHON_AST_MODULE_GRAMMAR: Grammar = \
        extend_grammar(PYTHON_AST_MODULE_GRAMMAR, {
        # As of 3.13, the type_ignore parameter is optional
        '<Module>': [ 'Module(body=<nonempty_stmt_list><type_ignore_param>?)'],
    }) 
assert is_valid_grammar(PYTHON_AST_MODULE_GRAMMAR) 
for elt in [ '<Module>' ]:
    print(elt)
    test_samples(PYTHON_AST_MODULE_GRAMMAR, start_symbol=elt)
    print() 
<Module>
EESc9e.w @ {
[*(not set())[y():set():{}]], } # Module(body=[Expr(value=BinOp(left=Attribute(value=Name(id='EESc9e', ctx=Del()), attr='w', ctx=Load()), op=MatMult(), right=Set(elts=[Expr(value=List(elts=[Starred(value=Subscript(value=UnaryOp(op=Not(), operand=Compare(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), ops=[], comparators=[])), slice=Slice(Call(func=Name(id='y', ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Dict(keys=[], values=[])), ctx=Load()), ctx=Load())], ctx=Del())), BoolOp(op=And(), values=[])])))], type_ignores=[])
while (None, ''):
    m = set()
    del 
    return
else:
    break
    with :
        return
    pass
return
continue # Module(body=[While(test=Tuple(elts=[Constant(value=None), Constant(value='')], ctx=Load()), body=[Assign(targets=[Name(id='m', ctx=Store())], value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Delete(targets=[]), Return()], orelse=[Break(), With(items=[], body=[Return()]), Pass()]), Return(), Continue()], type_ignores=[TypeIgnore(lineno=27, tag=''), TypeIgnore(lineno=2, tag="h")])
for I.V in set()[:]: # type: 
    return # Module(body=[For(target=Attribute(value=Name(id='I', ctx=Store()), attr='V', ctx=Store()), iter=Subscript(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), slice=Slice(), ctx=Del()), body=[Return()], orelse=[], type_comment="")], type_ignores=[TypeIgnore(lineno=131, tag='[bm')])
def Q():
    return
assert set().a
return # Module(body=[FunctionDef(name='Q', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[]), Assert(test=Attribute(value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), attr='a', ctx=Del())), Return()], type_ignores=[TypeIgnore(lineno=56, tag="M")])
if set():
    return
*h <<= set()        # Module(body=[If(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), body=[Return()], orelse=[]), AugAssign(target=Starred(value=Name(id='h', ctx=Store()), ctx=Store()), op=LShift(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], type_ignores=[TypeIgnore(lineno=8, tag=""), TypeIgnore(lineno=5, tag="")])
return [set()]
assert (set(), set()), [] # Module(body=[Return(value=List(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Load())), Assert(test=Tuple(elts=[Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])], ctx=Del()), msg=List(elts=[], ctx=Del()))], type_ignores=[TypeIgnore(lineno=89, tag=""), TypeIgnore(lineno=0, tag="Q")])
D |= set()                               # Module(body=[AugAssign(target=Name(id='D', ctx=Store()), op=BitOr(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))], type_ignores=[TypeIgnore(lineno=74, tag='1'), TypeIgnore(lineno=90, tag="")])
[o, j] += *set() % set() ** set()        # Module(body=[AugAssign(target=List(elts=[Name(id='o', ctx=Store()), Name(id='j', ctx=Store())], ctx=Store()), op=Add(), value=Starred(value=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mod(), right=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Pow(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))), ctx=Del()))], type_ignores=[TypeIgnore(lineno=3980, tag="7'Z")])
x[:] /= set()
i -= set() & set()         # Module(body=[AugAssign(target=Subscript(value=Name(id='x', ctx=Store()), slice=Slice(), ctx=Store()), op=Div(), value=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), AugAssign(target=Name(id='i', ctx=Store()), op=Sub(), value=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=BitAnd(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])))], type_ignores=[TypeIgnore(lineno=40, tag='W2j')])
s //= -(set() * set())                   # Module(body=[AugAssign(target=Name(id='s', ctx=Store()), op=FloorDiv(), value=UnaryOp(op=USub(), operand=BinOp(left=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), op=Mult(), right=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))))], type_ignores=[TypeIgnore(lineno=665, tag=""), TypeIgnore(lineno=5, tag="")])

```</details>

到目前为止,我们已经涵盖了(几乎)Python 的所有 AST 元素。还有一些 Python 元素需要考虑(标记为`FIXME`,见上),但我们将把这些留给读者。让我们定义`PYTHON_AST_GRAMMAR`为这一章中出现的官方语法。

```py
PYTHON_AST_GRAMMAR = PYTHON_AST_MODULE_GRAMMAR
python_ast_grammar = convert_ebnf_grammar(PYTHON_AST_GRAMMAR) 

这里有一些(非常奇怪)的 Python 函数示例,我们可以生成。所有这些都是有效的,但只有语法上有效——通过这种方式生成的代码样本中,真正有意义的非常少。

for elt in [ '<FunctionDef>' ]:
    print(elt)
    test_samples(PYTHON_AST_GRAMMAR, start_symbol=elt)
    print() 
<FunctionDef>
def w():
    pass                        # FunctionDef(name='w', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Pass()], decorator_list=[])
def a():
    break                       # FunctionDef(name='a', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Break()], decorator_list=[])
def o():
    return                      # FunctionDef(name='o', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[])
def v(): # type: 
    continue           # FunctionDef(name='v', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Continue()], decorator_list=[], type_comment='')
def j(): # type: 
    return             # FunctionDef(name='j', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[], type_comment="")
def k():
    return
    return           # FunctionDef(name='k', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return(), Return()], decorator_list=[])
def Q() -> set(): # type: 
    return    # FunctionDef(name='Q', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[], returns=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), type_comment='')
def d() -> None:
    return
    assert set(), set()
    return # FunctionDef(name='d', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return(), Assert(test=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]), msg=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[])), Return()], decorator_list=[], returns=Constant(value=None))
def K() -> set():
    return             # FunctionDef(name='K', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[], returns=Call(func=Name(id="set", ctx=Load()), args=[], keywords=[]))
def y(): # type: 
    return             # FunctionDef(name='y', args=arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Return()], decorator_list=[], type_comment='')

用于模糊测试 Python 的类

为了方便起见,让我们引入一个名为PythonFuzzer的类,它利用上述语法来生成 Python 代码。这将相当容易使用。

class PythonFuzzer(ISLaSolver):
  """Produce Python code."""

    def __init__(self,
                 start_symbol: Optional[str] = None, *,
                 grammar: Optional[Grammar] = None,
                 constraint: Optional[str] =None,
                 **kw_params) -> None:
  """Produce Python code. Parameters are:

 * `start_symbol`: The grammatical entity to be generated (default: `<FunctionDef>`)
 * `grammar`: The EBNF grammar to be used (default: `PYTHON__AST_GRAMMAR`); and
 * `constraint` an ISLa constraint (if any).

 Additional keyword parameters are passed to the `ISLaSolver` superclass.
 """
        if start_symbol is None:
            start_symbol = '<FunctionDef>'
        if grammar is None:
            grammar = PYTHON_AST_GRAMMAR
        assert start_symbol in grammar

        g = convert_ebnf_grammar(grammar)
        if constraint is None:
            super().__init__(g, start_symbol=start_symbol, **kw_params)
        else:
            super().__init__(g, constraint, start_symbol=start_symbol, **kw_params)

    def fuzz(self) -> str:
  """Produce a Python code string."""
        abstract_syntax_tree = eval(str(self.solve()))
        ast.fix_missing_locations(abstract_syntax_tree)
        return ast.unparse(abstract_syntax_tree) 

默认情况下,PythonFuzzer将生成一个函数定义——即函数头和主体。

fuzzer = PythonFuzzer()
print(fuzzer.fuzz()) 
def L():
    continue

通过传递一个起始符号作为参数,你可以让PythonFuzzer生成任意的 Python 元素:

fuzzer = PythonFuzzer('<While>')
print(fuzzer.fuzz()) 
while (set()[set():set()], *(set())):
    if {}:
        while set():
            continue
        break
    else:
        del 
        return

这里是一个所有可能的起始符号列表:

sorted(list(PYTHON_AST_GRAMMAR.keys())) 
['<Assert>',
 '<Assign>',
 '<Attribute>',
 '<AugAssign>',
 '<BinOp>',
 '<BoolOp>',
 '<Break>',
 '<Call>',
 '<Compare>',
 '<Constant>',
 '<Continue>',
 '<Delete>',
 '<Dict>',
 '<EmptySet>',
 '<Expr>',
 '<For>',
 '<FunctionDef>',
 '<If>',
 '<List>',
 '<Module>',
 '<Name>',
 '<Pass>',
 '<Return>',
 '<Set>',
 '<Slice>',
 '<Starred>',
 '<Subscript>',
 '<Tuple>',
 '<UnaryOp>',
 '<While>',
 '<With>',
 '<arg>',
 '<arg_list>',
 '<args>',
 '<args_param>',
 '<arguments>',
 '<bool>',
 '<boolop>',
 '<cmpop>',
 '<cmpop_list>',
 '<cmpops>',
 '<decorator_list_param>',
 '<defaults_param>',
 '<digit>',
 '<digits>',
 '<expr>',
 '<expr_list>',
 '<exprs>',
 '<float>',
 '<func>',
 '<id>',
 '<id_continue>',
 '<id_start>',
 '<identifier>',
 '<integer>',
 '<keyword>',
 '<keyword_list>',
 '<keywords>',
 '<keywords_param>',
 '<kw_defaults_param>',
 '<kwarg>',
 '<kwonlyargs_param>',
 '<lhs_Attribute>',
 '<lhs_List>',
 '<lhs_Name>',
 '<lhs_Starred>',
 '<lhs_Subscript>',
 '<lhs_Tuple>',
 '<lhs_expr>',
 '<lhs_exprs>',
 '<literal>',
 '<mod>',
 '<none>',
 '<nonempty_expr_list>',
 '<nonempty_lhs_expr_list>',
 '<nonempty_stmt_list>',
 '<nonzerodigit>',
 '<not_double_quotes>',
 '<not_single_quotes>',
 '<operator>',
 '<orelse_param>',
 '<posonlyargs_param>',
 '<returns>',
 '<start>',
 '<stmt>',
 '<stmt_list>',
 '<stmts>',
 '<string>',
 '<type_comment>',
 '<type_ignore>',
 '<type_ignore_list>',
 '<type_ignore_param>',
 '<type_ignores>',
 '<unaryop>',
 '<vararg>',
 '<withitem>',
 '<withitem_list>',
 '<withitems>']

定制 Python Fuzzer

在模糊测试时,你可能对生成的输出的特定属性感兴趣。我们如何影响PythonFuzzer生成的代码?我们探索两种方法:

  • 通过调整语法以满足我们的需求

  • 通过添加约束来自定义输出。

调整语法

调整输出生成的一个简单方法是调整语法

假设你想要没有装饰器的函数定义。为了实现这一点,你可以修改产生函数定义的规则

PYTHON_AST_GRAMMAR['<FunctionDef>'] 
['FunctionDef(name=<identifier>, args=<arguments>, body=<nonempty_stmt_list><decorator_list_param><returns>?<type_comment>?)']

作为一个 AST 规则,它以抽象语法的形式出现,因此我们首先必须确定我们想要调整的元素。在我们的例子中,这是 decorator_list

由于 decorator_list 是一个列表,我们可以修改规则以只产生空列表。为了创建一个新的适应语法,我们不会修改现有的 PYTHON_AST_GRAMMAR。相反,我们使用 extend_grammar() 函数创建一个新的语法,其中包含一个新的、适应的 <FunctionDef> 规则:

python_ast_grammar_without_decorators: Grammar = extend_grammar(PYTHON_AST_GRAMMAR,
{
    '<FunctionDef>' :
        ['FunctionDef(name=<identifier>, args=<arguments>, body=<nonempty_stmt_list>, decorator_list=[])']
}) 

然而,我们还没有完成。我们还需要确保我们的语法是有效的,因为任何拼写错误的非终端标识符都会在生产过程中导致问题。为此,我们使用 is_valid_grammar() 函数:

from ExpectError import ExpectError 
with ExpectError():
    assert is_valid_grammar(python_ast_grammar_without_decorators) 
'<decorator_list_param>': defined, but not used. Consider applying trim_grammar() on the grammar
'<returns>': defined, but not used. Consider applying trim_grammar() on the grammar
'<decorator_list_param>': unreachable from <start>. Consider applying trim_grammar() on the grammar
'<returns>': unreachable from <start>. Consider applying trim_grammar() on the grammar
Traceback (most recent call last):
  File "/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_17604/3611578183.py", line 2, in <module>
    assert is_valid_grammar(python_ast_grammar_without_decorators)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError (expected)

我们可以看到,随着我们的更改,我们的语法有一个孤立的规则<returns> 规则不再被使用。这是因为 <returns><type_annotation> 的一部分,我们刚刚已经删除了。(<type_annotation> 在为变量定义类型时仍然被使用。)

为了解决这个问题,我们需要从我们的语法中删除 <returns> 规则。幸运的是,我们有一个名为 trim_grammar() 的函数,它可以删除所有孤立的规则:

python_ast_grammar_without_decorators = trim_grammar(python_ast_grammar_without_decorators) 

这样,我们的语法就变得有效了...

assert is_valid_grammar(python_ast_grammar_without_decorators) 

...并且我们可以用它来进行模糊测试——现在不再需要装饰器:

fuzzer = PythonFuzzer(grammar=python_ast_grammar_without_decorators)
print(fuzzer.fuzz()) 
def X():
    break

一旦你理解了语法结构,调整语法就很简单了;但是,AST 语法很复杂;此外,你的更改和扩展会紧密地与语法结构相关联。仔细研究上面定义的各个规则。

使用约束进行定制

使用约束进行定制的一个更优雅的替代方案是利用约束来调整语法以满足你的需求。由于 PythonFuzzer 是从 ISLaSolver 派生的,我们可以传递一个 constraint 参数来约束语法,正如在使用约束进行模糊测试章节中讨论的那样。

如果我们想要每个标识符有 10 个字符的函数定义,我们可以使用一个 ISLa 约束:

fuzzer = PythonFuzzer(constraint='str.len(<id>) = 10')
print(fuzzer.fuzz()) 
def yWOOLwypwp(): # type: 
    return

我们还可以约束单个子项——比如说,函数的实际标识符。

# Also works (the <identifier> has quotes)
fuzzer = PythonFuzzer(constraint='<FunctionDef>.<identifier> = "\'my_favorite_function\'"')
print(fuzzer.fuzz()) 
@[set(), set()]
@set() | {}
@(-*set())[set():():
set()[:]()]
def my_favorite_function(dlFf=Qr, l1M=set(), *) -> 942.5:
    return

假设我们想要测试编译器如何处理大数字。让我们定义一个约束,使得函数体(<nonempty_stmt_list>)至少包含一个值至少为 1000 的整数(<integer>):

fuzzer = PythonFuzzer(constraint=
"""
 exists <integer> x:
 (inside(x, <nonempty_stmt_list>) and str.to.int(x) > 1000)
""")
print(fuzzer.fuzz()) 
@[set(), +set(), 
set()]
@{set(): set(), set(): set()}
@(set(), *set() & set())
def l(r, a, /, *uXLV, _=set()[:], **Z) -> sdTYWE9b or {set(), set().R}.Vy != z1vw([]):
    del 1007

假设我们想要测试带有非平凡函数的编译器。以下是定义一个约束的方法,使得函数体恰好有三个语句(<stmt>)。请注意,这可能需要超过一分钟才能解决,但结果肯定是一个非平凡函数。

# This will not work with ISLa 2
fuzzer = PythonFuzzer(constraint="""
 forall <FunctionDef> def: count(def, "<stmt>", "3")
""")
print(fuzzer.fuzz()) 
@3.91
def V8(w, /, *, t=set(), C5D=set(), **foT6):
    if *{}.S[:] - ((set()) not in set() in set()):
        pass
    else:
        return

最后,如果我们想要装饰器列表为空,就像我们在语法修改示例中做的那样,我们可以约束装饰器列表为空:

fuzzer = PythonFuzzer(constraint='<FunctionDef>..<expr_list> = "[]"')
print(fuzzer.fuzz()) 
def l(Jws4IzSPx_O2ajk687obQB3mflULCTJWnAv9GHg0YRtVNycueKFDMihZ5rXd1pqEo, /, *, **g):
    return

修改代码

当为编译器生成代码(或者实际上,生成一般输入时),通常一个好的做法不是从头开始创建一切,而是修改现有的输入。这样,可以在常见输入(需要修改的输入)和不常见输入(通过修改添加的新部分)之间达到更好的平衡。

解析输入

修改输入,我们首先需要能够解析它们。这正是语法真正发挥作用的地方——它真的能够解析所有可能的代码吗?这就是为什么依赖于一个现有的、经过验证的解析器(在我们的例子中是 Python 解析器)并在一个抽象(在我们的例子中是 AST)上操作是非常方便的。

我们已经看到如何使用ast.parse()将代码解析成 AST:

def sum(a, b):    # A simple example
    the_sum = a + b
    return the_sum 
sum_source = inspect.getsource(sum)
sum_tree = ast.parse(sum_source)
print(ast.unparse(sum_tree)) 
def sum(a, b):
    the_sum = a + b
    return the_sum

sum_str = ast.dump(sum_tree)
sum_str 
"Module(body=[FunctionDef(name='sum', args=arguments(posonlyargs=[], args=[arg(arg='a'), arg(arg='b')], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Assign(targets=[Name(id='the_sum', ctx=Store())], value=BinOp(left=Name(id='a', ctx=Load()), op=Add(), right=Name(id='b', ctx=Load()))), Return(value=Name(id='the_sum', ctx=Load()))], decorator_list=[])], type_ignores=[])"

我们的语法能够解析这个(非平凡)字符串:

solver = ISLaSolver(python_ast_grammar)
assert solver.check(sum_str) 

要修改输入,我们首先必须将其解析成推导树结构。这是(再次)代码的树表示,但这次,使用的是我们语法的元素。

sum_tree = solver.parse(sum_str) 

让我们检查一下推导树的样子。唉,字符串表示非常长,并不那么有用:

len(repr(sum_tree)) 
8737

repr(sum_tree)[:200] 
"DerivationTree('<start>', (DerivationTree('<mod>', (DerivationTree('<Module>', (DerivationTree('Module(body=', (), id=495073), DerivationTree('<nonempty_stmt_list>', (DerivationTree('', (), id=495071"

然而,我们可以可视化推导树:

from [GrammarFuzzer import display_tree 
display_tree(sum_tree) 

0 1 0->1 2 1->2 3 Module(body= 2->3 4 <nonempty_stmt_list> 2->4 200 <type_ignore_param> 2->200 206 ) (41) 2->206 5 [ (91) 4->5 6 4->6 199 ] (93) 4->199 7 6->7 8 7->8 9 FunctionDef(name= 8->9 10 8->10 23 , args= 8->23 24 8->24 81 , body= 8->81 82 <nonempty_stmt_list> 8->82 190 <decorator_list_param> 8->190 196 8->196 197 <type_comment-3> 8->197 198 ) (41) 8->198 11 ' (39) 10->11 12 10->12 22 ' (39) 10->22 13 <id_start> 12->13 15 <id_continue-1> 12->15 14 s (115) 13->14 16 <id_continue> 15->16 18 <id_continue-1> 15->18 17 u (117) 16->17 19 <id_continue> 18->19 21 <id_continue-1> 18->21 20 m (109) 19->20 25 arguments( 24->25 26 <posonlyargs_param> 24->26 33 args= 24->33 34 <arg_list> 24->34 60 24->60 61 <kwonlyargs_param> 24->61 67 <kw_defaults_param> 24->67 73 24->73 74 <defaults_param> 24->74 80 ) (41) 24->80 27 posonlyargs= 26->27 28 <arg_list> 26->28 32 , 26->32 29 [ (91) 28->29 30 28->30 31 ] (93) 28->31 35 [ (91) 34->35 36 34->36 59 ] (93) 34->59 37 36->37 38 37->38 48 , 37->48 49 37->49 39 arg(arg= 38->39 40 38->40 47 ) (41) 38->47 41 ' (39) 40->41 42 40->42 46 ' (39) 40->46 43 <id_start> 42->43 45 <id_continue-1> 42->45 44 a (97) 43->44 50 arg(arg= 49->50 51 49->51 58 ) (41) 49->58 52 ' (39) 51->52 53 51->53 57 ' (39) 51->57 54 <id_start> 53->54 56 <id_continue-1> 53->56 55 b (98) 54->55 62 , kwonlyargs= 61->62 63 <arg_list> 61->63 64 [ (91) 63->64 65 63->65 66 ] (93) 63->66 68 , kw_defaults= 67->68 69 <expr_list> 67->69 70 [ (91) 69->70 71 69->71 72 ] (93) 69->72 75 , defaults= 74->75 76 <expr_list> 74->76 77 [ (91) 76->77 78 76->78 79 ] (93) 76->79 83 [ (91) 82->83 84 82->84 189 ] (93) 82->189 85 84->85 154 , 84->154 155 84->155 86 85->86 87 Assign(targets= 86->87 88 <nonempty_lhs_expr_list> 86->88 121 , value= 86->121 122 86->122 152 <type_comment-1> 86->152 153 ) (41) 86->153 89 [ (91) 88->89 90 <lhs_exprs> 88->90 120 ] (93) 88->120 91 <lhs_expr> 90->91 92 <lhs_Name> 91->92 93 Name(id= 92->93 94 92->94 119 , ctx=Store()) 92->119 95 ' (39) 94->95 96 94->96 118 ' (39) 94->118 97 <id_start> 96->97 99 <id_continue-1> 96->99 98 t (116) 97->98 100 <id_continue> 99->100 102 <id_continue-1> 99->102 101 h (104) 100->101 103 <id_continue> 102->103 105 <id_continue-1> 102->105 104 e (101) 103->104 106 <id_continue> 105->106 108 <id_continue-1> 105->108 107 _ (95) 106->107 109 <id_continue> 108->109 111 <id_continue-1> 108->111 110 s (115) 109->110 112 <id_continue> 111->112 114 <id_continue-1> 111->114 113 u (117) 112->113 115 <id_continue> 114->115 117 <id_continue-1> 114->117 116 m (109) 115->116 123 122->123 124 BinOp(left= 123->124 125 123->125 136 , op= 123->136 137 123->137 139 , right= 123->139 140 123->140 151 ) (41) 123->151 126 125->126 127 Name(id= 126->127 128 126->128 135 , ctx=Load()) 126->135 129 ' (39) 128->129 130 128->130 134 ' (39) 128->134 131 <id_start> 130->131 133 <id_continue-1> 130->133 132 a (97) 131->132 138 Add() 137->138 141 140->141 142 Name(id= 141->142 143 141->143 150 , ctx=Load()) 141->150 144 ' (39) 143->144 145 143->145 149 ' (39) 143->149 146 <id_start> 145->146 148 <id_continue-1> 145->148 147 b (98) 146->147 156 155->156 157 156->157 158 Return(value= 157->158 159 157->159 188 ) (41) 157->188 160 159->160 161 Name(id= 160->161 162 160->162 187 , ctx=Load()) 160->187 163 ' (39) 162->163 164 162->164 186 ' (39) 162->186 165 <id_start> 164->165 167 <id_continue-1> 164->167 166 t (116) 165->166 168 <id_continue> 167->168 170 <id_continue-1> 167->170 169 h (104) 168->169 171 <id_continue> 170->171 173 <id_continue-1> 170->173 172 e (101) 171->172 174 <id_continue> 173->174 176 <id_continue-1> 173->176 175 _ (95) 174->175 177 <id_continue> 176->177 179 <id_continue-1> 176->179 178 s (115) 177->178 180 <id_continue> 179->180 182 <id_continue-1> 179->182 181 u (117) 180->181 183 <id_continue> 182->183 185 <id_continue-1> 182->185 184 m (109) 183->184 191 , decorator_list= 190->191 192 <expr_list> 190->192 193 [ (91) 192->193 194 192->194 195 ] (93) 192->195 201 , type_ignores= 200->201 202 <type_ignore_list> 200->202 203 [ (91) 202->203 204 <type_ignores-1> 202->204 205 ] (93) 202->205

我们可以看到,推导树由非终端节点组成,其子节点构成了从语法中来的扩展。例如,在最顶层,我们看到一个<start>非终端扩展成一个<mod>非终端,而<mod>非终端又扩展成一个<Module>非终端。这完全来自语法规则

python_ast_grammar['<start>'] 
['<mod>']

python_ast_grammar['<mod>'] 
['<Module>']

<mod>的子节点是一个<Module>,它扩展成以下节点

  • (body=

  • <nonempty_stmt_list>

  • , type_ignores=

  • <type_ignore_list>

  • )

在这里,像(body=, type_ignores=这样的节点被称为终端节点(因为它们没有更多的元素可以扩展)。非终端如<nonempty_stmt_list>将在下面进一步扩展——特别是,<nonempty_stmt_list>扩展成一个<FunctionDef>节点,代表sum()的定义。

再次,结构严格遵循我们语法中的<Module>定义:

python_ast_grammar['<Module>'] 
['Module(body=<nonempty_stmt_list><type_ignore_param>)']

如果我们以深度优先、从左到右的顺序遍历树,并且只收集终端符号,我们就能获得我们解析的原字符串。将str()函数应用于推导树会得到 exactly that string:

str(sum_tree) 
"Module(body=[FunctionDef(name='sum', args=arguments(posonlyargs=[], args=[arg(arg='a'), arg(arg='b')], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Assign(targets=[Name(id='the_sum', ctx=Store())], value=BinOp(left=Name(id='a', ctx=Load()), op=Add(), right=Name(id='b', ctx=Load()))), Return(value=Name(id='the_sum', ctx=Load()))], decorator_list=[])], type_ignores=[])"

再次,我们可以将这个字符串转换成 AST(抽象语法树),从而获得我们的原始函数:

sum_ast = ast.fix_missing_locations(eval(str(sum_tree)))
print(ast.unparse(sum_ast)) 
def sum(a, b):
    the_sum = a + b
    return the_sum

修改输入

使用推导树,我们可以对我们的输入有一个结构化的表示。在我们的例子中,我们已经有 AST 了,为什么还要引入一个新的呢?答案是简单的:推导树还允许我们合成新的输入,因为我们有一个语法描述了它们的结构。

最值得注意的是,我们可以按照以下方式修改输入:

  1. 将输入解析成如上所示的推导树。

  2. 在推导树中随机选择一些节点<symbol>进行修改。

  3. 使用语法为<symbol>生成一个新的扩展。

  4. <symbol>的子节点替换为刚刚生成的扩展。

  5. 如有必要,重复此过程。

这是一个不错的编程任务,如果你想看看蓝图,可以看看这篇关于使用语法进行灰盒模糊测试的教程中的FragmentMutator

幸运的是,ISLa 已经为我们提供了执行此操作的功能。ISLaSolver.mutate()方法接受一个输入并根据语法规则对其进行变异。要变异的输入可以以推导树的形式给出,也可以以字符串的形式给出;其输出是一个推导树(这又可以转换成字符串)。

让我们在sum()函数上应用mutate()min_mutationsmax_mutations参数定义了应该执行多少次变异步骤;我们将两者都设置为 1,以便正好进行一次变异。

sum_mutated_tree = solver.mutate(sum_str, min_mutations=1, max_mutations=1) 
sum_mutated_ast = ast.fix_missing_locations(eval(str(sum_mutated_tree)))
print(ast.unparse(sum_mutated_ast)) 
def sum(a, b):
    the_sum = a + b
    return the_sum

玩弄上面的内容,看看变异的效果。注意,如果顶级节点(如<FunctionDef><Module>)被选中进行变异,那么sum()将被替换为完全不同的内容。否则,代码仍然与原始的sum()代码非常相似。

当然,我们增加变异的数量越多,代码看起来就越不同:

sum_mutated_tree = solver.mutate(sum_str, min_mutations=10, max_mutations=20) 
sum_mutated_ast = ast.fix_missing_locations(eval(str(sum_mutated_tree)))
print(ast.unparse(sum_mutated_ast)) 
def sum(a, b):
    the_9GuWCvL4cpgyi37K5I_ = a + b
    return the_jXHPe1oqMG

通过调整mutate()参数,我们可以控制我们的输入应该是多么常见和多么不常见

变异的有效性如何?

变异现有代码能帮助我们找到错误吗?让我们假设我们有一个有错误的编译器,它为形式为<elem> * (<elem> + <elem>)的表达式生成糟糕的代码。has_distributive_law()中的代码检查 AST 是否存在此错误:

def has_distributive_law(tree) -> bool:
    for node in walk(tree):  # iterate over all nodes in `tree`
        # print(node)
        if isinstance(node, ast.BinOp):
            if isinstance(node.op, ast.Mult):
                if isinstance(node.right, ast.BinOp):
                    if isinstance(node.right.op, ast.Add):
                        return True

                if isinstance(node.left, ast.BinOp):
                    if isinstance(node.left.op, ast.Add):
                        return True

    return False 

为了理解这是如何工作的,AST 的可视化非常有帮助:

show_ast(ast.parse("1 + (2 * 3)")) 

0 Expr 1 BinOp 0--1 2 Constant 1--2 4 Add 1--4 5 BinOp 1--5 3 1 2--3 6 Constant 5--6 8 Mult 5--8 9 Constant 5--9 7 2 6--7 10 3 9--10

has_distributive_law(ast.parse("1 * (2 + 3)")) 
True

has_distributive_law(ast.parse("(1 + 2) * 3")) 
True

has_distributive_law(ast.parse("1 + (2 * 3)")) 
False

has_distributive_law(ast.parse("def f(a, b):\n return a * (b + 10)")) 
True

我们需要多少次尝试才能找到触发has_distributive_law()函数错误的变异?让我们编写一个函数来计算这个数字。

def how_many_mutations(code: str) -> int:
    solver = ISLaSolver(python_ast_grammar)

    code_ast = ast.parse(code)
    code_ast = ast.fix_missing_locations(code_ast)
    code_ast_str = ast.dump(code_ast)
    code_derivation_tree = solver.parse(code_ast_str)
    mutations = 0
    mutated_code_ast = code_ast

    while not has_distributive_law(mutated_code_ast):
        mutations += 1
        if mutations % 100 == 0:
            print(f'{mutations}...', end='')

        mutated_code_str = str(solver.mutate(code_derivation_tree))
        mutated_code_ast = eval(mutated_code_str)
        # mutated_code_ast = ast.fix_missing_locations(mutated_code_ast)
        # print(ast.dump(mutated_code_ast))
        # print(ast.unparse(mutated_code_ast))

    return mutations 

如果我们传递一个已经表现出错误的输入,我们不需要任何变异:

assert how_many_mutations('1 * (2 + 3)') == 0 

然而,我们离错误越远,找到它所需的变异(和时间)就越多。值得注意的是,将2 + 2变异到具有分配律仍然比变异2要快得多。

how_many_mutations('2 + 2')    # <-- Note: this can take a minute 
54

how_many_mutations('2')  # <-- Note: this can take several minutes 
100...200...300...400...500...600...700...800...900...1000...1100...1200...1300...1400...1500...1600...1700...1800...1900...2000...2100...2200...2300...2400...2500...

2500

我们得出结论,变异现有代码确实是有帮助的,尤其是如果它在语法上接近触发错误的输入。如果你想有很好的机会找到错误,请专注于之前已经触发错误的输入——有时对这些输入进行简单的变异就已经有助于找到新的错误。

进化模糊测试

变异输入的一个有趣应用是使用变异进行进化模糊测试。想法是拥有一个输入种群,对它们应用变异,并检查它们是否在特定目标上有所改进(通常是代码覆盖率)。那些确实有所改进的输入被保留下来(“适者生存”)作为下一代,并进一步进化。通过重复这个过程足够多次,我们可能会获得覆盖大量代码的输入,从而提高发现错误的机会。

让我们假设我们有一个有缺陷的编译器,它为形式为 <elem> * (<elem> + <elem>) 的表达式生成糟糕的代码。上面的 has_distributive_law() 函数检查 AST 中是否存在这个错误。

我们的目的是通过模糊测试来检测这个错误。但如果我们简单地从头开始生成随机输入,可能需要很长时间才能生成触发错误的精确操作符组合。

获取覆盖率

要让我们的模糊测试器由覆盖率引导,我们首先需要 测量 代码覆盖率。我们使用了来自《模糊测试手册》的 覆盖率模块,它特别易于使用。它简单地使用一个 with 子句来从 with 子句中的代码获取覆盖率。以下是如何获取我们上面 has_distributive_law() 代码的覆盖率的方法:

from Coverage import Coverage 
mult_ast = ast.parse("1 * 2")
with Coverage() as cov:
    has_distributive_law(mult_ast) 

coverage() 方法告诉我们代码中哪些行实际上已经被执行到。这包括来自 has_distributive_law() 的行,也包括其他被调用的函数中的行。

cov.coverage() 
{('_handle_fromlist', 1217),
 ('_handle_fromlist', 1218),
 ('_handle_fromlist', 1225),
 ('_handle_fromlist', 1229),
 ('_handle_fromlist', 1241),
 ('has_distributive_law', 2),
 ('has_distributive_law', 4),
 ('has_distributive_law', 5),
 ('has_distributive_law', 6),
 ('has_distributive_law', 10),
 ('has_distributive_law', 14),
 ('iter_child_nodes', 272),
 ('iter_child_nodes', 273),
 ('iter_child_nodes', 274),
 ('iter_child_nodes', 275),
 ('iter_child_nodes', 276),
 ('iter_child_nodes', 277),
 ('iter_child_nodes', 278),
 ('iter_fields', 260),
 ('iter_fields', 261),
 ('iter_fields', 262),
 ('walk', 386),
 ('walk', 387),
 ('walk', 388),
 ('walk', 389),
 ('walk', 390),
 ('walk', 391)}

哪些行被执行了?通过一点代码检查,我们可以轻松地可视化已覆盖的行:

def show_coverage(cov, fun):
    fun_lines, fun_start = inspect.getsourcelines(fun)
    fun_name = fun.__name__
    coverage = cov.coverage()
    for line in range(len(fun_lines)):
        if (fun_name, line + fun_start) in coverage:
            print('# ', end='')  # covered lines
        else:
            print('  ', end='')  # uncovered lines
        print(line + fun_start, fun_lines[line], end='') 
show_coverage(cov, has_distributive_law) 
  1 def has_distributive_law(tree) -> bool:
# 2     for node in walk(tree):  # iterate over all nodes in `tree`
  3         # print(node)
# 4         if isinstance(node, ast.BinOp):
# 5             if isinstance(node.op, ast.Mult):
# 6                 if isinstance(node.right, ast.BinOp):
  7                     if isinstance(node.right.op, ast.Add):
  8                         return True
  9 
# 10                 if isinstance(node.left, ast.BinOp):
  11                     if isinstance(node.left.op, ast.Add):
  12                         return True
  13 
# 14     return False

在这个列表中,一个 # 表示代码已被执行(已覆盖)。我们看到我们的输入 "1 * 2" 满足第 4 行和第 5 行的条件,但不满足后续行的条件。

适应度

让我们现在使用覆盖率作为 适应度函数 来引导进化。适应度(覆盖率)越高,输入被保留用于进一步进化的可能性就越高。我们的 ast_fitness() 函数简单地计算 has_distributive_law() 中覆盖的行数。

def ast_fitness(code_ast) -> int:
    with Coverage() as cov:
        has_distributive_law(code_ast)
    lines = set()
    for (name, line) in cov.coverage():
        if name == has_distributive_law.__name__:
            lines.add(line)
    return len(lines) 

下面是一些给定输入的适应度:

ast_fitness(ast.parse("1")) 
3

ast_fitness(ast.parse("1 + 1")) 
4

ast_fitness(ast.parse("1 * 2")) 
6

ast_fitness(ast.parse("1 * (2 + 3)")) 
6

现在,让我们设置一个适应度函数,它接受推导树。本质上,我们的 tree_fitness() 函数基于上面的 ast_fitness() 函数;然而,我们还添加了一个小的组件 1 / len(code_str),以给较短的输入额外的适应度。否则,我们的输入可能会不断增长,使突变变得低效。

def tree_fitness(tree) -> float:
    code_str = str(tree)
    code_ast = ast.fix_missing_locations(eval(code_str))
    fitness = ast_fitness(code_ast)
    # print(ast.unparse(code_ast), f"\n=> Fitness = {fitness}\n")
    return fitness + 1 / len(code_str) 
tree_fitness(sum_tree) 
4.002666666666666

进化输入

让我们现在利用我们的适应度函数来实现一个简单的进化模糊测试算法。我们开始于 进化 —— 也就是说,通过突变来从一个种群中添加后代。我们的初始种群由一个候选者组成——在我们的例子中,sum_tree 反映了上面的 sum() 函数。

def initial_population(tree):
    return [ (tree, tree_fitness(tree)) ] 
sum_population = initial_population(sum_tree) 
len(sum_population) 
1

我们的 evolve() 函数为每个种群成员添加两个新的子代。

OFFSPRING = 2 
def evolve(population, min_fitness=-1):
    solver = ISLaSolver(python_ast_grammar)

    for (candidate, _) in list(population):
        for i in range(OFFSPRING):
            child = solver.mutate(candidate, min_mutations=1, max_mutations=1)
            child_fitness = tree_fitness(child)
            if child_fitness > min_fitness:
                population.append((child, child_fitness))
    return population 
sum_population = evolve(sum_population)
len(sum_population) 
3

由于我们可以进化所有这些,我们得到了指数级增长。

sum_population = evolve(sum_population)
len(sum_population) 
9

sum_population = evolve(sum_population)
len(sum_population) 
27

sum_population = evolve(sum_population)
len(sum_population) 
81

sum_population = evolve(sum_population)
len(sum_population) 
243

适者生存

没有个体可以无限扩张并仍然存活。因此,让我们将种群限制在一定的规模内。

POPULATION_SIZE = 100 

select() 函数实现了适者生存:它将种群限制在最多 POPULATION_SIZE 个元素,并按它们的适应度(从高到低)进行排序。适应度低于 POPULATION_SIZE 的成员无法存活。

def get_fitness(elem):
    (candidate, fitness) = elem
    return fitness

def select(population):
    population = sorted(population, key=get_fitness, reverse=True)
    population = population[:POPULATION_SIZE]
    return population 

我们可以使用以下调用来修剪我们的 sum_population 到最适应的成员:

sum_population = select(sum_population)
len(sum_population) 
100

进化

我们现在已经准备好了所有东西:

  • 我们有一个 种群(比如说,sum_population

  • 我们可以通过evolve()函数进化种群

  • 我们只能让最适应的生存下来(使用select()

让我们在几代中重复这个过程。我们跟踪每次找到新的“最佳”候选者并记录它们。如果我们找到一个触发错误的候选者,我们就停止。请注意,这可能会花费很长时间,并且不一定能产生完美的结果。

在基于搜索的方法中很常见,如果我们经过几代之后还没有找到足够的解决方案,我们就停止并重新开始搜索(这里:GENERATIONS)。除此之外,我们会继续搜索,直到我们找到一个解决方案。

GENERATIONS = 100  # Upper bound 
trial = 1
found = False

while not found:
    sum_population = initial_population(sum_tree)
    prev_best_fitness = -1

    for generation in range(GENERATIONS):
        sum_population = evolve(sum_population, min_fitness=prev_best_fitness)
        sum_population = select(sum_population)
        best_candidate, best_fitness = sum_population[0]
        if best_fitness > prev_best_fitness:
            print(f"Generation {generation}: found new best candidate (fitness={best_fitness}):")
            best_ast = ast.fix_missing_locations(eval(str(best_candidate)))
            print(ast.unparse(best_ast))
            prev_best_fitness = best_fitness

            if has_distributive_law(best_ast):
                print("Done!")
                found = True
                break

    trial = trial + 1
    print(f"\n\nRestarting; trial #{trial}") 
Generation 0: found new best candidate (fitness=4.002666666666666):
def sum(a, b):
    the_sum = a + b
    return the_sum
Generation 1: found new best candidate (fitness=4.0027027027027025):
def sum(a, b):
    the_sum = a + b
    return FE
Generation 4: found new best candidate (fitness=4.002865329512894):
def sum():
    the_sum = a + b
    return the_sum
Generation 5: found new best candidate (fitness=6.00094696969697):
if set()[:] * *set():

    def sum(a, b):
        mc = a + b
        return FE
else:
    M = set()
continue

set().f[set():set()]()
Generation 7: found new best candidate (fitness=7.002364066193853):
def sum(a, b):
    mc = (a + b) * ()
    return FE
Done!

Restarting; trial #2

成功!我们找到了一段触发错误的代码。检查分配律的出现。

print(ast.unparse(best_ast)) 
def sum(a, b):
    mc = (a + b) * ()
    return FE

assert has_distributive_law(best_ast) 

你可能会注意到并非所有代码都是触发错误的必要条件。我们可以让我们的进化模糊器运行得更久一些,看看它是否可以被进一步减少,或者使用专门的输入减少技术,例如Delta Debugging

进化模糊测试的可能性

distributive_law()中的错误在没有进化指导的情况下能否被发现 - 也就是说,仅仅通过将一个变异应用到sum()上?

当产生一个表达式(<expr>)时,我们计算触发概率有多大

  • 产生一个二元运算符,并且

  • 产生一个*,并且

  • 产生另一个二元运算符作为子节点,并且

  • 产生一个+

让我们在我们的语法上进行一些查询来计算概率。

assert '<BinOp>' in python_ast_grammar['<expr>'] 
len(python_ast_grammar['<expr>']) 
15

assert 'Add()' in python_ast_grammar['<operator>']
assert 'Mult()' in python_ast_grammar['<operator>'] 
len(python_ast_grammar['<operator>']) 
13

(len(python_ast_grammar['<expr>'])       # chances of choosing a `BinOp`
* len(python_ast_grammar['<operator>'])  # chances of choosing a `*`
* len(python_ast_grammar['<expr>'])      # chances of choosing a `BinOp` as a child
* len(python_ast_grammar['<operator>'])  # chances of choosing a `+`
/ 2)   # two chances - one for the left child, one for the right 
19012.5

平均来说,我们需要大约 19000 次(非进化)运行,直到我们得到一个触发分配律的表达式。所以,利用额外的信息(比如覆盖率)来引导变异向目标进化是绝对更好的。

经验教训

  • 当创建和处理复杂输入,例如程序代码时,

    • 尝试依赖现有的基础设施将输入解析成某种抽象语法,然后

    • 让你的语法处理那个抽象语法而不是具体语法。

  • 特别是,程序代码通常在编译或解释之前被转换成抽象语法树,你可以(并且应该)利用这种转换。

  • 一旦程序代码被转换成抽象语法树(AST),尽管其复杂,但生成、变异和进化都相对容易。

背景

编译器测试领域的开创性工作是Csmith [杨等人,2011],一个 C 程序生成器。Csmith 已被用于彻底测试编译器,如 Clang 或 GCC;除了产生语法正确的代码外,它还旨在实现语义正确性以及避免未定义和未指定行为。这是编译器测试领域的人必读的。

Creative Commons License 本项目的内容受Creative Commons Attribution-NonCommercial-ShareAlike 4.0 国际许可协议许可。作为内容一部分的源代码,以及用于格式化和显示该内容的源代码,受MIT 许可协议许可。 最后修改:2024-11-24 21:37:28+01:00 • 引用 • 版权信息

如何引用本作品

Andreas Zeller, Rahul Gopinath, Marcel Böhme, Gordon Fraser, and Christian Holler: "测试编译器". In Andreas Zeller, Rahul Gopinath, Marcel Böhme, Gordon Fraser, and Christian Holler, "模糊测试书籍", www.fuzzingbook.org/html/PythonFuzzer.html. Retrieved 2024-11-24 21:37:28+01:00.

@incollection{fuzzingbook2024:PythonFuzzer,
    author = {Andreas Zeller and Rahul Gopinath and Marcel B{\"o}hme and Gordon Fraser and Christian Holler},
    booktitle = {The Fuzzing Book},
    title = {Testing Compilers},
    year = {2024},
    publisher = {CISPA Helmholtz Center for Information Security},
    howpublished = {\url{https://www.fuzzingbook.org/html/PythonFuzzer.html}},
    note = {Retrieved 2024-11-24 21:37:28+01:00},
    url = {https://www.fuzzingbook.org/html/PythonFuzzer.html},
    urldate = {2024-11-24 21:37:28+01:00}
}

posted @ 2025-12-13 18:14  绝不原创的飞龙  阅读(0)  评论(0)    收藏  举报