# 生成器的模糊化生成器和语法覆盖率

> 原文： [https://www.fuzzingbook.org/html/GeneratorGrammarFuzzer.html](https://www.fuzzingbook.org/html/GeneratorGrammarFuzzer.html)

在本章中，我们将展示如何使用*函数*扩展语法-在语法扩展过程中执行的，可以生成，检查或更改所生成元素的代码段。 向语法添加功能可实现非常通用的测试，从而将最佳的语法生成和编程结合在一起。

**前提条件**

*   由于本章与[关于有效语法模糊](GrammarFuzzer.html)的一章中讨论的技术有很深的互动，因此建议您对这些技术有一个很好的了解。

## 内容提要

要使用本章中提供的代码来[，请编写](Importing.html)

```py
>>> from [fuzzingbook.GeneratorGrammarFuzzer](GeneratorGrammarFuzzer.html) import <identifier>

```

然后利用以下功能。

本章介绍将*函数*附加到各个生产规则的能力：

*   在展开之前，先执行*功能`pre`。 其结果（通常是字符串）可以*代替*实际的扩展。*
*   扩展后，将执行*功能。 如果返回字符串，则该字符串将替换扩展名；否则，将替换扩展名。 返回`False`，则触发新的扩展。*

这两个函数都可以返回`None`，完全不会干扰语法的产生。

要将函数`F`附加到语法中的单个扩展`S`，请用一对替换`S`

```py
(opts(S, pre=F))   # Set a function to be executed before expansion

```

要么

```py
(opts(S, post=F))  # Set a function to be executed after expansion

```

这是一个示例，要从以编程方式给出的列表中获取区号，我们可以编写：

```py
>>> from [Grammars](Grammars.html) import US_PHONE_GRAMMAR, extend_grammar, opts
>>> def pick_area_code():
>>>     return random.choice(['555', '554', '553'])
>>> PICKED_US_PHONE_GRAMMAR = extend_grammar(US_PHONE_GRAMMAR,
>>> {
>>>     "<area>": [("<lead-digit><digit><digit>", opts(pre=pick_area_code))]
>>> })

```

`GeneratorGrammarFuzzer`将提取并解释这些选项。 这是一个例子：

```py
>>> picked_us_phone_fuzzer = GeneratorGrammarFuzzer(PICKED_US_PHONE_GRAMMAR)
>>> [picked_us_phone_fuzzer.fuzz() for i in range(5)]
['(553)200-6118',
 '(553)889-0205',
 '(555)317-0936',
 '(553)455-2577',
 '(553)263-8511']

```

如您所见，现在区号都源自`pick_area_code()`。 这样的定义允许将程序代码（例如`pick_area_code()`）与语法紧密联系在一起。

`PGGCFuzzer`类包含[，`GrammarFuzzer`类](GrammarFuzzer.html)及其基于[覆盖率](GrammarCoverageFuzzer.html)，基于[概率](ProbabilisticGrammarFuzzer.html)和基于[生成器 衍生物。

## 示例：测试信用卡系统

假设您使用的购物系统-除其他功能外-允许客户使用信用卡付款。 您的任务是测试付款功能。

为简单起见，我们假设我们只需要两笔数据-16位信用卡号和要收取的金额。 这两部分都可以使用语法轻松生成，如下所示：

```py
import [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils)

```

```py
from [Grammars](Grammars.html) import EXPR_GRAMMAR, is_valid_grammar, is_nonterminal, extend_grammar
from [Grammars](Grammars.html) import opts, exp_opt, exp_string, crange

```

```py
CHARGE_GRAMMAR = {
    "<start>": ["Charge <amount> to my credit card <credit-card-number>"],
    "<amount>": ["$<float>"],
    "<float>": ["<integer>.<digit><digit>"],
    "<integer>": ["<digit>", "<integer><digit>"],
    "<digit>": crange('0', '9'),

    "<credit-card-number>": ["<digits>"],
    "<digits>": ["<digit-block><digit-block><digit-block><digit-block>"],
    "<digit-block>": ["<digit><digit><digit><digit>"],
}

assert is_valid_grammar(CHARGE_GRAMMAR)

```

所有这些工作都很巧妙-我们可以生成任意金额和信用卡号：

```py
from [GrammarFuzzer](GrammarFuzzer.html) import GrammarFuzzer, all_terminals, display_tree

```

```py
g = GrammarFuzzer(CHARGE_GRAMMAR)
[g.fuzz() for i in range(5)]

```

```py
['Charge $9.40 to my credit card 7166898575638313',
 'Charge $8.79 to my credit card 6845418694643271',
 'Charge $5.64 to my credit card 6655894657077388',
 'Charge $0.60 to my credit card 2596728464872261',
 'Charge $8.90 to my credit card 2363769342732142']

```

但是，当使用这些数据实际测试我们的系统时，我们发现两个问题：

1.  我们想测试*特定的*金额-例如，超出信用卡限额的金额。
2.  我们发现10个信用卡号中有9个由于校验和不正确而被拒绝。 如果我们想测试信用卡号的拒绝功能就可以了-但是，如果我们想测试处理收费的实际功能，我们需要*有效的*号。

我们可以忽略这些问题； 毕竟，最终产生大量有效数字只是时间问题。 对于第一个问题，我们也可以通过适当地更改语法来解决它，例如，仅产生至少具有六个前导数字的费用。 但是，将其推广到任意值范围将很麻烦。

第二个问题是信用卡号的校验和，但是，它更深入地研究了-至少就语法而言，是像校验和这样的复杂算术运算不能仅用语法来表达-至少不能在*中表达 我们在这里使用无上下文语法*。 （原则上，*可以在*上下文相关的*语法中执行*，但指定这样做根本没有意思。）我们想要的是一种允许我们进行*的机制。 ]将程序化计算*附加到我们的语法中，汇集了两全其美的方法。

## 将功能附加到扩展

本章的主要思想是*扩展*语法，以便*可以将Python函数*附加到各个扩展中。 这些功能可以执行

1.  扩展之前的*，*用计算值替换要扩展的元素*； 要么*
2.  扩展后的*，*检查*生成的元素，并可能还会替换它们。*

在这两种情况下，都使用[语法](Grammars.html)一章中介绍的`opts()`扩展机制指定功能。 因此，它们与符号$ s $的特定扩展$ e $相关联。

### 扩展前调用的函数

在将$ s $扩展为$ e $之前，使用`pre`选项定义的函数将被调用*。 它的值*替换了*要生产的扩展$ e $。 要为上述信用卡示例生成一个值，我们可以定义一个*预展开*生成器函数*

```py
import [random](https://docs.python.org/3/library/random.html)

```

```py
def high_charge():
    return random.randint(10000000, 90000000) / 100.0

```

使用`opts()`，我们可以将此函数附加到语法上：

```py
CHARGE_GRAMMAR.update({
    "<float>": [("<integer>.<digit><digit>", opts(pre=high_charge))],
})

```

目的是每当`<float>`扩展时，将调用函数`high_charge`为`<float>`生成一个值。 （对于忽略诸如`GrammarFuzzer`之类的功能的模糊器，仍将存在语法的实际扩展）。

由于与语法相关的函数通常非常简单，因此我们也可以使用 *lambda* 表达式对*内联*。 *lambda表达式*用于*匿名*功能，这些功能在范围和功能上受到限制。 这是一个例子：

```py
def apply_twice(function, x):
    return function(function(x))

```

```py
apply_twice(lambda x: x * x, 2)

```

```py
16

```

在这里，我们不必给两次使用的`function`命名（例如`square()`）； 相反，我们在调用中内联应用它。

使用`lambda`，我们的语法如下所示：

```py
CHARGE_GRAMMAR.update({
    "<float>": [("<integer>.<digit><digit>",
                 opts(pre=lambda: random.randint(10000000, 90000000) / 100.0))]
})

```

### 扩展后调用的函数

在将$ s $的扩展为$ e $之后，使用`post`选项定义的函数被调用*，并将$ e $中符号的扩展值作为参数传递。 扩展后功能可以通过两种方式发挥作用：*

1.  它可以用作扩展值的*约束*或*过滤器*，如果扩展有效，则返回`True`，否则为`False`； 如果返回`False`，则尝试另一个扩展。
2.  它也可以用作*修复程序*，返回一个字符串值。 像预扩展功能一样，返回值将替换扩展。

对于我们的信用卡示例，我们可以选择两种方式。 如果我们有一个函数`check_credit_card(s)`，它为有效数字`s`返回`True`，对于无效数字返回`False`，那么我们将选择第一个选项：

```py
CHARGE_GRAMMAR.update({
    "<credit-card-number>": [("<digits>", opts(post=lambda digits: check_credit_card(digits)))]
})

```

使用这种过滤器，只会生成有效的信用卡。 平均而言，每次`check_credit_card()`满足时，仍然需要尝试10次，但是，我们不必求助于被测系统。

如果我们有一个函数`fix_credit_card(s)`，它可以更改数字以使校验和有效并返回“固定”数字，则可以改用该函数：

```py
CHARGE_GRAMMAR.update({
    "<credit-card-number>": [("<digits>", opts(post=lambda digits: fix_credit_card(digits)))]
})

```

在此，每个编号仅生成一次，然后进行修复。 这非常有效。

用于信用卡的校验和函数是 [Luhn算法](https://en.wikipedia.org/wiki/Luhn_algorithm)，这是一个简单而有效的公式。

```py
def luhn_checksum(s):
    """Compute Luhn's check digit over a string of digits"""
    LUHN_ODD_LOOKUP = (0, 2, 4, 6, 8, 1, 3, 5, 7,
                       9)  # sum_of_digits (index * 2)

    evens = sum(int(p) for p in s[-1::-2])
    odds = sum(LUHN_ODD_LOOKUP[int(p)] for p in s[-2::-2])
    return (evens + odds) % 10

```

```py
def valid_luhn_checksum(s):
    """Check whether the last digit is Luhn's checksum over the earlier digits"""
    return luhn_checksum(s[:-1]) == int(s[-1])

```

```py
def fix_luhn_checksum(s):
    """Return the given string of digits, with a fixed check digit"""
    return s[:-1] + repr(luhn_checksum(s[:-1]))

```

```py
luhn_checksum("123")

```

```py
8

```

```py
fix_luhn_checksum("123x")

```

```py
'1238'

```

我们可以在信用卡语法中使用以下功能：

```py
check_credit_card = valid_luhn_checksum
fix_credit_card = fix_luhn_checksum

fix_credit_card("1234567890123456")

```

```py
'1234567890123458'

```

## 集成约束的类

虽然很容易指定函数，但是我们的语法模糊器将忽略它们，就像忽略所有扩展一样。 它将发出警告，但是：

```py
g = GrammarFuzzer(CHARGE_GRAMMAR)
g.fuzz()

```

```py
warning: option 'pre' is not supported
warning: option 'post' is not supported

```

```py
'Charge $4.05 to my credit card 0637034038177393'

```

我们需要定义一个特殊的模糊器，该模糊器实际上会调用给定的`pre`和`post`函数并相应地执行操作。 我们将其命名为`GeneratorGrammarFuzzer`：

```py
class GeneratorGrammarFuzzer(GrammarFuzzer):
    def supported_opts(self):
        return super().supported_opts() | {"pre", "post", "order"}

```

我们定义了自定义函数来访问`pre`和`post`选项：

```py
def exp_pre_expansion_function(expansion):
    """Return the specified pre-expansion function, or None if unspecified"""
    return exp_opt(expansion, 'pre')

```

```py
def exp_post_expansion_function(expansion):
    """Return the specified post-expansion function, or None if unspecified"""
    return exp_opt(expansion, 'post')

```

`order`属性将在本章稍后的中使用。

## 扩展前生成元素

我们的第一个任务将是实现预扩展功能-即在扩展之前替换以替换要扩展的值的功能。 为此，我们将使用`process_chosen_children()`方法，该方法将在扩展之前获取选定的子代。 我们对其进行设置，以使其调用给定的`pre`函数，并将其结果应用于子级，可能会替换它们。

```py
import [inspect](https://docs.python.org/3/library/inspect.html)

```

```py
class GeneratorGrammarFuzzer(GeneratorGrammarFuzzer):
    def process_chosen_children(self, children, expansion):
        function = exp_pre_expansion_function(expansion)
        if function is None:
            return children

        assert callable(function)
        if inspect.isgeneratorfunction(function):
            # See "generators", below
            result = self.run_generator(expansion, function)
        else:
            result = function()

        if self.log:
            print(repr(function) + "()", "=", repr(result))
        return self.apply_result(result, children)

```

方法`apply_result()`从预扩展功能获取结果并将其应用于子级。 确切的效果取决于结果的类型：

*   *字符串* $ s $用$ s $替换了整个扩展。
*   *列表* $ [x_1，x_2，\ dots，x_n] $用不是`None`的每个$ x_i $用$ x_i $替换第$ i $个符号。 将`None`指定为列表元素$ x_i $可以使该元素保持不变。 如果$ x_i $不是字符串，则将其转换为字符串。
*   值`None`被忽略。 如果只想在扩展时调用一个函数而对扩展的字符串不起作用，则这很有用。
*   *布尔值*会被忽略。 这对后述的扩展功能很有用。
*   所有其他*类型*都将转换为字符串，从而替换整个扩展。

```py
class GeneratorGrammarFuzzer(GeneratorGrammarFuzzer):
    def apply_result(self, result, children):
        if isinstance(result, str):
            children = [(result, [])]
        elif isinstance(result, list):
            symbol_indexes = [i for i, c in enumerate(
                children) if is_nonterminal(c[0])]

            for index, value in enumerate(result):
                if value is not None:
                    child_index = symbol_indexes[index]
                    if not isinstance(value, str):
                        value = repr(value)
                    if self.log:
                        print(
                            "Replacing", all_terminals(
                                children[child_index]), "by", value)

                    # children[child_index] = (value, [])
                    child_symbol, _ = children[child_index]
                    children[child_index] = (child_symbol, [(value, [])])
        elif result is None:
            pass
        elif isinstance(result, bool):
            pass
        else:
            if self.log:
                print("Replacing", "".join(
                    [all_terminals(c) for c in children]), "by", result)

            children = [(repr(result), [])]

        return children

```

### 示例：数值范围

通过上述扩展，我们完全支持预扩展功能。 使用增强的`CHARGE_GRAMMAR`，我们发现实际上使用了预扩展`lambda`函数：

```py
charge_fuzzer = GeneratorGrammarFuzzer(CHARGE_GRAMMAR)
charge_fuzzer.fuzz()

```

```py
'Charge $439383.87 to my credit card 2433506594138520'

```

日志揭示了更多细节，当调用预扩展功能时会发生什么。 我们看到扩展`<integer>.<digit><digit>`被计算值直接替换：

```py
amount_fuzzer = GeneratorGrammarFuzzer(
    CHARGE_GRAMMAR, start_symbol="<amount>", log=True)
amount_fuzzer.fuzz()

```

```py
Tree: <amount>
Expanding <amount> randomly
Tree: $<float>
Expanding <float> randomly
<function <lambda> at 0x10806e2f0>() = 382087.72
Replacing <integer>.<digit><digit> by 382087.72
Tree: $382087.72
'$382087.72'

```

```py
'$382087.72'

```

### 示例：更多数值范围

我们也可以在其他情况下使用此类预扩展功能。 假设我们要生成每个数字都在100到200之间的算术表达式。我们可以相应地扩展`EXPR_GRAMMAR`：

```py
expr_100_200_grammar = extend_grammar(EXPR_GRAMMAR,
                                      {
                                          "<factor>": [
                                              "+<factor>", "-<factor>", "(<expr>)",

                                              # Generate only the integer part with a function;
                                              # the fractional part comes from
                                              # the grammar
                                              ("<integer>.<integer>", opts(
                                                  pre=lambda: [random.randint(100, 200), None])),

                                              # Generate the entire integer
                                              # from the function
                                              ("<integer>", opts(
                                                  pre=lambda: random.randint(100, 200))),
                                          ],
                                      }
                                      )

```

```py
expr_100_200_fuzzer = GeneratorGrammarFuzzer(expr_100_200_grammar)
expr_100_200_fuzzer.fuzz()

```

```py
'(108.6 / 155 + 177) / 118 * 120 * 107 + 151 + 195 / -200 - 150 * 188 / 147 + 112'

```

### 支持Python生成器

Python语言具有自己的生成器功能概念，我们当然也希望支持。 Python 中的*生成器函数是一个返回所谓的*迭代器对象*的函数，我们可以一次迭代一个值。*

要在Python中创建一个生成器函数，可以使用`yield`语句而不是`return`语句定义一个普通函数。 当`return`语句终止该函数时，`yield`语句暂停其执行，保存其所有状态，以便稍后在下一个连续调用中恢复。

这是生成器功能的示例。 首次调用时，`iterate()`产生值1，然后是2、3，依此类推：

```py
def iterate():
    t = 0
    while True:
        t = t + 1
        yield t

```

我们可以像`range()`函数（这也是一个生成器函数）一样循环使用`iterate`：

```py
for i in iterate():
    if i > 10:
        break
    print(i, end=" ")

```

```py
1 2 3 4 5 6 7 8 9 10 

```

我们还可以将`iterate()`用作预扩展生成器函数，确保它将创建一个接一个的连续整数：

```py
iterate_grammar = extend_grammar(EXPR_GRAMMAR,
                                 {
                                     "<factor>": [
                                         "+<factor>", "-<factor>", "(<expr>)",
                                         # "<integer>.<integer>",

                                         # Generate one integer after another
                                         # from the function
                                         ("<integer>", opts(pre=iterate)),
                                     ],
                                 })

```

为了支持生成器，我们上面的`process_chosen_children()`方法检查一个函数是否是生成器； 如果是这样，它将调用`run_generator()`方法。 当`run_generator()`在`fuzz_tree()`（或`fuzz()`）调用中首次看到该函数时，它会调用该函数来创建一个生成器对象。 它将保存在`generators`属性中，然后调用。 随后的调用直接进入生成器，保持状态。

```py
class GeneratorGrammarFuzzer(GeneratorGrammarFuzzer):
    def fuzz_tree(self):
        self.reset_generators()
        return super().fuzz_tree()

    def reset_generators(self):
        self.generators = {}

    def run_generator(self, expansion, function):
        key = repr((expansion, function))
        if key not in self.generators:
            self.generators[key] = function()
        generator = self.generators[key]
        return next(generator)

```

这样行吗？ 让我们使用`iterator()`在上述语法上运行我们的模糊器：

```py
iterate_fuzzer = GeneratorGrammarFuzzer(iterate_grammar)
iterate_fuzzer.fuzz()

```

```py
'1 * ++++3 / ---+4 - 2 * +--6 / 7 * 10 - (9 - 11) - 5 + (13) * 14 + 8 + 12'

```

我们看到该表达式包含以1开头的所有整数。

除了指定自己的Python生成器函数（例如`iterate()`），我们还可以使用内置的Python生成器之一（例如`range()`）。 这还将生成以1开头的整数：

```py
iterate_grammar = extend_grammar(EXPR_GRAMMAR,
                                 {
                                     "<factor>": [
                                         "+<factor>", "-<factor>", "(<expr>)",
                                         ("<integer>", opts(pre=range(1, 1000))),
                                     ],
                                 })

```

通过在括号中添加它们的生成器函数，也可以使用Python列表推导：

```py
iterate_grammar = extend_grammar(EXPR_GRAMMAR,
                                 {
                                     "<factor>": [
                                         "+<factor>", "-<factor>", "(<expr>)",
                                         ("<integer>", opts(
                                             pre=(x for x in range(1, 1000)))),
                                     ],
                                 })

```

请注意，以上两种语法实际上会在创建1000个以上的整数时使模糊器引发异常，但是您将发现很容易解决此问题。

最后，`yield`实际上是一个表达式，而不是语句，因此也可以使`lambda`表达式`yield`为一个值。 如果您对此找到了合理的用途，请告诉我们。

## 扩展后检查和修复元素

现在让我们转向要支持的第二组功能-即扩展后功能。 使用它们的最简单方法是在生成整个树后立即运行它们，并像`pre`函数一样照顾替换。 但是，如果其中之一返回`False`，我们将重新开始。

```py
class GeneratorGrammarFuzzer(GeneratorGrammarFuzzer):
    def fuzz_tree(self):
        while True:
            tree = super().fuzz_tree()
            (symbol, children) = tree
            result, new_children = self.run_post_functions(tree)
            if not isinstance(result, bool) or result:
                return (symbol, new_children)
            self.restart_expansion()

    def restart_expansion(self):
        # To be overloaded in subclasses
        self.reset_generators()

```

方法`run_post_functions()`递归应用于派生树的所有节点。 对于每个节点，它确定应用的扩展，然后运行与该扩展关联的功能。

```py
class GeneratorGrammarFuzzer(GeneratorGrammarFuzzer):
    # Return True iff all constraints of grammar are satisfied in TREE
    def run_post_functions(self, tree, depth=float("inf")):
        symbol, children = tree
        if children == []:
            return True, children  # Terminal symbol

        try:
            expansion = self.find_expansion(tree)
        except KeyError:
            # Expansion (no longer) found - ignore
            return True, children

        result = True
        function = exp_post_expansion_function(expansion)
        if function is not None:
            result = self.eval_function(tree, function)
            if isinstance(result, bool) and not result:
                if self.log:
                    print(
                        all_terminals(tree),
                        "did not satisfy",
                        symbol,
                        "constraint")
                return False, children

            children = self.apply_result(result, children)

        if depth > 0:
            for c in children:
                result, _ = self.run_post_functions(c, depth - 1)
                if isinstance(result, bool) and not result:
                    return False, children

        return result, children

```

辅助方法`find_expansion()`接受一个子树`tree`，并从用于在`tree`中创建子级的语法确定扩展。

```py
class GeneratorGrammarFuzzer(GeneratorGrammarFuzzer):
    def find_expansion(self, tree):
        symbol, children = tree

        applied_expansion = \
            "".join([child_symbol for child_symbol, _ in children])

        for expansion in self.grammar[symbol]:
            if exp_string(expansion) == applied_expansion:
                return expansion

        raise KeyError(
            symbol +
            ": did not find expansion " +
            repr(applied_expansion))

```

方法`eval_function()`是负责实际调用后扩展功能的方法。 它创建一个包含所有非终结子级扩展的参数列表，即语法扩展中每个符号的一个参数。 然后，它调用给定的函数。

```py
class GeneratorGrammarFuzzer(GeneratorGrammarFuzzer):
    def eval_function(self, tree, function):
        symbol, children = tree

        assert callable(function)

        args = []
        for (symbol, exp) in children:
            if exp != [] and exp is not None:
                symbol_value = all_terminals((symbol, exp))
                args.append(symbol_value)

        result = function(*args)
        if self.log:
            print(repr(function) + repr(tuple(args)), "=", repr(result))

        return result

```

请注意，与扩展前的函数不同，扩展后的函数通常会处理已经生成的值，因此此处我们不支持Python生成器。

### 示例：负表达式

让我们在一个示例上尝试这些后表达功能。 假设我们只想生成计算结果为负数的算术表达式-例如，将生成的表达式馈送到编译器或其他外部系统中。 用`pre`函数进行构造性的操作将非常困难。 相反，我们可以使用Python `eval()`函数定义一个严格检查此属性的约束。

Python `eval()`函数采用一个字符串，并根据Python规则对其求值。 由于我们生成的表达式的语法与Python稍有不同，并且由于Python可以在求值过程中引发算术异常，因此我们需要一种能够优雅地处理此类错误的方法。 函数`eval_with_exception()`环绕`eval()`； 如果评估期间发生异常，则返回False-这会导致生产算法产生另一个值。

```py
from [ExpectError](ExpectError.html) import ExpectError

```

```py
def eval_with_exception(s):
    # Use "mute=True" to suppress all messages
    with ExpectError(print_traceback=False):
        return eval(s)
    return False

```

```py
negative_expr_grammar = extend_grammar(EXPR_GRAMMAR,
                                       {
                                           "<start>": [("<expr>", opts(post=lambda s: eval_with_exception(s) < 0))]
                                       }
                                       )

assert is_valid_grammar(negative_expr_grammar)

```

```py
negative_expr_fuzzer = GeneratorGrammarFuzzer(negative_expr_grammar)
expr = negative_expr_fuzzer.fuzz()
expr

```

```py
ZeroDivisionError: division by zero (expected)

```

```py
'(8.9 / 6 * 4 - 0.2 + -7 - 7 - 8 * 6) * 7 * 15.55 - -945.9'

```

结果确实是负面的：

```py
eval(expr)

```

```py
-5178.726666666667

```

### 示例：匹配XML标签

扩展后功能不仅可以用于*检查*扩展，还可以对其进行修复。 为此，我们可以让它们返回一个字符串或字符串列表。 就像预扩展功能一样，这些字符串将替换整个扩展或单个符号。

例如，考虑 *XML文档*，它们由匹配的 *XML标签*中的文本组成。 例如，考虑以下HTML片段，它是XML的子集：

```py
from [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils) import HTML

```

```py
HTML("<strong>A bold text</strong>")

```

**A bold text**

该片段由围绕文本的两个HTML（XML）标签组成。 标签名（`strong`）出现在开头（`<strong>`）和结尾（`</strong>`）标签中。

对于*有限的*标签集（例如HTML标签`<strong>`，`<head>`，`<body>`，`<form>`等），我们可以定义一个无上下文语法来解析 它; 每对标记将构成语法中的单个规则。 但是，如果标记集是*无限*，则与常规XML一样，我们无法定义适当的语法； 那是因为结束标记必须与开始标记匹配的约束是上下文相关的，因此不适合上下文无关的语法。

（顺便说一句，如果结束标记的标识符*颠倒了*（`</gnorts>`），那么可以使用上下文无关的语法来描述它。将此作为编程练习。）

我们可以通过引入适当的后扩展功能来解决此问题，这些功能会自动使结束标记与开始标记匹配。 让我们从产生XML树的简单语法开始：

```py
XML_GRAMMAR = {
    "<start>": ["<xml-tree>"],
    "<xml-tree>": ["<<id>><xml-content></<id>>"],
    "<xml-content>": ["Text", "<xml-tree>"],
    "<id>": ["<letter>", "<id><letter>"],
    "<letter>": crange('a', 'z')
}

assert is_valid_grammar(XML_GRAMMAR)

```

如果我们使用此语法进行模糊测试，则会得到不匹配的XML标记，如预期的那样：

```py
xml_fuzzer = GrammarFuzzer(XML_GRAMMAR)
xml_fuzzer.fuzz()

```

```py
'<t><qju>Text</m></q>'

```

设置后扩展功能，将第二个标识符设置为在第一个标识符中找到的字符串即可解决此问题：

```py
XML_GRAMMAR.update({
    "<xml-tree>": [("<<id>><xml-content></<id>>",
                    opts(post=lambda id1, content, id2: [None, None, id1])
                    )]
})

assert is_valid_grammar(XML_GRAMMAR)

```

```py
xml_fuzzer = GeneratorGrammarFuzzer(XML_GRAMMAR)
xml_fuzzer.fuzz()

```

```py
'<u>Text</u>'

```

### 示例：校验和

作为最后一个示例，让我们考虑一下引言中的校验和问题。 使用我们新定义的修复机制，我们现在可以生成有效的信用卡号：

```py
credit_card_fuzzer = GeneratorGrammarFuzzer(
    CHARGE_GRAMMAR, start_symbol="<credit-card-number>")
credit_card_number = credit_card_fuzzer.fuzz()
credit_card_number

```

```py
'2967308746680770'

```

```py
assert valid_luhn_checksum(credit_card_number)

```

有效性扩展到整个语法：

```py
charge_fuzzer = GeneratorGrammarFuzzer(CHARGE_GRAMMAR)
charge_fuzzer.fuzz()

```

```py
'Charge $818819.97 to my credit card 2817984968014288'

```

## 本地检查和修复

到目前为止，我们总是首先生成一个完整的表达式树，之后再检查它的有效性。 这可能会变得很昂贵：如果首先生成多个元素只是后来发现它们中的一个无效，我们将花费大量时间尝试（随机）重新生成匹配的输入。

为了演示这个问题，让我们创建一个表达式语法，其中所有数字都由零和一组成。 但是，我们不是使用建设性的方法，而是使用`post`约束过滤掉所有不符合条件的表达式：

```py
binary_expr_grammar = extend_grammar(EXPR_GRAMMAR,
                                     {
                                         "<integer>": [("<digit><integer>", opts(post=lambda digit, _: digit in ["0", "1"])),
                                                       ("<digit>", opts(post=lambda digit: digit in ["0", "1"]))]
                                     }
                                     )

assert is_valid_grammar(binary_expr_grammar)

```

这可行，但是非常慢； 找到匹配的表达式可能需要花费几秒钟的时间。

```py
binary_expr_fuzzer = GeneratorGrammarFuzzer(binary_expr_grammar)
binary_expr_fuzzer.fuzz()

```

```py
'(-+0)'

```

我们可以通过不仅检查最终子树的约束，而且还检查部分子树的约束来解决问题。 为此，我们扩展了方法`expand_tree_once()`，以便一旦子树中的所有符号都被扩展，它就会调用后扩展功能。

```py
class GeneratorGrammarFuzzer(GeneratorGrammarFuzzer):
    def expand_tree_once(self, tree):
        # Apply inherited method.  This also calls `expand_tree_once()` on all
        # subtrees.
        new_tree = super().expand_tree_once(tree)

        (symbol, children) = new_tree
        if all([exp_post_expansion_function(expansion)
                is None for expansion in self.grammar[symbol]]):
            # No constraints for this symbol
            return new_tree

        if self.any_possible_expansions(tree):
            # Still expanding
            return new_tree

        return self.run_post_functions_locally(new_tree)

```

主要工作在此辅助方法`run_post_functions_locally()`中进行。 通过将`depth`设置为零，它仅在当前节点上使用`run_post_functions()`运行扩展后功能$ f $，因为任何完成的子树都已经运行了它们的扩展后功能。 如果$ f $返回`False`，则`run_post_functions_locally()`返回未扩展的符号，以便主驱动程序可以尝试另一种扩展。 它最多可以执行10次（可在构建过程中通过`replacement_attempts`参数进行配置）； 之后，它会提出一个`RestartExpansionException`以重新开始从头开始创建树。

```py
class GeneratorGrammarFuzzer(GeneratorGrammarFuzzer):
    def run_post_functions_locally(self, new_tree):
        symbol, _ = new_tree

        result, children = self.run_post_functions(new_tree, depth=0)
        if not isinstance(result, bool) or result:
            # No constraints, or constraint satisfied
            # children = self.apply_result(result, children)
            new_tree = (symbol, children)
            return new_tree

        # Replace tree by unexpanded symbol and try again
        if self.log:
            print(
                all_terminals(new_tree),
                "did not satisfy",
                symbol,
                "constraint")

        if self.replacement_attempts_counter > 0:
            if self.log:
                print("Trying another expansion")
            self.replacement_attempts_counter -= 1
            return (symbol, None)

        if self.log:
            print("Starting from scratch")
        raise RestartExpansionException

```

```py
class RestartExpansionException(Exception):
    pass

```

设置类构造函数方法和`fuzz_tree()`以处理其他功能：

```py
class GeneratorGrammarFuzzer(GeneratorGrammarFuzzer):
    def __init__(self, grammar, replacement_attempts=10, **kwargs):
        super().__init__(grammar, **kwargs)
        self.replacement_attempts = replacement_attempts

    def restart_expansion(self):
        super().restart_expansion()
        self.replacement_attempts_counter = self.replacement_attempts

    def fuzz_tree(self):
        self.replacement_attempts_counter = self.replacement_attempts
        while True:
            try:
                # This is fuzz_tree() as defined above
                tree = super().fuzz_tree()
                return tree
            except RestartExpansionException:
                self.restart_expansion()

```

```py
binary_expr_fuzzer = GeneratorGrammarFuzzer(
    binary_expr_grammar, replacement_attempts=100)
binary_expr_fuzzer.fuzz()

```

```py
'+0 / +-1 - 1 / +0 * -+0 * 0 * 1 / 1'

```

## 定义和使用

利用上述生成器和约束，我们还可以解决复杂的示例。 [解析器](Parser.html)一章中的`VAR_GRAMMAR`语法将许多变量定义为算术表达式（它们也可以包含变量）。 在语法上应用简单的`GrammarFuzzer`会产生大量标识符，但是每个标识符都有一个唯一的名称。

```py
from [LangFuzzer](LangFuzzer.html) import VAR_GRAMMAR  # minor dependency

```

```py
g = GrammarFuzzer(VAR_GRAMMAR)
for i in range(10):
    print(g.fuzz())

```

```py
Gc=F/1*Y+M-D-9;N=n/(m)/m*7
a=79.0;W=o-9;v=2;K=u;D=9
o=y-z+y+4;q=5+W;X=T
M=-98.032*5/o
H=IA-5-1;n=3-t;QQ=5-5
Y=-80;d=D-M+M;Z=4.3+1*r-5+b
ZDGSS=(1*Y-4)*54/0*pcO/4;RI=r*5.0
Q=6+z-6;J=6/t/9/i-3-5+k
x=-GT*+-x*6++-93*5
q=da*T/e--v;x=3+g;bk=u

```

我们想要的是在表达式中，仅应使用先前定义的标识符。 为此，我们围绕*符号表*引入了一组函数，该函数跟踪已定义的所有变量。

```py
SYMBOL_TABLE = set()

```

```py
def define_id(id):
    SYMBOL_TABLE.add(id)

```

```py
def use_id():
    if len(SYMBOL_TABLE) == 0:
        return False

    id = random.choice(list(SYMBOL_TABLE))
    return id

```

```py
def clear_symbol_table():
    global SYMBOL_TABLE
    SYMBOL_TABLE = set()

```

为了使用符号表，我们在`VAR_GRAMMAR`上附加了扩展前和扩展后函数，这些函数定义和查找符号表中的标识符。 我们将扩展语法命名为`CONSTRAINED_VAR_GRAMMAR`：

```py
CONSTRAINED_VAR_GRAMMAR = extend_grammar(VAR_GRAMMAR)

```

首先，我们设置语法，以便在每次定义标识符后，将其名称存储在符号表中：

```py
CONSTRAINED_VAR_GRAMMAR = extend_grammar(CONSTRAINED_VAR_GRAMMAR, {
    "<assignment>": [("<identifier>=<expr>",
                      opts(post=lambda id, expr: define_id(id)))]
})

```

其次，我们确保在生成标识符时，也从符号表中选择它。 （我们在此处使用`post`，以便在尚无可用标识符的情况下可以返回`False`，从而导致进行另一次扩展。）

```py
CONSTRAINED_VAR_GRAMMAR = extend_grammar(CONSTRAINED_VAR_GRAMMAR, {
    "<factor>": ['+<factor>', '-<factor>', '(<expr>)',
                 ("<identifier>", opts(post=lambda _: use_id())),
                 '<number>']
})

```

最后，每次（重新）开始扩展时，我们都要清除符号表。 这很有用，因为我们有时可能不得不重新启动扩展。

```py
CONSTRAINED_VAR_GRAMMAR = extend_grammar(CONSTRAINED_VAR_GRAMMAR, {
    "<start>": [("<statements>", opts(pre=clear_symbol_table))]
})

```

```py
assert is_valid_grammar(CONSTRAINED_VAR_GRAMMAR)

```

对此语法进行模糊处理可确保实际上定义了每个使用的标识符：

```py
var_grammar_fuzzer = GeneratorGrammarFuzzer(CONSTRAINED_VAR_GRAMMAR)
for i in range(10):
    print(var_grammar_fuzzer.fuzz())

```

```py
DB=+(8/4/7-9+3+3)/2178/+-9
lNIqc=+(1+9-8)/2.9*8/5*0
Sg=(+9/8/6)*++1/(1+7)*8*4
r=+---552
iz=5/7/7;K=1+6*iz*1
q=3-2;MPy=q;p=2*5
zj=+5*-+35.2-+1.5727978+(-(-0/6-7+3))*--+44*1
Tl=((0*9+4-3)-6)/(-3-7*8*8/7)+9
aXZ=-5/-+3*9/3/1-8-+0*0/3+7+4
NA=-(8+a-1)*1.6;g=++7;a=++g*g*g

```

## 订购扩展

虽然我们前面的def / use示例确保使用的每个*变量*也是*定义的*变量，但是它并不需要考虑其中进行这些定义的*顺序* 。 实际上，可能首先会扩展`;`右侧的术语，从而在符号表中创建一个条目，然后将其用于左侧的表达式中。 我们可以通过使用`exec()`执行分配序列，在Python中实际评估产生的变量分配来证明这一点。 （鲜为人知的事实：Python *是否支持*作为语句分隔符。）

```py
var_grammar_fuzzer = GeneratorGrammarFuzzer(CONSTRAINED_VAR_GRAMMAR)
with ExpectError():
    for i in range(100):
        s = var_grammar_fuzzer.fuzz()
        try:
            exec(s, {}, {})
        except SyntaxError:
            continue
        except ZeroDivisionError:
            continue
print(s)

```

```py
f=(9)*kOj*kOj-6/7;kOj=(9-8)*7*1

```

```py
Traceback (most recent call last):
  File "<ipython-input-75-7d8198a01aa0>", line 6, in <module>
    exec(s, {}, {})
  File "<string>", line 1, in <module>
NameError: name 'kOj' is not defined (expected)

```

为了解决这个问题，我们允许显式指定扩展的*顺序。 对于我们以前的模糊器来说，这样的排序是无关紧要的，因为最终所有符号都将被扩展。 但是，如果我们具有具有副作用的扩展功能，那么控制扩展的顺序（以及控制相关功能的调用顺序）就很重要。*

为了指定顺序，我们为各个扩展指定了特殊属性`order`。 这是一个列表，在扩展中的每个符号都有一个数字，表示从最小的顺序开始进行扩展的顺序。 例如，以下规则指定首先应扩展`;`分隔符的左侧：

```py
CONSTRAINED_VAR_GRAMMAR = extend_grammar(CONSTRAINED_VAR_GRAMMAR, {
    "<statements>": [("<statement>;<statements>", opts(order=[1, 2])),
                     "<statement>"]
})

```

同样，我们希望仅在表达式扩展后的之后才生成变量的定义，因为否则，表达式可能已经引用了定义的变量：

```py
CONSTRAINED_VAR_GRAMMAR = extend_grammar(CONSTRAINED_VAR_GRAMMAR, {
    "<assignment>": [("<identifier>=<expr>", opts(post=lambda id, expr: define_id(id),
                                                  order=[2, 1]))],
})

```

助手`exp_order()`允许我们检索订单：

```py
def exp_order(expansion):
    """Return the specified expansion ordering, or None if unspecified"""
    return exp_opt(expansion, 'order')

```

为了控制符号扩展的顺序，我们使用了`choose_tree_expansion()`方法，该方法专门为在子类中扩展而设置。 它遍历可扩展子项列表`expandable_children`进行选择，并将它们与扩展中的非末子项进行匹配，以确定其订单号。 然后返回订单号最低的可扩展子项的索引`min_given_order`，选择该子项进行扩展。

```py
class GeneratorGrammarFuzzer(GeneratorGrammarFuzzer):
    def choose_tree_expansion(self, tree, expandable_children):
        """Return index of subtree in `children` to be selected for expansion.  Defaults to random."""
        (symbol, tree_children) = tree
        if len(expandable_children) == 1:
            # No choice
            return super().choose_tree_expansion(tree, expandable_children)

        expansion = self.find_expansion(tree)
        given_order = exp_order(expansion)
        if given_order is None:
            # No order specified
            return super().choose_tree_expansion(tree, expandable_children)

        nonterminal_children = [c for c in tree_children if c[1] != []]
        assert len(nonterminal_children) == len(given_order), \
            "Order must have one element for each nonterminal"

        # Find expandable child with lowest ordering
        min_given_order = None
        j = 0
        for k, expandable_child in enumerate(expandable_children):
            while j < len(
                    nonterminal_children) and expandable_child != nonterminal_children[j]:
                j += 1
            assert j < len(nonterminal_children), "Expandable child not found"
            if self.log:
                print("Expandable child #%d  %s has order %d" %
                      (k, expandable_child[0], given_order[j]))

            if min_given_order is None or given_order[j] < min_given_order:
                min_given_order = k

        assert min_given_order is not None

        if self.log:
            print("Returning expandable child #%d  %s" %
                  (min_given_order, expandable_children[min_given_order][0]))

        return min_given_order

```

这样，我们的模糊器现在可以遵守顺序，并且所有变量都已正确定义：

```py
var_grammar_fuzzer = GeneratorGrammarFuzzer(CONSTRAINED_VAR_GRAMMAR)
for i in range(100):
    s = var_grammar_fuzzer.fuzz()
    if i < 10:
        print(s)
    try:
        exec(s, {}, {})
    except SyntaxError:
        continue
    except ZeroDivisionError:
        continue

```

```py
SAl=4/-(2.92+5.27)/-4*0/6/3
Z=+-7/+5.519*68/4*-+-(5/7+4)*(8*-1/0-1+6-0);o=2
j=-(8+1+5)-+8/7*5-2/9+4;t=j
S=-(9+9-1)--5+-8*4-1+8-2
VImBX=06/7*6*7++-(+5*7*2+9-0)
f=-7;j=(f*f*0)/-5*(f-f)-f*-1/f
W=85.3*-++(9-5)/76.82/-2/++8*7;j=W
xb=1*5*1*1-+0*3*7+2+9+0
Edd=(4+8)/+4-+8*2*7+++9*+-+08.9
u=-++33*+-6/(2*3*2+1/9-4*1-8)*((8.5))

```

真正的编程语言不仅具有一个全局范围，而且具有多个局部范围（通常嵌套在一起）。 通过仔细组织全局和局部符号表，我们可以建立一种语法来处理所有这些。 但是，在模糊编译器和解释器时，我们通常只关注单个函数，对于单个函数，单个作用域足以使大多数输入有效。

## 全部在一起

让我们通过将生成器功能与先前介绍的其他语法功能集成在一起来结束本章，尤其是[覆盖范围驱动的模糊测试](GrammarCoverageFuzzer.html)和[概率语法模糊测试](ProbabilisticGrammarFuzzer.html)。

集成单个功能的总体思路是通过*多重继承*，我们已经将其用于`ProbabilisticGrammarCoverageFuzzer`，并已在[概率模糊](ProbabilisticGrammarFuzzer.html)练习中引入。

### 生成器和概率模糊

概率模糊非常容易与生成器集成，因为二者以不同的方式扩展了`GrammarFuzzer`。

```py
from [ProbabilisticGrammarFuzzer](ProbabilisticGrammarFuzzer.html) import ProbabilisticGrammarFuzzer  # minor dependency

```

```py
from [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils) import inheritance_conflicts

```

```py
inheritance_conflicts(ProbabilisticGrammarFuzzer, GeneratorGrammarFuzzer)

```

```py
['supported_opts']

```

我们必须将`supported_opts()`实现为两个超类的合并。 同时，我们还设置了构造函数，使其可以同时调用两者。

```py
class ProbabilisticGeneratorGrammarFuzzer(GeneratorGrammarFuzzer,
                                          ProbabilisticGrammarFuzzer):
    def supported_opts(self):
        return (super(GeneratorGrammarFuzzer, self).supported_opts() |
                super(ProbabilisticGrammarFuzzer, self).supported_opts())

    def __init__(self, grammar, replacement_attempts=10, **kwargs):
        super(
            GeneratorGrammarFuzzer,
            self).__init__(
            grammar,
            replacement_attempts)
        super(ProbabilisticGrammarFuzzer, self).__init__(grammar, **kwargs)

```

让我们给联合类一个简单的测试，使用概率来支持长标识符：

```py
CONSTRAINED_VAR_GRAMMAR.update({
    '<word>': [('<alpha><word>', opts(prob=0.9)),
               '<alpha>'],
})

```

```py
pgg_fuzzer = ProbabilisticGeneratorGrammarFuzzer(CONSTRAINED_VAR_GRAMMAR)
pgg_fuzzer.supported_opts()

```

```py
{'order', 'post', 'pre', 'prob'}

```

```py
pgg_fuzzer.fuzz()

```

```py
'hfaHpaxvv=+-(8*-(7)*5-(4))/+-(-5*-43);h=7'

```

基于语法覆盖的模糊测试是一个更大的挑战。 对于这两种方法中都重载的方法，并没有那么多； 我们可以如上所述解决这些问题。

```py
from [ProbabilisticGrammarFuzzer](ProbabilisticGrammarFuzzer.html) import ProbabilisticGrammarCoverageFuzzer  # minor dependency

```

```py
from [GrammarCoverageFuzzer](GrammarCoverageFuzzer.html) import GrammarCoverageFuzzer  # minor dependency

```

```py
inheritance_conflicts(
    ProbabilisticGrammarCoverageFuzzer,
    GeneratorGrammarFuzzer)

```

```py
['__init__', 'supported_opts']

```

```py
class ProbabilisticGeneratorGrammarCoverageFuzzer(GeneratorGrammarFuzzer,
                                                  ProbabilisticGrammarCoverageFuzzer):
    def supported_opts(self):
        return (super(GeneratorGrammarFuzzer, self).supported_opts() |
                super(ProbabilisticGrammarCoverageFuzzer, self).supported_opts())

    def __init__(self, grammar, replacement_attempts=10, **kwargs):
        super(
            GeneratorGrammarFuzzer,
            self).__init__(
            grammar,
            replacement_attempts)
        super(
            ProbabilisticGrammarCoverageFuzzer,
            self).__init__(
            grammar,
            **kwargs)

```

问题在于，在扩展过程中，我们*可能会*生成（并覆盖）我们随后删除的扩展（例如，因为`post`函数返回`False`）。 因此，我们必须*删除*最终生产中不再存在的此覆盖范围。

我们通过在生成后的最终树中重建*的覆盖率*来解决该问题。 为此，我们将使用`fuzz_tree()`方法。 我们让它在创建树之前保存原始coverage，然后将其还原。 然后我们遍历生成的树，再次将其覆盖范围重新添加（`add_tree_coverage()`）。

```py
import [copy](https://docs.python.org/3/library/copy.html)

```

```py
class ProbabilisticGeneratorGrammarCoverageFuzzer(
        ProbabilisticGeneratorGrammarCoverageFuzzer):
    def fuzz_tree(self):
        self.orig_covered_expansions = copy.deepcopy(self.covered_expansions)
        tree = super().fuzz_tree()
        self.covered_expansions = self.orig_covered_expansions
        self.add_tree_coverage(tree)
        return tree

    def add_tree_coverage(self, tree):
        (symbol, children) = tree
        if len(children) > 0:
            flat_children = [(child_symbol, None)
                             for (child_symbol, _) in children]
            self.add_coverage(symbol, flat_children)
            for c in children:
                self.add_tree_coverage(c)

```

作为最后一步，我们确保如果必须从头开始重新启动扩展，我们还将还原先前的覆盖范围，以便我们可以重新开始：

```py
class ProbabilisticGeneratorGrammarCoverageFuzzer(
        ProbabilisticGeneratorGrammarCoverageFuzzer):
    def restart_expansion(self):
        super().restart_expansion()
        self.covered_expansions = self.orig_covered_expansions

```

让我们尝试一下。 生成字符串后，我们应该在`expansion_coverage()`中看到其覆盖范围：

```py
pggc_fuzzer = ProbabilisticGeneratorGrammarCoverageFuzzer(
    CONSTRAINED_VAR_GRAMMAR)
pggc_fuzzer.fuzz()

```

```py
'sM=7/5*9++(-6/2/8)-4+1;g=3'

```

```py
pggc_fuzzer.expansion_coverage()

```

```py
{'<alpha> -> M',
 '<alpha> -> g',
 '<alpha> -> s',
 '<assignment> -> <identifier>=<expr>',
 '<digit> -> 1',
 '<digit> -> 2',
 '<digit> -> 3',
 '<digit> -> 4',
 '<digit> -> 5',
 '<digit> -> 6',
 '<digit> -> 7',
 '<digit> -> 8',
 '<digit> -> 9',
 '<expr> -> <term>',
 '<expr> -> <term>+<expr>',
 '<expr> -> <term>-<expr>',
 '<factor> -> (<expr>)',
 '<factor> -> +<factor>',
 '<factor> -> -<factor>',
 '<factor> -> <number>',
 '<identifier> -> <word>',
 '<integer> -> <digit>',
 '<number> -> <integer>',
 '<start> -> <statements>',
 '<statement> -> <assignment>',
 '<statements> -> <statement>',
 '<statements> -> <statement>;<statements>',
 '<term> -> <factor>',
 '<term> -> <factor>*<term>',
 '<term> -> <factor>/<term>',
 '<word> -> <alpha>',
 '<word> -> <alpha><word>'}

```

再次进行模糊测试将最终覆盖标识符中的所有字母：

```py
[pggc_fuzzer.fuzz() for i in range(10)]

```

```py
['tIbFSBC=0/7/8+9*2/1*9;m=7',
 'LHkGcwXqTUzODjWhRruVo=6/4/5/6+1+7+5*1-2',
 'QYePndypZKxNafAElvILv=551.3;ZRhYGwEfIDYSfiunr=02.45*(2+0)+3*QYePndypZKxNafAElvILv+QYePndypZKxNafAElvILv/7',
 'uIQHzf=(4/8)/5/-8-+(9-3+9+8+0)/++-2',
 'DWNVzzWELL=((3+4)-++4/9*0+67)/(50169.1*3)',
 'NVKZ=-011/++82.4*-(5*6*6)/97.3/-3/1',
 'O=+(2*(8)*2+(7/0))/+-6*+0.3*8',
 'RMfynoao=-+02/(7)*8--9+8*5/3-5+9',
 'k=2/3/4.7*6---+-3;q=1',
 'MR=49*1++21-5/6/2*5;x=MR']

```

有了`ProbabilisticGeneratorGrammarCoverageFuzzer`，我们现在有了一个语法模糊器，它将有效的语法模糊与覆盖率，概率和生成器功能结合在一起。 唯一缺少的是缩写。 `PGGCFuzzer`，也许？

```py
PGGCFuzzer = ProbabilisticGeneratorGrammarCoverageFuzzer

```

## 经验教训

语法扩展附带的功能可以使用

*   作为*生成器*，可以有效地从函数产生符号扩展；
*   由于*约束*对照（复杂）有效性条件检查生成的字符串； 和
*   如*修复*以将更改应用于所产生的字符串，例如校验和和标识符。

## 后续步骤

在本章中，我们具有强大的语法，可以在许多领域中使用：

*   在有关模糊化API 的[一章中，我们展示了如何利用`GeneratorGrammarFuzzer`功能来组合语法和生成器功能，从而生成用于测试的复杂数据结构。](APIFuzzer.html)
*   在[关于模糊用户界面](WebFuzzer.html)的章节中，我们利用`GeneratorGrammarFuzzer`生成复杂的用户界面输入。

## 背景

对于模糊API，生成器功能非常常见。 在API模糊测试的[一章中，我们展示了如何将它们与语法结合起来以产生更丰富的测试。](APIFuzzer.html)

生成器函数和语法的组合是最可能的，因为我们在全Python环境中定义和使用了语法。 我们不知道另一个具有类似功能的基于语法的模糊系统。

## 练习

### 练习1：树处理

到目前为止，我们的`pre`和`post`处理函数都接受并产生字符串。 但是，在某些情况下，直接访问*派生树*很有用-例如，访问和检查某些子元素。

您的任务是使用可以接受和返回派生树的预处理和后处理功能扩展`GeneratorGrammarFuzzer`。 为此，请按照下列步骤操作：

1.  扩展`GeneratorGrammarFuzzer`，以使函数可以返回派生树（元组）或派生树列表，然后以与字符串相同的方式替换子树。
2.  用`post_tree`属性扩展`GeneratorGrammarFuzzer`，该属性具有与`post`类似的功能，不同之处在于其参数将是派生树。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/GeneratorGrammarFuzzer.ipynb#Exercises) to work on the exercises and see solutions.

### 练习2：属性文法

建立一种机制，通过该机制可以将任意*属性*附加到派生树中的各个元素。 扩展功能可以将此类属性附加到各个符号上（例如，通过返回`opts()`），还可以在以后的调用中访问符号的属性。 这是一个例子：

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/GeneratorGrammarFuzzer.ipynb#Exercises) to work on the exercises and see solutions.

```py
ATTR_GRAMMAR = {
    "<clause>": [("<xml-open>Text<xml-close>", opts(post=lambda x1, x2: [None, x1.name]))],
    "<xml-open>": [("<<tag>>", opts(post=lambda tag: opts(name=random_name())))],
    "<xml-close>": ["</<tag>>"]
}

```

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/GeneratorGrammarFuzzer.ipynb#Exercises) to work on the exercises and see solutions.