# 用文法模糊处理

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

在[“基于突变的模糊检测”](MutationFuzzer.html) 一章中，我们了解了如何使用额外的提示（例如样本输入文件）来加快测试生成速度。 在本章中，我们通过为程序提供合法输入的*规范*来使这一想法更进一步。 通过*语法*指定输入可以进行非常系统和有效的测试生成，尤其是对于复杂的输入格式。 语法还充当配置模糊测试，API模糊测试，GUI模糊测试等的基础。

**前提条件**

*   您应该知道基本的模糊测试如何工作，例如 [一章中介绍了模糊](Fuzzer.html)。
*   基于[突变的模糊](MutationFuzzer.html)和[覆盖率](Coverage.html)的知识尚不需要*，但仍推荐使用。*

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

```

```py
import [Fuzzer](Fuzzer.html)

```

## 内容提要

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

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

```

然后利用以下功能。

本章介绍*语法*，作为指定输入语言并将其用于测试具有语法有效输入的程序的简单方法。 语法定义为非终结符到替代扩展列表的映射，如以下示例所示：

```py
>>> US_PHONE_GRAMMAR = {
>>>     "<start>": ["<phone-number>"],
>>>     "<phone-number>": ["(<area>)<exchange>-<line>"],
>>>     "<area>": ["<lead-digit><digit><digit>"],
>>>     "<exchange>": ["<lead-digit><digit><digit>"],
>>>     "<line>": ["<digit><digit><digit><digit>"],
>>>     "<lead-digit>": ["2", "3", "4", "5", "6", "7", "8", "9"],
>>>     "<digit>": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
>>> }
>>> 
>>> assert is_valid_grammar(US_PHONE_GRAMMAR)

```

非终端符号括在尖括号中（例如`<digit>`）。 为了从语法生成输入字符串，*生产者*以起始符号（`<start>`）开头，并为该符号随机选择一个随机扩展。 它将继续该过程，直到所有非终结符都展开为止。 函数`simple_grammar_fuzzer()`可以做到：

```py
>>> [simple_grammar_fuzzer(US_PHONE_GRAMMAR) for i in range(5)]
['(692)449-5179',
 '(519)230-7422',
 '(613)761-0853',
 '(979)881-3858',
 '(810)914-5475']

```

但是，实际上，您应该使用[类，](GrammarFuzzer.html)类或基于[覆盖率的](GrammarCoverageFuzzer.html)，基于[概率性的](ProbabilisticGrammarFuzzer.html)而不是  [基于生成器的](GeneratorGrammarFuzzer.html)衍生物； 这些功能更加有效，可防止无限增长，并提供其他一些功能。

本章还介绍了[语法工具箱](#A-Grammar-Toolbox)，该工具箱具有多个辅助功能，可简化语法的编写，例如对字符类和重复使用快捷方式符号或扩展语法

## 输入语言

程序的所有可能的行为都可以通过其输入来触发。 这里的“输入”可以有很多种可能的来源：我们正在谈论从文件，环境或网络中读取的数据，用户输入的数据或通过与其他资源交互而获取的数据。 所有这些输入的集合决定了程序的行为方式-包括其失败。 因此，在测试时，考虑可能的输入源，如何控制它们以及*如何系统地测试它们*非常有帮助。

为了简单起见，我们现在假设该程序只有一个输入源； 这也是我们在前几章中一直使用的假设。 程序的有效输入集称为*语言*。 语言的范围从简单到复杂：CSV语言表示有效的逗号分隔输入的集合，而Python语言表示有效的Python程序的集合。 我们通常将数据语言和编程语言分开，尽管任何程序也可以视为输入数据（例如，对于编译器）。 文件格式的 [Wikipedia页面列出了1,000多种不同的文件格式，每种格式都是其自己的语言。](https://en.wikipedia.org/wiki/List_of_file_formats)

为了正式描述语言，*正式语言*领域已经设计了许多描述语言的*语言规范*。 *正则表达式*表示这些语言中表示字符串集的最简单的类：例如，正则表达式`[a-z]*`表示小写字母（可能为空）的序列。 *自动机理论*将这些语言与接受这些输入的自动机联系起来； 例如，*有限状态机*可用于指定正则表达式的语言。

正则表达式非常适合不太复杂的输入格式，并且关联的有限状态机具有许多使它们适合推理的属性。 但是，要指定更复杂的输入，它们很快就会遇到限制。 在语言频谱的另一端，我们有*通用语法*，它们表示 *Turing机器*接受的语言。 图灵机可以计算任何可以计算的东西。 并且由于Python是图灵完备的，这意味着我们还可以使用Python程序$ p $来指定甚至枚举合法输入。 但是，然后，计算机科学理论也告诉我们，每个这样的测试程序都必须专门针对要测试的程序编写，这不是我们想要的自动化水平。

## 语法

正则表达式和图灵机之间的中间立场由*语法*覆盖。 语法是用于正式指定输入语言的最流行（和最佳理解）形式主义之一。 使用一种语法，可以表达一种输入语言的多种属性。 语法对于表示输入的*语法结构*特别有用，并且是表示嵌套或递归输入的形式化选择。 我们使用的语法是所谓的*上下文无关文法*，这是最简单和最受欢迎的语法形式主义之一。

### 规则和扩展

语法由*起始符号*和一组*扩展规则*（或简称为*规则*）组成，这些规则指示如何扩展起始符号（和其他符号） 。 例如，请考虑以下语法，表示两个数字的序列：

```py
<start> ::= <digit><digit>
<digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
```

要阅读此类语法，请从开始符号（`<start>`）开始。 扩展规则`<A> ::= <B>`表示左侧的符号（`<A>`）可以由右侧的字符串（`<B>`）代替。 在以上语法中，`<start>`将替换为`<digit><digit>`。

再次在此字符串中，`<digit>`将替换为`<digit>`规则右侧的字符串。 特殊运算符`|`表示*扩展替代项*（或简称为*替代项*），意味着可以为扩展选择任何数字。 因此，每个`<digit>`都将扩展为给定的数字之一，最终在`00`和`99`之间产生一个字符串。 `0`到`9`没有进一步的扩展，因此我们都准备就绪。

关于语法的有趣之处在于它们可以是*递归*。 也就是说，扩展可以利用之前扩展的符号-然后再将其扩展。 例如，考虑描述整数的语法：

```py
<start>  ::= <integer>
<integer> ::= <digit> | <digit><integer>
<digit>   ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
```

此处，`<integer>`可以是一个数字，也可以是后面跟另一个整数的数字。 因此，数字`1234`将被表示为一个数字`1`，后跟一个整数`234`，后者又是一个数字`2`，然后是整数`34`。

如果我们想表达一个整数可以以一个符号（`+`或`-`）开头，我们可以将语法写为

```py
<start>   ::= <number>
<number>  ::= <integer> | +<integer> | -<integer>
<integer> ::= <digit> | <digit><integer>
<digit>   ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
```

这些规则正式定义了语言：可以从起始符号派生的任何内容都是该语言的一部分； 没有的一切都不是。

### 算术表达式

让我们扩展语法以涵盖完整的*算术表达式*-语法的后代子示例。 我们看到表达式（`<expr>`）是和，差，或项。 术语是产品或部门或因素； 因素是数字或带括号的表达式。 几乎所有规则都可以递归，因此可以使用任意复杂的表达式，例如`(1 + 2) * (3.4 / 5.6 - 789)`。

```py
<start>   ::= <expr>
<expr>    ::= <term> + <expr> | <term> - <expr> | <term>
<term>    ::= <term> * <factor> | <term> / <factor> | <factor>
<factor>  ::= +<factor> | -<factor> | (<expr>) | <integer> | <integer>.<integer>
<integer> ::= <digit><integer> | <digit>
<digit>   ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
```

在这样的语法中，如果我们以`<start>`开头，然后将一个符号扩展为另一个，随机选择替代符号，则可以快速地生成一个有效的算术表达式。 这种*语法模糊*可以产生复杂的输入，因此非常有效，这就是我们将在本章中实现的功能。

## 在Python 中表示语法

构建语法模糊器的第一步是为语法找到合适的格式。 为了使语法的编写尽可能简单，我们使用基于字符串和列表的格式。 我们在Python中的语法采用符号名称和扩展名之间的*映射*的格式，其中扩展名是*列表的*。 因此，数字的单规则语法采用以下形式：

```py
DIGIT_GRAMMAR = {
    "<start>":
        ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
}

```

而算术表达式的完整语法如下所示：

```py
EXPR_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"]
}

```

在语法中，每个符号可以定义一次。 我们可以通过其符号访问任何规则...

```py
EXPR_GRAMMAR["<digit>"]

```

```py
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

```

....我们可以检查语法中是否包含符号：

```py
"<identifier>" in EXPR_GRAMMAR

```

```py
False

```

请注意，我们假设在规则的左侧（即映射中的键）始终是单个符号。 这是使我们的语法具有*上下文无关*的特征的属性。

## 一些定义

我们假设规范的开始符号是`<start>`：

```py
START_SYMBOL = "<start>"

```

方便的`nonterminals()`函数从扩展中提取非终结符列表（即`<`和`>`之间的任何字符，空格除外）。

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

```

```py
RE_NONTERMINAL = re.compile(r'(<[^<> ]*>)')

```

```py
def nonterminals(expansion):
    # In later chapters, we allow expansions to be tuples,
    # with the expansion being the first element
    if isinstance(expansion, tuple):
        expansion = expansion[0]

    return re.findall(RE_NONTERMINAL, expansion)

```

```py
assert nonterminals("<term> * <factor>") == ["<term>", "<factor>"]
assert nonterminals("<digit><integer>") == ["<digit>", "<integer>"]
assert nonterminals("1 < 3 > 2") == []
assert nonterminals("1 <3> 2") == ["<3>"]
assert nonterminals("1 + 2") == []
assert nonterminals(("<1>", {'option': 'value'})) == ["<1>"]

```

同样，`is_nonterminal()`检查某个符号是否为非终结符：

```py
def is_nonterminal(s):
    return re.match(RE_NONTERMINAL, s)

```

```py
assert is_nonterminal("<abc>")
assert is_nonterminal("<symbol-1>")
assert not is_nonterminal("+")

```

## 一个简单的语法模糊器

现在让我们使用以上语法。 我们将构建一个非常简单的语法模糊器，以起始符号（`<start>`）开头，然后继续对其进行扩展。 为了避免扩展为无限输入，我们对非终端数设置了限制（`max_nonterminals`）。 此外，为了避免陷入无法再减少符号数的情况，我们还限制了扩展步骤的总数。

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

```

```py
class ExpansionError(Exception):
    pass

```

```py
def simple_grammar_fuzzer(grammar, start_symbol=START_SYMBOL,
                          max_nonterminals=10, max_expansion_trials=100,
                          log=False):
    term = start_symbol
    expansion_trials = 0

    while len(nonterminals(term)) > 0:
        symbol_to_expand = random.choice(nonterminals(term))
        expansions = grammar[symbol_to_expand]
        expansion = random.choice(expansions)
        new_term = term.replace(symbol_to_expand, expansion, 1)

        if len(nonterminals(new_term)) < max_nonterminals:
            term = new_term
            if log:
                print("%-40s" % (symbol_to_expand + " -> " + expansion), term)
            expansion_trials = 0
        else:
            expansion_trials += 1
            if expansion_trials >= max_expansion_trials:
                raise ExpansionError("Cannot expand " + repr(term))

    return term

```

让我们看看这个简单的语法模糊器如何从起始符号中获得算术表达式：

```py
simple_grammar_fuzzer(grammar=EXPR_GRAMMAR, max_nonterminals=3, log=True)

```

```py
<start> -> <expr>                        <expr>
<expr> -> <term> + <expr>                <term> + <expr>
<term> -> <factor>                       <factor> + <expr>
<factor> -> <integer>                    <integer> + <expr>
<integer> -> <digit>                     <digit> + <expr>
<digit> -> 6                             6 + <expr>
<expr> -> <term> - <expr>                6 + <term> - <expr>
<expr> -> <term>                         6 + <term> - <term>
<term> -> <factor>                       6 + <factor> - <term>
<factor> -> -<factor>                    6 + -<factor> - <term>
<term> -> <factor>                       6 + -<factor> - <factor>
<factor> -> (<expr>)                     6 + -(<expr>) - <factor>
<factor> -> (<expr>)                     6 + -(<expr>) - (<expr>)
<expr> -> <term>                         6 + -(<term>) - (<expr>)
<expr> -> <term>                         6 + -(<term>) - (<term>)
<term> -> <factor>                       6 + -(<factor>) - (<term>)
<factor> -> +<factor>                    6 + -(+<factor>) - (<term>)
<factor> -> +<factor>                    6 + -(++<factor>) - (<term>)
<term> -> <factor>                       6 + -(++<factor>) - (<factor>)
<factor> -> (<expr>)                     6 + -(++(<expr>)) - (<factor>)
<factor> -> <integer>                    6 + -(++(<expr>)) - (<integer>)
<expr> -> <term>                         6 + -(++(<term>)) - (<integer>)
<integer> -> <digit>                     6 + -(++(<term>)) - (<digit>)
<digit> -> 9                             6 + -(++(<term>)) - (9)
<term> -> <factor> * <term>              6 + -(++(<factor> * <term>)) - (9)
<term> -> <factor>                       6 + -(++(<factor> * <factor>)) - (9)
<factor> -> <integer>                    6 + -(++(<integer> * <factor>)) - (9)
<integer> -> <digit>                     6 + -(++(<digit> * <factor>)) - (9)
<digit> -> 2                             6 + -(++(2 * <factor>)) - (9)
<factor> -> +<factor>                    6 + -(++(2 * +<factor>)) - (9)
<factor> -> -<factor>                    6 + -(++(2 * +-<factor>)) - (9)
<factor> -> -<factor>                    6 + -(++(2 * +--<factor>)) - (9)
<factor> -> -<factor>                    6 + -(++(2 * +---<factor>)) - (9)
<factor> -> -<factor>                    6 + -(++(2 * +----<factor>)) - (9)
<factor> -> <integer>.<integer>          6 + -(++(2 * +----<integer>.<integer>)) - (9)
<integer> -> <digit>                     6 + -(++(2 * +----<digit>.<integer>)) - (9)
<integer> -> <digit>                     6 + -(++(2 * +----<digit>.<digit>)) - (9)
<digit> -> 1                             6 + -(++(2 * +----1.<digit>)) - (9)
<digit> -> 7                             6 + -(++(2 * +----1.7)) - (9)

```

```py
'6 + -(++(2 * +----1.7)) - (9)'

```

通过增加非终端的数量，我们可以快速获得更长的产量：

```py
for i in range(10):
    print(simple_grammar_fuzzer(grammar=EXPR_GRAMMAR, max_nonterminals=5))

```

```py
7 / +48.5
-5.9 / 9 - 4 * +-(-+++((1 + (+7 - (-1 * (++-+7.7 - -+-4.0))))) * +--4 - -(6) + 64)
8.2 - 27 - -9 / +((+9 * --2 + --+-+-((-1 * +(8 - 5 - 6)) * (-((-+(((+(4))))) - ++4) / +(-+---((5.6 - --(3 * -1.8 * +(6 * +-(((-(-6) * ---+6)) / +--(+-+-7 * (-0 * (+(((((2)) + 8 - 3 - ++9.0 + ---(--+7 / (1 / +++6.37) + (1) / 482) / +++-+0)))) * -+5 + 7.513)))) - (+1 / ++((-84)))))))) * ++5 / +-(--2 - -++-9.0)))) / 5 * --++090
1 - -3 * 7 - 28 / 9
(+9) * +-5 * ++-926.2 - (+9.03 / -+(-(-6) / 2 * +(-+--(8) / -(+1.0) - 5 + 4)) * 3.5)
8 + -(9.6 - 3 - -+-4 * +77)
-(((((++((((+((++++-((+-37))))))))))))) / ++(-(+++(+6)) * -++-(+(++(---6 * (((7)) * (1) / (-7.6 * 535338) + +256) * 0) * 0))) - 4 + +1
5.43
(9 / -405 / -23 - +-((+-(2 * (13))))) + +6 - +8 - 934
-++2 - (--+715769550) / 8 / (1)

```

注意，由于大量的搜索和替换操作，此模糊器效率很低。 另一方面，实现很简单，并且在大多数情况下都能胜任。 在本章中，我们将坚持下去； 在[下一章](GrammarFuzzer.html)中，我们将展示如何构建更有效的代码。

## 将语法可视化为铁路图

使用语法，我们可以轻松指定前面讨论的几个示例的格式。 例如，上述算术表达式可以直接发送到`bc`（或任何采用算术表达式的程序）中。 在介绍一些其他语法之前，让我们给出一种方法，使*可视化*，并提供另一种视图以帮助他们理解。

*铁路图*，也称为*语法图*，是无上下文语法的图形表示。 按照可能的“轨道”轨迹从左到右读取它们； 轨道上遇到的符号顺序定义了语言。

我们使用 [RailroadDiagrams](RailroadDiagrams.html) （一个用于可视化的外部库）。

```py
from [RailroadDiagrams](RailroadDiagrams.html) import NonTerminal, Terminal, Choice, HorizontalChoice, Sequence, Diagram, show_diagram

```

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

```

我们首先定义方法`syntax_diagram_symbol()`以可视化给定的符号。 终端符号表示为椭圆形，而非终端符号（例如`<term>`）表示为矩形。

```py
def syntax_diagram_symbol(symbol):
    if is_nonterminal(symbol):
        return NonTerminal(symbol[1:-1])
    else:
        return Terminal(symbol)

```

```py
SVG(show_diagram(syntax_diagram_symbol('<term>')))

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 154.0 62" width="154.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g class="non-terminal"><text x="77.0" y="35">term</text></g></g></svg>

我们定义`syntax_diagram_expr()`以可视化扩展替代方案。

```py
def syntax_diagram_expr(expansion):
    # In later chapters, we allow expansions to be tuples,
    # with the expansion being the first element
    if isinstance(expansion, tuple):
        expansion = expansion[0]

    symbols = [sym for sym in re.split(RE_NONTERMINAL, expansion) if sym != ""]
    if len(symbols) == 0:
        symbols = [""]  # special case: empty expansion

    return Sequence(*[syntax_diagram_symbol(sym) for sym in symbols])

```

```py
SVG(show_diagram(syntax_diagram_expr(EXPR_GRAMMAR['<term>'][0])))

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 310.5 62" width="310.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g class="non-terminal"><text x="85.5" y="35">factor</text></g> <g class="terminal"><text x="163.75" y="35">*</text></g> <g class="non-terminal"><text x="233.5" y="35">term</text></g></g></g></svg>

这是`<term>`的第一种选择– `<factor>`，然后是`*`和`<term>`。

接下来，我们定义`syntax_diagram_alt()`以显示替代表达式。

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

```

```py
def syntax_diagram_alt(alt):
    max_len = 5
    alt_len = len(alt)
    if alt_len > max_len:
        iter_len = alt_len // max_len
        alts = list(zip_longest(*[alt[i::iter_len] for i in range(iter_len)]))
        exprs = [[syntax_diagram_expr(expr) for expr in alt
                  if expr is not None] for alt in alts]
        choices = [Choice(len(expr) // 2, *expr) for expr in exprs]
        return HorizontalChoice(*choices)
    else:
        return Choice(alt_len // 2, *[syntax_diagram_expr(expr) for expr in alt])

```

```py
SVG(show_diagram(syntax_diagram_alt(EXPR_GRAMMAR['<digit>'])))

```

 <svg class="railroad-diagram" height="109" viewBox="0 0 522.5 109" width="522.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g><g class="terminal"><text x="84.25" y="43">0</text></g></g> <g><g class="terminal"><text x="84.25" y="73">1</text></g></g></g> <g><g><g class="terminal"><text x="172.75" y="43">2</text></g></g> <g><g class="terminal"><text x="172.75" y="73">3</text></g></g></g> <g><g><g class="terminal"><text x="261.25" y="43">4</text></g></g> <g><g class="terminal"><text x="261.25" y="73">5</text></g></g></g> <g><g><g class="terminal"><text x="349.75" y="43">6</text></g></g> <g><g class="terminal"><text x="349.75" y="73">7</text></g></g></g> <g><g><g class="terminal"><text x="438.25" y="43">8</text></g></g> <g><g class="terminal"><text x="438.25" y="73">9</text></g></g></g></g></g></svg>

我们看到`<digit>`可以是`0`到`9`的任何一位数字。

最后，我们定义`syntax_diagram()`，它给出了一个语法，显示了其规则的语法图。

```py
def syntax_diagram(grammar):
    from [IPython.display](https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html) import SVG, display

    for key in grammar:
        print("%s" % key[1:-1])
        display(SVG(show_diagram(syntax_diagram_alt(grammar[key]))))

```

```py
syntax_diagram(EXPR_GRAMMAR)

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 174.0 62" width="174.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="87.0" y="35">expr</text></g></g></g></g></svg>

```py
expr

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 313.5 122" width="313.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="87.0" y="35">term</text></g> <g class="terminal"><text x="156.75" y="35">+</text></g> <g class="non-terminal"><text x="226.5" y="35">expr</text></g></g> <g><g class="non-terminal"><text x="87.0" y="65">term</text></g> <g class="terminal"><text x="156.75" y="65">-</text></g> <g class="non-terminal"><text x="226.5" y="65">expr</text></g></g> <g><g class="non-terminal"><text x="156.75" y="95">term</text></g></g></g></g></svg>

```py
term

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 330.5 122" width="330.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="95.5" y="35">factor</text></g> <g class="terminal"><text x="173.75" y="35">*</text></g> <g class="non-terminal"><text x="243.5" y="35">term</text></g></g> <g><g class="non-terminal"><text x="95.5" y="65">factor</text></g> <g class="terminal"><text x="173.75" y="65">/</text></g> <g class="non-terminal"><text x="243.5" y="65">term</text></g></g> <g><g class="non-terminal"><text x="165.25" y="95">factor</text></g></g></g></g></svg>

```py
factor

```

 <svg class="railroad-diagram" height="182" viewBox="0 0 347.5 182" width="347.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="128.25" y="65">-</text></g> <g class="non-terminal"><text x="198.0" y="65">factor</text></g></g> <g><g class="terminal"><text x="128.25" y="35">+</text></g> <g class="non-terminal"><text x="198.0" y="35">factor</text></g></g> <g><g class="terminal"><text x="112.5" y="95">(</text></g> <g class="non-terminal"><text x="173.75" y="95">expr</text></g> <g class="terminal"><text x="235.0" y="95">)</text></g></g> <g><g class="non-terminal"><text x="99.75" y="125">integer</text></g> <g class="terminal"><text x="173.75" y="125">.</text></g> <g class="non-terminal"><text x="247.75" y="125">integer</text></g></g> <g><g class="non-terminal"><text x="173.75" y="155">integer</text></g></g></g></g></svg>

```py
integer

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 282.0 92" width="282.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="91.25" y="35">digit</text></g> <g class="non-terminal"><text x="182.25" y="35">integer</text></g></g> <g><g class="non-terminal"><text x="141.0" y="65">digit</text></g></g></g></g></svg>

```py
digit

```

 <svg class="railroad-diagram" height="109" viewBox="0 0 522.5 109" width="522.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g><g class="terminal"><text x="84.25" y="43">0</text></g></g> <g><g class="terminal"><text x="84.25" y="73">1</text></g></g></g> <g><g><g class="terminal"><text x="172.75" y="43">2</text></g></g> <g><g class="terminal"><text x="172.75" y="73">3</text></g></g></g> <g><g><g class="terminal"><text x="261.25" y="43">4</text></g></g> <g><g class="terminal"><text x="261.25" y="73">5</text></g></g></g> <g><g><g class="terminal"><text x="349.75" y="43">6</text></g></g> <g><g class="terminal"><text x="349.75" y="73">7</text></g></g></g> <g><g><g class="terminal"><text x="438.25" y="43">8</text></g></g> <g><g class="terminal"><text x="438.25" y="73">9</text></g></g></g></g></g></svg>

这种铁路表示形式将在可视化语法结构时派上用场-特别是对于更复杂的语法。

## 一些文法

让我们创建（并可视化）更多语法并将其用于模糊测试。

### CGI语法

这是[关于覆盖](Coverage.html)的一章中介绍的`cgi_decode()`的语法。

```py
CGI_GRAMMAR = {
    "<start>":
        ["<string>"],

    "<string>":
        ["<letter>", "<letter><string>"],

    "<letter>":
        ["<plus>", "<percent>", "<other>"],

    "<plus>":
        ["+"],

    "<percent>":
        ["%<hexdigit><hexdigit>"],

    "<hexdigit>":
        ["0", "1", "2", "3", "4", "5", "6", "7",
            "8", "9", "a", "b", "c", "d", "e", "f"],

    "<other>":  # Actually, could be _all_ letters
        ["0", "1", "2", "3", "4", "5", "a", "b", "c", "d", "e", "-", "_"],
}

```

```py
syntax_diagram(CGI_GRAMMAR)

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 191.0 62" width="191.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="95.5" y="35">string</text></g></g></g></g></svg>

```py
string

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 282.0 92" width="282.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="141.0" y="35">letter</text></g></g> <g><g class="non-terminal"><text x="95.5" y="65">letter</text></g> <g class="non-terminal"><text x="186.5" y="65">string</text></g></g></g></g></svg>

```py
letter

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 199.5 122" width="199.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="99.75" y="35">plus</text></g></g> <g><g class="non-terminal"><text x="99.75" y="65">percent</text></g></g> <g><g class="non-terminal"><text x="99.75" y="95">other</text></g></g></g></g></svg>

```py
plus

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 148.5 62" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">+</text></g></g></g></g></svg>

```py
percent

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 364.5 62" width="364.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">%</text></g> <g class="non-terminal"><text x="152.5" y="35">hexdigit</text></g> <g class="non-terminal"><text x="260.5" y="35">hexdigit</text></g></g></g></g></svg>

```py
hexdigit

```

 <svg class="railroad-diagram" height="138" viewBox="0 0 611.0 138" width="611.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g><g class="terminal"><text x="84.25" y="43">0</text></g></g> <g><g class="terminal"><text x="84.25" y="73">1</text></g></g> <g><g class="terminal"><text x="84.25" y="103">2</text></g></g></g> <g><g><g class="terminal"><text x="172.75" y="43">3</text></g></g> <g><g class="terminal"><text x="172.75" y="73">4</text></g></g> <g><g class="terminal"><text x="172.75" y="103">5</text></g></g></g> <g><g><g class="terminal"><text x="261.25" y="43">6</text></g></g> <g><g class="terminal"><text x="261.25" y="73">7</text></g></g> <g><g class="terminal"><text x="261.25" y="103">8</text></g></g></g> <g><g><g class="terminal"><text x="349.75" y="43">9</text></g></g> <g><g class="terminal"><text x="349.75" y="73">a</text></g></g> <g><g class="terminal"><text x="349.75" y="103">b</text></g></g></g> <g><g><g class="terminal"><text x="438.25" y="43">c</text></g></g> <g><g class="terminal"><text x="438.25" y="73">d</text></g></g> <g><g class="terminal"><text x="438.25" y="103">e</text></g></g></g> <g><g><g class="terminal"><text x="526.75" y="73">f</text></g></g></g></g></g></svg>

```py
other

```

 <svg class="railroad-diagram" height="109" viewBox="0 0 699.5 109" width="699.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g><g class="terminal"><text x="84.25" y="43">0</text></g></g> <g><g class="terminal"><text x="84.25" y="73">1</text></g></g></g> <g><g><g class="terminal"><text x="172.75" y="43">2</text></g></g> <g><g class="terminal"><text x="172.75" y="73">3</text></g></g></g> <g><g><g class="terminal"><text x="261.25" y="43">4</text></g></g> <g><g class="terminal"><text x="261.25" y="73">5</text></g></g></g> <g><g><g class="terminal"><text x="349.75" y="43">a</text></g></g> <g><g class="terminal"><text x="349.75" y="73">b</text></g></g></g> <g><g><g class="terminal"><text x="438.25" y="43">c</text></g></g> <g><g class="terminal"><text x="438.25" y="73">d</text></g></g></g> <g><g><g class="terminal"><text x="526.75" y="43">e</text></g></g> <g><g class="terminal"><text x="526.75" y="73">-</text></g></g></g> <g><g><g class="terminal"><text x="615.25" y="73">_</text></g></g></g></g></g></svg>

与[基本模糊处理](Fuzzer.html)或基于[突变的模糊处理](MutationFuzzer.html)相比，语法可以快速产生各种组合：

```py
for i in range(10):
    print(simple_grammar_fuzzer(grammar=CGI_GRAMMAR, max_nonterminals=10))

```

```py
+%9a
+++%ce+
+_
+%c6c
++
+%cd+5
1%ee
%b9%d5
%96
%57d%42

```

### URL语法

我们为CGI输入看到的相同属性也适用于更复杂的输入。 让我们使用一种语法来产生大量有效的URL：

```py
URL_GRAMMAR = {
    "<start>":
        ["<url>"],
    "<url>":
        ["<scheme>://<authority><path><query>"],
    "<scheme>":
        ["http", "https", "ftp", "ftps"],
    "<authority>":
        ["<host>", "<host>:<port>", "<userinfo>@<host>", "<userinfo>@<host>:<port>"],
    "<host>":  # Just a few
        ["cispa.saarland", "www.google.com", "fuzzingbook.com"],
    "<port>":
        ["80", "8080", "<nat>"],
    "<nat>":
        ["<digit>", "<digit><digit>"],
    "<digit>":
        ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
    "<userinfo>":  # Just one
        ["user:password"],
    "<path>":  # Just a few
        ["", "/", "/<id>"],
    "<id>":  # Just a few
        ["abc", "def", "x<digit><digit>"],
    "<query>":
        ["", "?<params>"],
    "<params>":
        ["<param>", "<param>&<params>"],
    "<param>":  # Just a few
        ["<id>=<id>", "<id>=<nat>"],
}

```

```py
syntax_diagram(URL_GRAMMAR)

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 165.5 62" width="165.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="82.75" y="35">url</text></g></g></g></g></svg>

```py
url

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 529.5 62" width="529.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="95.5" y="35">scheme</text></g> <g class="terminal"><text x="173.75" y="35">://</text></g> <g class="non-terminal"><text x="264.75" y="35">authority</text></g> <g class="non-terminal"><text x="360.0" y="35">path</text></g> <g class="non-terminal"><text x="438.25" y="35">query</text></g></g></g></g></svg>

```py
scheme

```

 <svg class="railroad-diagram" height="152" viewBox="0 0 182.5 152" width="182.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="91.25" y="65">https</text></g></g> <g><g class="terminal"><text x="91.25" y="35">http</text></g></g> <g><g class="terminal"><text x="91.25" y="95">ftp</text></g></g> <g><g class="terminal"><text x="91.25" y="125">ftps</text></g></g></g></g></svg>

```py
authority

```

 <svg class="railroad-diagram" height="152" viewBox="0 0 453.0 152" width="453.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="165.25" y="65">host</text></g> <g class="terminal"><text x="226.5" y="65">:</text></g> <g class="non-terminal"><text x="287.75" y="65">port</text></g></g> <g><g class="non-terminal"><text x="226.5" y="35">host</text></g></g> <g><g class="non-terminal"><text x="165.25" y="95">userinfo</text></g> <g class="terminal"><text x="243.5" y="95">@</text></g> <g class="non-terminal"><text x="304.75" y="95">host</text></g></g> <g><g class="non-terminal"><text x="104.0" y="125">userinfo</text></g> <g class="terminal"><text x="182.25" y="125">@</text></g> <g class="non-terminal"><text x="243.5" y="125">host</text></g> <g class="terminal"><text x="304.75" y="125">:</text></g> <g class="non-terminal"><text x="366.0" y="125">port</text></g></g></g></g></svg>

```py
host

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 267.5 122" width="267.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="133.75" y="35">cispa.saarland</text></g></g> <g><g class="terminal"><text x="133.75" y="65">www.google.com</text></g></g> <g><g class="terminal"><text x="133.75" y="95">fuzzingbook.com</text></g></g></g></g></svg>

```py
port

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 174.0 122" width="174.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="87.0" y="35">80</text></g></g> <g><g class="terminal"><text x="87.0" y="65">8080</text></g></g> <g><g class="non-terminal"><text x="87.0" y="95">nat</text></g></g></g></g></svg>

```py
nat

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 265.0 92" width="265.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="132.5" y="35">digit</text></g></g> <g><g class="non-terminal"><text x="91.25" y="65">digit</text></g> <g class="non-terminal"><text x="173.75" y="65">digit</text></g></g></g></g></svg>

```py
digit

```

 <svg class="railroad-diagram" height="109" viewBox="0 0 522.5 109" width="522.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g><g class="terminal"><text x="84.25" y="43">0</text></g></g> <g><g class="terminal"><text x="84.25" y="73">1</text></g></g></g> <g><g><g class="terminal"><text x="172.75" y="43">2</text></g></g> <g><g class="terminal"><text x="172.75" y="73">3</text></g></g></g> <g><g><g class="terminal"><text x="261.25" y="43">4</text></g></g> <g><g class="terminal"><text x="261.25" y="73">5</text></g></g></g> <g><g><g class="terminal"><text x="349.75" y="43">6</text></g></g> <g><g class="terminal"><text x="349.75" y="73">7</text></g></g></g> <g><g><g class="terminal"><text x="438.25" y="43">8</text></g></g> <g><g class="terminal"><text x="438.25" y="73">9</text></g></g></g></g></g></svg>

```py
userinfo

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 250.5 62" width="250.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="125.25" y="35">user:password</text></g></g></g></g></svg>

```py
path

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 205.5 122" width="205.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="102.75" y="65">/</text></g></g> <g><g class="terminal"><text x="74.25" y="95">/</text></g> <g class="non-terminal"><text x="127.0" y="95">id</text></g></g></g></g></svg>

```py
id

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 313.5 122" width="313.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="156.75" y="35">abc</text></g></g> <g><g class="terminal"><text x="156.75" y="65">def</text></g></g> <g><g class="terminal"><text x="74.25" y="95">x</text></g> <g class="non-terminal"><text x="139.75" y="95">digit</text></g> <g class="non-terminal"><text x="222.25" y="95">digit</text></g></g></g></g></svg>

```py
query

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 239.5 92" width="239.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="65">?</text></g> <g class="non-terminal"><text x="144.0" y="65">params</text></g></g></g></g></svg>

```py
params

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 322.0 92" width="322.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="161.0" y="35">param</text></g></g> <g><g class="non-terminal"><text x="91.25" y="65">param</text></g> <g class="terminal"><text x="156.75" y="65">&</text></g> <g class="non-terminal"><text x="226.5" y="65">params</text></g></g></g></g></svg>

```py
param

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 271.0 92" width="271.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="82.75" y="35">id</text></g> <g class="terminal"><text x="135.5" y="35">=</text></g> <g class="non-terminal"><text x="188.25" y="35">id</text></g></g> <g><g class="non-terminal"><text x="78.5" y="65">id</text></g> <g class="terminal"><text x="131.25" y="65">=</text></g> <g class="non-terminal"><text x="188.25" y="65">nat</text></g></g></g></g></svg>

同样，在几毫秒内，我们可以产生大量有效的输入。

```py
for i in range(10):
    print(simple_grammar_fuzzer(grammar=URL_GRAMMAR, max_nonterminals=10))

```

```py
https://user:password@cispa.saarland:80/
http://fuzzingbook.com?def=56&x89=3&x46=48&def=def
ftp://cispa.saarland/?x71=5&x35=90&def=abc
https://cispa.saarland:80/def?def=7&x23=abc
https://fuzzingbook.com:80/
https://fuzzingbook.com:80/abc?def=abc&abc=x14&def=abc&abc=2&def=38
ftps://fuzzingbook.com/x87
https://user:password@fuzzingbook.com:6?def=54&x44=abc
http://fuzzingbook.com:80?x33=25&def=8
http://fuzzingbook.com:8080/def

```

### 自然语言语法

最后，语法不仅限于诸如计算机输入之类的*形式语言*，而且还可以用于产生*自然语言*。 这是我们用来选择本书标题的语法：

```py
TITLE_GRAMMAR = {
    "<start>": ["<title>"],
    "<title>": ["<topic>: <subtopic>"],
    "<topic>": ["Generating Software Tests", "<fuzzing-prefix>Fuzzing", "The Fuzzing Book"],
    "<fuzzing-prefix>": ["", "The Art of ", "The Joy of "],
    "<subtopic>": ["<subtopic-main>",
                   "<subtopic-prefix><subtopic-main>",
                   "<subtopic-main><subtopic-suffix>"],
    "<subtopic-main>": ["Breaking Software",
                        "Generating Software Tests",
                        "Principles, Techniques and Tools"],
    "<subtopic-prefix>": ["", "Tools and Techniques for "],
    "<subtopic-suffix>": [" for <reader-property> and <reader-property>",
                          " for <software-property> and <software-property>"],
    "<reader-property>": ["Fun", "Profit"],
    "<software-property>": ["Robustness", "Reliability", "Security"],
}

```

```py
syntax_diagram(TITLE_GRAMMAR)

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 182.5 62" width="182.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="91.25" y="35">title</text></g></g></g></g></svg>

```py
title

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 347.5 62" width="347.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="91.25" y="35">topic</text></g> <g class="terminal"><text x="161.0" y="35">:</text></g> <g class="non-terminal"><text x="243.5" y="35">subtopic</text></g></g></g></g></svg>

```py
topic

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 358.5 122" width="358.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="179.25" y="35">Generating Software Tests</text></g></g> <g><g class="non-terminal"><text x="129.5" y="65">fuzzing-prefix</text></g> <g class="terminal"><text x="258.75" y="65">Fuzzing</text></g></g> <g><g class="terminal"><text x="179.25" y="95">The Fuzzing Book</text></g></g></g></g></svg>

```py
fuzzing-prefix

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 233.5 122" width="233.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="116.75" y="65">The Art of</text></g></g> <g><g class="terminal"><text x="116.75" y="95">The Joy of</text></g></g></g></g></svg>

```py
subtopic

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 418.0 122" width="418.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="209.0" y="35">subtopic-main</text></g></g> <g><g class="non-terminal"><text x="133.75" y="65">subtopic-prefix</text></g> <g class="non-terminal"><text x="292.75" y="65">subtopic-main</text></g></g> <g><g class="non-terminal"><text x="125.25" y="95">subtopic-main</text></g> <g class="non-terminal"><text x="284.25" y="95">subtopic-suffix</text></g></g></g></g></svg>

```py
subtopic-main

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 412.0 122" width="412.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="206.0" y="35">Breaking Software</text></g></g> <g><g class="terminal"><text x="206.0" y="65">Generating Software Tests</text></g></g> <g><g class="terminal"><text x="206.0" y="95">Principles, Techniques and Tools</text></g></g></g></g></svg>

```py
subtopic-prefix

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 352.5 92" width="352.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="176.25" y="65">Tools and Techniques for</text></g></g></g></g></svg>

```py
subtopic-suffix

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 634.0 92" width="634.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="108.25" y="35">for</text></g> <g class="non-terminal"><text x="233.25" y="35">reader-property</text></g> <g class="terminal"><text x="358.25" y="35">and</text></g> <g class="non-terminal"><text x="483.25" y="35">reader-property</text></g></g> <g><g class="terminal"><text x="91.25" y="65">for</text></g> <g class="non-terminal"><text x="224.75" y="65">software-property</text></g> <g class="terminal"><text x="358.25" y="65">and</text></g> <g class="non-terminal"><text x="491.75" y="65">software-property</text></g></g></g></g></svg>

```py
reader-property

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 191.0 92" width="191.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="95.5" y="35">Fun</text></g></g> <g><g class="terminal"><text x="95.5" y="65">Profit</text></g></g></g></g></svg>

```py
software-property

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 233.5 122" width="233.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="116.75" y="35">Robustness</text></g></g> <g><g class="terminal"><text x="116.75" y="65">Reliability</text></g></g> <g><g class="terminal"><text x="116.75" y="95">Security</text></g></g></g></g></svg>

```py
titles = set()
while len(titles) < 10:
    titles.add(simple_grammar_fuzzer(
        grammar=TITLE_GRAMMAR, max_nonterminals=10))
titles

```

```py
{'Fuzzing: Generating Software Tests',
 'Fuzzing: Principles, Techniques and Tools',
 'Generating Software Tests: Breaking Software',
 'Generating Software Tests: Breaking Software for Robustness and Robustness',
 'Generating Software Tests: Principles, Techniques and Tools',
 'Generating Software Tests: Principles, Techniques and Tools for Profit and Fun',
 'Generating Software Tests: Tools and Techniques for Principles, Techniques and Tools',
 'The Fuzzing Book: Breaking Software',
 'The Fuzzing Book: Generating Software Tests for Profit and Profit',
 'The Fuzzing Book: Generating Software Tests for Robustness and Robustness'}

```

（如果您发现此处存在冗余（“鲁棒性和鲁棒性”）：在[我们有关基于覆盖率的模糊测试](GrammarCoverageFuzzer.html)的章节中，我们将展示如何仅覆盖每个扩展一次。并且，如果您喜欢某些替代方法 [概率语法模糊](ProbabilisticGrammarFuzzer.html)比其他要多。

## 语法作为突变种子

语法的一个非常有用的属性是它们产生大多数有效的输入。 从句法的观点来看，输入实际上*始终是*有效，因为它们满足给定语法的约束。 （当然，首先需要一个有效的语法。）但是，还有*语义*属性，这些属性无法轻松地用语法表达。 例如，如果对于URL，端口范围应该在1024到2048之间，则很难用语法来编写。 如果必须满足更复杂的约束条件，则可以很快达到语法可以表达的极限。

解决此问题的一种方法是对语法附加约束，因为我们将在本书的后面讨论[。 另一种可能性是将基于语法的模糊测试和基于](ConstraintFuzzer.html)[突变的模糊测试](MutationFuzzer.html)的优势结合在一起。 想法是将语法生成的输入用作*种子*，以进行进一步的基于突变的模糊测试。 这样，我们不仅可以探索*有效*输入，还可以检查有效输入和无效输入之间的*边界*。 这一点特别有趣，因为稍微无效的输入允许查找解析器错误（通常很丰富）。 就像一般的模糊测试一样，意外的情况揭示了程序中的错误。

要将生成的输入用作种子，我们可以将它们直接输入到前面介绍的突变模糊处理程序中：

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

```

```py
number_of_seeds = 10
seeds = [
    simple_grammar_fuzzer(
        grammar=URL_GRAMMAR,
        max_nonterminals=10) for i in range(number_of_seeds)]
seeds

```

```py
['ftps://user:password@www.google.com:80',
 'http://cispa.saarland/',
 'ftp://www.google.com:42/',
 'ftps://user:password@fuzzingbook.com:39?abc=abc',
 'https://www.google.com?x33=1&x06=1',
 'http://www.google.com:02/',
 'https://user:password@www.google.com/',
 'ftp://cispa.saarland:8080/?abc=abc&def=def&abc=5',
 'http://www.google.com:80/def?def=abc',
 'http://user:password@cispa.saarland/']

```

```py
m = MutationFuzzer(seeds)

```

```py
[m.fuzz() for i in range(20)]

```

```py
['ftps://user:password@www.google.com:80',
 'http://cispa.saarland/',
 'ftp://www.google.com:42/',
 'ftps://user:password@fuzzingbook.com:39?abc=abc',
 'https://www.google.com?x33=1&x06=1',
 'http://www.google.com:02/',
 'https://user:password@www.google.com/',
 'ftp://cispa.saarland:8080/?abc=abc&def=def&abc=5',
 'http://www.google.com:80/def?def=abc',
 'http://user:password@cispa.saarland/',
 'Eh4tp:www.coogle.com:80/def?d%f=abc',
 'ftps://}ser:passwod@fuzzingbook.com:9?abc=abc',
 'uftp//cispa.sRaarland:808&0?abc=abc&def=defabc=5',
 'http://user:paswor9d@cispar.saarland/v',
 'ftp://Www.g\x7fogle.cAom:42/',
 'hht://userC:qassMword@cispy.csaarland/',
 'httx://ww.googlecom:80defde`f=ac',
 'htt://cispq.waarlnd/',
 'htFtp\t://cmspa./saarna(md/',
 'ft:/www.google.com:42\x0f']

```

前10个`fuzz()`调用返回种子输入（按设计），而后一个再次创建任意突变。 使用`MutationCoverageFuzzer`而不是`MutationFuzzer`，我们可以再次根据覆盖率进行搜索-从而将多个世界的最佳点融合在一起。

## 语法工具箱

现在让我们介绍一些有助于我们编写语法的技术。

### 转义

通过`<`和`>`在语法中定界非终结符，我们如何实际表达某些输入应包含`<`和`>`？ 答案很简单：只需为它们引入一个符号即可。

```py
simple_nonterminal_grammar = {
    "<start>": ["<nonterminal>"],
    "<nonterminal>": ["<left-angle><identifier><right-angle>"],
    "<left-angle>": ["<"],
    "<right-angle>": [">"],
    "<identifier>": ["id"]  # for now
}

```

在`simple_nonterminal_grammar`中，`<left-angle>`的扩展名和`<right-angle>`的扩展名都不能误认为是非终结符。 因此，我们可以生产任意数量的东西。

### 扩展语法

在本书的过程中，我们经常遇到通过*扩展*具有新功能的现有语法来创建语法的问题。 这样的扩展非常类似于面向对象编程中的子类化。

要从现有语法$ g $创建新语法$ g'$，我们首先将$ g $复制到$ g'$，然后使用新的替代方法扩展现有规则和/或添加新的符号。 这是一个示例，使用更好的标识符规则扩展了上述`nonterminal`语法：

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

```

```py
nonterminal_grammar = copy.deepcopy(simple_nonterminal_grammar)
nonterminal_grammar["<identifier>"] = ["<idchar>", "<identifier><idchar>"]
nonterminal_grammar["<idchar>"] = ['a', 'b', 'c', 'd']  # for now

```

```py
nonterminal_grammar

```

```py
{'<start>': ['<nonterminal>'],
 '<nonterminal>': ['<left-angle><identifier><right-angle>'],
 '<left-angle>': ['<'],
 '<right-angle>': ['>'],
 '<identifier>': ['<idchar>', '<identifier><idchar>'],
 '<idchar>': ['a', 'b', 'c', 'd']}

```

由于语法的这种扩展是常见的操作，因此我们引入了自定义函数`extend_grammar()`，该函数首先复制给定的语法，然后使用Python字典`update()`方法从字典中对其进行更新：

```py
def extend_grammar(grammar, extension={}):
    new_grammar = copy.deepcopy(grammar)
    new_grammar.update(extension)
    return new_grammar

```

对`extend_grammar()`的此调用将`simple_nonterminal_grammar`扩展到`nonterminal_grammar`，就像上面的“手动”示例一样：

```py
nonterminal_grammar = extend_grammar(simple_nonterminal_grammar,
                                     {
                                         "<identifier>": ["<idchar>", "<identifier><idchar>"],
                                         # for now
                                         "<idchar>": ['a', 'b', 'c', 'd']
                                     }
                                     )

```

### 字符类

在上面的`nonterminal_grammar`中，我们仅列举了前几个字母； 确实，如`<idchar> ::= 'a' | 'b' | 'c' ...`中那样手动枚举语法中的所有字母或数字有点痛苦。

但是，请记住，语法是程序的一部分，因此也可以通过程序构造。 我们引入一个函数`srange()`，该函数在字符串中构造一个字符列表：

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

```

```py
def srange(characters):
    """Construct a list with all characters in the string"""
    return [c for c in characters]

```

如果我们将包含所有ASCII字母的常量`string.ascii_letters`传递给它，则`srange()`将返回所有ASCII字母的列表：

```py
string.ascii_letters

```

```py
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

```

```py
srange(string.ascii_letters)[:10]

```

```py
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

```

我们可以在语法中使用这些常量来快速定义标识符：

```py
nonterminal_grammar = extend_grammar(nonterminal_grammar,
                                     {
                                         "<idchar>": srange(string.ascii_letters) + srange(string.digits) + srange("-_")
                                     }
                                     )

```

```py
[simple_grammar_fuzzer(nonterminal_grammar, "<identifier>") for i in range(10)]

```

```py
['b', 'd', 'V9', 'x4c', 'YdiEWj', 'c', 'xd', '7', 'vIU', 'QhKD']

```

快捷方式`crange(start, end)`返回`start`到（包括）`end`的ASCII范围内的所有字符的列表：

```py
def crange(character_start, character_end):
    return [chr(i)
            for i in range(ord(character_start), ord(character_end) + 1)]

```

我们可以使用它来表示字符范围：

```py
crange('0', '9')

```

```py
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

```

```py
assert crange('a', 'z') == srange(string.ascii_lowercase)

```

### 语法快捷方式

在上面的`nonterminal_grammar`中，像在其他语法中一样，我们必须使用*递归*来表示字符的重复，即通过引用原始定义：

```py
nonterminal_grammar["<identifier>"]

```

```py
['<idchar>', '<identifier><idchar>']

```

如果我们简单地声明非终结符应该是字母的非空序列，则可能会容易一些，例如

```py
<identifier> = <idchar>+
```

其中`+`表示其后跟符号的非空重复。

诸如`+`之类的运算符经常作为语法中方便的*快捷方式*引入。 正式地，我们的语法以 [Backus-Naur形式](https://en.wikipedia.org/wiki/Backus-Naur_form)或简称 *BNF* 的形式出现。 运算符*将* BNF扩展为所谓的_extended BNF *或简称为* EBNF *：

*   `<symbol>?`格式表示`<symbol>`是可选的-也就是说，它可以出现0或1次。
*   形式`<symbol>+`表示`<symbol>`可以重复出现1次或多次。
*   形式`<symbol>*`表示`<symbol>`可以出现0次或更多次。 （换句话说，这是一个可选的重复。）

为了使事情变得更加有趣，我们想在上述快捷方式中使用*括号*。 因此，`(<foo><bar>)?`表示`<foo>`和`<bar>`的顺序是可选的。

使用此类运算符，我们可以以更简单的方式定义标识符规则。 为此，让我们创建原始语法的副本并修改`<identifier>`规则：

```py
nonterminal_ebnf_grammar = extend_grammar(nonterminal_grammar,
                                          {
                                              "<identifier>": ["<idchar>+"]
                                          }
                                          )

```

同样，我们可以简化表达语法。 考虑一下符号是如何可选的，以及如何将整数表示为数字序列。

```py
EXPR_EBNF_GRAMMAR = {
    "<start>":
        ["<expr>"],

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

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

    "<factor>":
        ["<sign>?<factor>", "(<expr>)", "<integer>(.<integer>)?"],

    "<sign>":
        ["+", "-"],

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

    "<digit>":
        srange(string.digits)
}

```

我们的目标是将上述EBNF语法转换为常规BNF语法。 这是通过以下四个规则完成的：

1.  具有新规则`<new-symbol> ::= content`的表达式`(content)op`变为`<new-symbol>op`，其中`op`是`?`，`+`和`*`之一。
2.  表达式`<symbol>?`变为`<new-symbol>`，其中`<new-symbol> ::= <empty> | <symbol>`。
3.  表达式`<symbol>+`变为`<new-symbol>`，其中`<new-symbol> ::= <symbol> | <symbol><new-symbol>`。
4.  表达式`<symbol>*`变为`<new-symbol>`，其中`<new-symbol> ::= <empty> | <symbol><new-symbol>`。

这里，`<empty>`扩展为空字符串，与`<empty> ::=`相同。 （这也称为 *epsilon扩展*。）

如果这些运算符使您想起*正则表达式*，这并非偶然：实际上，可以使用上述规则（以及使用`crange()`定义的字符类）将任何基本正则表达式转换为语法。 。

将这些规则应用于上面的示例将产生以下结果：

*   `<idchar>+`与`<new-symbol> ::= <idchar> | <idchar><new-symbol>`成为`<idchar><new-symbol>`。
*   `<integer>(.<integer>)?`与`<new-symbol> ::= <empty> | .<integer>`成为`<integer><new-symbol>`。

让我们分三步实施这些规则。

#### 创建新符号

首先，我们需要一种机制来创建新符号。 这很简单。

```py
def new_symbol(grammar, symbol_name="<symbol>"):
    """Return a new symbol for `grammar` based on `symbol_name`"""
    if symbol_name not in grammar:
        return symbol_name

    count = 1
    while True:
        tentative_symbol_name = symbol_name[:-1] + "-" + repr(count) + ">"
        if tentative_symbol_name not in grammar:
            return tentative_symbol_name
        count += 1

```

```py
assert new_symbol(EXPR_EBNF_GRAMMAR, '<expr>') == '<expr-1>'

```

#### 扩展括号表达式

接下来，我们需要一种从扩展中提取括号表达式的方法，并根据上述规则对其进行扩展。 让我们从提取表达式开始：

```py
RE_PARENTHESIZED_EXPR = re.compile(r'\([^()]*\)[?+*]')

```

```py
def parenthesized_expressions(expansion):
    # In later chapters, we allow expansions to be tuples,
    # with the expansion being the first element
    if isinstance(expansion, tuple):
        expansion = expansion[0]

    return re.findall(RE_PARENTHESIZED_EXPR, expansion)

```

```py
assert parenthesized_expressions("(<foo>)* (<foo><bar>)+ (+<foo>)? <integer>(.<integer>)?") == [
    '(<foo>)*', '(<foo><bar>)+', '(+<foo>)?', '(.<integer>)?']

```

现在，我们可以使用它们来应用上面的规则编号1，为括号中的表达式引入新的符号。

```py
def convert_ebnf_parentheses(ebnf_grammar):
    """Convert a grammar in extended BNF to BNF"""
    grammar = extend_grammar(ebnf_grammar)
    for nonterminal in ebnf_grammar:
        expansions = ebnf_grammar[nonterminal]

        for i in range(len(expansions)):
            expansion = expansions[i]

            while True:
                parenthesized_exprs = parenthesized_expressions(expansion)
                if len(parenthesized_exprs) == 0:
                    break

                for expr in parenthesized_exprs:
                    operator = expr[-1:]
                    contents = expr[1:-2]

                    new_sym = new_symbol(grammar)
                    expansion = grammar[nonterminal][i].replace(
                        expr, new_sym + operator, 1)
                    grammar[nonterminal][i] = expansion
                    grammar[new_sym] = [contents]

    return grammar

```

这将执行上面概述的转换：

```py
convert_ebnf_parentheses({"<number>": ["<integer>(.<integer>)?"]})

```

```py
{'<number>': ['<integer><symbol>?'], '<symbol>': ['.<integer>']}

```

它甚至适用于带括号的嵌套表达式：

```py
convert_ebnf_parentheses({"<foo>": ["((<foo>)?)+"]})

```

```py
{'<foo>': ['<symbol-1>+'], '<symbol>': ['<foo>'], '<symbol-1>': ['<symbol>?']}

```

#### 扩展运算符

扩展带括号的表达式后，我们现在需要注意符号后跟运算符（`?`，`*`和`+`）。 与上面的`convert_ebnf_parentheses()`一样，我们首先提取所有符号，然后提取一个运算符。

```py
RE_EXTENDED_NONTERMINAL = re.compile(r'(<[^<> ]*>[?+*])')

```

```py
def extended_nonterminals(expansion):
    # In later chapters, we allow expansions to be tuples,
    # with the expansion being the first element
    if isinstance(expansion, tuple):
        expansion = expansion[0]

    return re.findall(RE_EXTENDED_NONTERMINAL, expansion)

```

```py
assert extended_nonterminals(
    "<foo>* <bar>+ <elem>? <none>") == ['<foo>*', '<bar>+', '<elem>?']

```

我们的转换器提取符号和运算符，并根据上述规则添加新符号。

```py
def convert_ebnf_operators(ebnf_grammar):
    """Convert a grammar in extended BNF to BNF"""
    grammar = extend_grammar(ebnf_grammar)
    for nonterminal in ebnf_grammar:
        expansions = ebnf_grammar[nonterminal]

        for i in range(len(expansions)):
            expansion = expansions[i]
            extended_symbols = extended_nonterminals(expansion)

            for extended_symbol in extended_symbols:
                operator = extended_symbol[-1:]
                original_symbol = extended_symbol[:-1]

                new_sym = new_symbol(grammar, original_symbol)
                grammar[nonterminal][i] = grammar[nonterminal][i].replace(
                    extended_symbol, new_sym, 1)

                if operator == '?':
                    grammar[new_sym] = ["", original_symbol]
                elif operator == '*':
                    grammar[new_sym] = ["", original_symbol + new_sym]
                elif operator == '+':
                    grammar[new_sym] = [
                        original_symbol, original_symbol + new_sym]

    return grammar

```

```py
convert_ebnf_operators({"<integer>": ["<digit>+"]})

```

```py
{'<integer>': ['<digit>'], '<digit>': ['<digit>', '<digit><digit>']}

```

#### 全部在一起

我们可以结合两个，首先扩展括号，然后是运算符：

```py
def convert_ebnf_grammar(ebnf_grammar):
    return convert_ebnf_operators(convert_ebnf_parentheses(ebnf_grammar))

```

```py
convert_ebnf_grammar({"<authority>": ["(<userinfo>@)?<host>(:<port>)?"]})

```

```py
{'<authority>': ['<symbol-2><host><symbol-1-1>'],
 '<symbol>': ['<userinfo>@'],
 '<symbol-1>': [':<port>'],
 '<symbol-2>': ['', '<symbol>'],
 '<symbol-1-1>': ['', '<symbol-1>']}

```

```py
expr_grammar = convert_ebnf_grammar(EXPR_EBNF_GRAMMAR)
expr_grammar

```

```py
{'<start>': ['<expr>'],
 '<expr>': ['<term> + <expr>', '<term> - <expr>', '<term>'],
 '<term>': ['<factor> * <term>', '<factor> / <term>', '<factor>'],
 '<factor>': ['<sign-1><factor>', '(<expr>)', '<integer><symbol-1>'],
 '<sign>': ['+', '-'],
 '<integer>': ['<digit-1>'],
 '<digit>': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
 '<symbol>': ['.<integer>'],
 '<sign-1>': ['', '<sign>'],
 '<symbol-1>': ['', '<symbol>'],
 '<digit-1>': ['<digit>', '<digit><digit-1>']}

```

成功！ 我们已经很好地将EBNF语法转换为BNF。

通过字符类和EBNF语法转换，我们有两个功能强大的工具可以使语法编写更加容易。 在处理语法时，我们将一再使用它们。

### 语法扩展

在本书的学习过程中，我们经常希望为语法指定*附加信息*，例如 [*概率*](ProbabilisticGrammarFuzzer.html) 或 [*约束*  。 为了支持这些扩展以及可能的其他扩展，我们定义了*注释*机制。

我们注释语法的概念是将*注释*添加到各个扩展中。 为此，我们允许扩展不仅是字符串，而且是字符串和一组属性的*对*，如

```py
"<expr>":
        [("<term> + <expr>", opts(min_depth=10)),
         ("<term> - <expr>", opts(max_depth=2)),
         "<term>"]

```

在这里，`opts()`函数将使我们能够表达适用于各个扩展的注释； 在这种情况下，加法将以`min_depth`值为10注释，减法以`max_depth`值为2注释。这些注释的含义留给处理语法的各个算法； 但是，一般的想法是可以忽略它们。

我们的`opts()`辅助函数返回其参数到值的映射：

```py
def opts(**kwargs):
    return kwargs

```

```py
opts(min_depth=10)

```

```py
{'min_depth': 10}

```

为了处理扩展字符串以及扩展和注释对，我们通过指定的辅助函数`exp_string()`和`exp_opts()`访问扩展字符串和相关的注释：

```py
def exp_string(expansion):
    """Return the string to be expanded"""
    if isinstance(expansion, str):
        return expansion
    return expansion[0]

```

```py
exp_string(("<term> + <expr>", opts(min_depth=10)))

```

```py
'<term> + <expr>'

```

```py
def exp_opts(expansion):
    """Return the options of an expansion.  If options are not defined, return {}"""
    if isinstance(expansion, str):
        return {}
    return expansion[1]

```

```py
def exp_opt(expansion, attribute):
    """Return the given attribution of an expansion.
 If attribute is not defined, return None"""
    return exp_opts(expansion).get(attribute, None)

```

```py
exp_opts(("<term> + <expr>", opts(min_depth=10)))

```

```py
{'min_depth': 10}

```

```py
exp_opt(("<term> - <expr>", opts(max_depth=2)), 'max_depth')

```

```py
2

```

最后，我们定义一个设置特定选项的辅助函数：

```py
def set_opts(grammar, symbol, expansion, opts=None):
    """Set the options of the given expansion of grammar[symbol] to opts"""
    expansions = grammar[symbol]
    for i, exp in enumerate(expansions):
        if exp_string(exp) != exp_string(expansion):
            continue

        new_opts = exp_opts(exp)
        if opts is None or new_opts == {}:
            new_opts = opts
        else:
            for key in opts:
                new_opts[key] = opts[key]
        if new_opts == {}:
            grammar[symbol][i] = exp_string(exp)
        else:
            grammar[symbol][i] = (exp_string(exp), new_opts)
        return

    raise KeyError(
        "no expansion " +
        repr(symbol) +
        " -> " +
        repr(
            exp_string(expansion)))

```

## 检查语法

由于语法以字符串表示，因此引入错误非常容易。 因此，让我们介绍一个辅助函数，该函数检查语法的一致性。

辅助函数`is_valid_grammar()`遍历语法以检查是否定义了所有使用的符号，反之亦然，这对于调试非常有用； 它还检查从起始符号是否可以访问所有符号。 您不必在这里深入研究细节，但是与往常一样，在使用输入数据之前，请务必先弄清楚它。

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

```

```py
def def_used_nonterminals(grammar, start_symbol=START_SYMBOL):
    defined_nonterminals = set()
    used_nonterminals = {start_symbol}

    for defined_nonterminal in grammar:
        defined_nonterminals.add(defined_nonterminal)
        expansions = grammar[defined_nonterminal]
        if not isinstance(expansions, list):
            print(repr(defined_nonterminal) + ": expansion is not a list",
                  file=sys.stderr)
            return None, None

        if len(expansions) == 0:
            print(repr(defined_nonterminal) + ": expansion list empty",
                  file=sys.stderr)
            return None, None

        for expansion in expansions:
            if isinstance(expansion, tuple):
                expansion = expansion[0]
            if not isinstance(expansion, str):
                print(repr(defined_nonterminal) + ": "
                      + repr(expansion) + ": not a string",
                      file=sys.stderr)
                return None, None

            for used_nonterminal in nonterminals(expansion):
                used_nonterminals.add(used_nonterminal)

    return defined_nonterminals, used_nonterminals

```

```py
def reachable_nonterminals(grammar, start_symbol=START_SYMBOL):
    reachable = set()

    def _find_reachable_nonterminals(grammar, symbol):
        nonlocal reachable
        reachable.add(symbol)
        for expansion in grammar.get(symbol, []):
            for nonterminal in nonterminals(expansion):
                if nonterminal not in reachable:
                    _find_reachable_nonterminals(grammar, nonterminal)

    _find_reachable_nonterminals(grammar, start_symbol)
    return reachable

```

```py
def unreachable_nonterminals(grammar, start_symbol=START_SYMBOL):
    return grammar.keys() - reachable_nonterminals(grammar, start_symbol)

```

```py
def opts_used(grammar):
    used_opts = set()
    for symbol in grammar:
        for expansion in grammar[symbol]:
            used_opts |= set(exp_opts(expansion).keys())
    return used_opts

```

```py
def is_valid_grammar(grammar, start_symbol=START_SYMBOL, supported_opts=None):
    defined_nonterminals, used_nonterminals = \
        def_used_nonterminals(grammar, start_symbol)
    if defined_nonterminals is None or used_nonterminals is None:
        return False

    # Do not complain about '<start>' being not used,
    # even if start_symbol is different
    if START_SYMBOL in grammar:
        used_nonterminals.add(START_SYMBOL)

    for unused_nonterminal in defined_nonterminals - used_nonterminals:
        print(repr(unused_nonterminal) + ": defined, but not used",
              file=sys.stderr)
    for undefined_nonterminal in used_nonterminals - defined_nonterminals:
        print(repr(undefined_nonterminal) + ": used, but not defined",
              file=sys.stderr)

    # Symbols must be reachable either from <start> or given start symbol
    unreachable = unreachable_nonterminals(grammar, start_symbol)
    msg_start_symbol = start_symbol
    if START_SYMBOL in grammar:
        unreachable = unreachable - \
            reachable_nonterminals(grammar, START_SYMBOL)
        if start_symbol != START_SYMBOL:
            msg_start_symbol += " or " + START_SYMBOL
    for unreachable_nonterminal in unreachable:
        print(repr(unreachable_nonterminal) + ": unreachable from " + msg_start_symbol,
              file=sys.stderr)

    used_but_not_supported_opts = set()
    if supported_opts is not None:
        used_but_not_supported_opts = opts_used(
            grammar).difference(supported_opts)
        for opt in used_but_not_supported_opts:
            print(
                "warning: option " +
                repr(opt) +
                " is not supported",
                file=sys.stderr)

    return used_nonterminals == defined_nonterminals and len(unreachable) == 0

```

上面定义的语法通过了测试：

```py
assert is_valid_grammar(EXPR_GRAMMAR)
assert is_valid_grammar(CGI_GRAMMAR)
assert is_valid_grammar(URL_GRAMMAR)

```

该检查也可以应用于EBNF语法：

```py
assert is_valid_grammar(EXPR_EBNF_GRAMMAR)

```

但是，这些没有通过测试：

```py
assert not is_valid_grammar({"<start>": ["<x>"], "<y>": ["1"]})

```

```py
'<y>': defined, but not used
'<x>': used, but not defined
'<y>': unreachable from <start>

```

```py
assert not is_valid_grammar({"<start>": "123"})

```

```py
'<start>': expansion is not a list

```

```py
assert not is_valid_grammar({"<start>": []})

```

```py
'<start>': expansion list empty

```

```py
assert not is_valid_grammar({"<start>": [1, 2, 3]})

```

```py
'<start>': 1: not a string

```

从这里开始，在定义语法时，我们将始终使用`is_valid_grammar()`。

## 经验教训

*   语法是表达和产生语法有效输入的强大工具。
*   语法产生的输入可以直接使用，也可以用作基于突变的模糊测试的种子。
*   可以使用字符类和运算符扩展语法，以使编写更加容易。

## 后续步骤

由于它们为生成软件测试奠定了良好的基础，因此在本书中我们一再使用语法。 作为先睹为快，我们可以对[模糊配置](ConfigurationFuzzer.html)使用语法：

```py
<options> ::= <option>*
<option> ::= -h | --version | -v | -d | -i | --global-config <filename>
```

我们可以对[模糊功能和API](APIFuzzer.html) 和[模糊图形用户界面](WebFuzzer.html)使用语法：

```py
<call-sequence> ::= <call>*
<call> ::= urlparse(<url>) | urlsplit(<url>)
```

我们可以将[概率](ProbabilisticGrammarFuzzer.html)和[约束](GeneratorGrammarFuzzer.html)分配给各个扩展：

```py
<term>: 50% <factor> * <term> |  30% <factor> / <term> | 20% <factor>
<integer>: <digit>+ { <integer> >= 100 }
```

所有这些额外的东西变得特别有价值

1.  *自动推断语法*，无需手动指定语法，并且
2.  *指导他们实现特定目标*，例如覆盖范围或关键功能；

我们还将在本书中讨论所有技术。

但是，要到达那里，我们还有一些功课要做。 特别是，我们首先必须学习如何

*   [创建高效的语法模糊器](GrammarFuzzer.html)

## 背景

作为人类语言的基础之一，语法一直存在，只要人类语言存在就可以。 生成语法的第一个*形式化*是达克西·普特拉·帕尼（DakṣiputraPāṇini）于公元前350年。 。 作为表达用于数据和程序的形式语言的一般手段，不能夸大其在计算机科学中的作用。 Chomsky [ [Chomsky *等人*，1956\.](https://chomsky.info/wp-content/uploads/195609-.pdf) ]的开创性工作介绍了正则语言，上下文无关文法，上下文相关文法和通用文法的中心模型。 从那以后（并在计算机科学领域任教）作为一种指定输入和编程语言的方法。

语法在产生*的*测试输入中的使用可以追溯到Burkhardt [ [Burkhardt *等人*，1967年。](https://doi.org/10.1007/BF02235512)]，稍后由Hanford重新发现并应用 [] Hanford *等*，1970。](https://doi.org/10.1147/sj.94.0242)]和Purdom [ [Purdom *等*，1972。](https://doi.org/10.1007/BF01932308)]。 此后，语法测试最重要的用途是*编译器测试*。 实际上，基于语法的测试是编译器和Web浏览器按其应有的方式工作的重要原因之一：

*   [CSmith](https://embed.cs.utah.edu/csmith/) 工具[ [Yang *等*，2011年。](https://doi.org/10.1145/1993498.1993532)]特别针对C程序，从C语法开始，然后应用其他步骤，例如引用变量 和较早定义的函数，或确保整数和类型安全。 他们的作者使用它“查找并报告了400多个以前未知的编译器错误”。

*   [LangFuzz](http://issta2016.cispa.saarland/interview-with-christian-holler/) 作品[ [Holler *等人*，2012年。](https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final73.pdf)]与本书共有两名作者，使用通用语法产生输出，每天使用 晚上生成JavaScript程序并测试其解释器； 截止到今天，它已经在Mozilla Firefox，Google Chrome和Microsoft Edge等浏览器中发现了2,600多个错误。

*   [EMI项目](http://web.cs.ucdavis.edu/~su/emi-project/) [ [Le *等*，2014年。](https://doi.org/10.1145/2594291.2594334)]使用语法对C编译器进行压力测试，将已知测试转换为在语义上等效的替代程序 所有输入。 同样，这已经修复了100多个C编译器中的错误。

*   [Grammarinator](https://github.com/renatahodovan/grammarinator) [[Hodován*等人*，2018。](https://www.researchgate.net/publication/328510752_Grammarinator_a_grammar-based_open_source_fuzzer)]是一种开源语法模糊工具（用Python编写！），使用流行的ANTLR格式作为语法规范。 像LangFuzz一样，它使用语法进行解析和生成，并且在 *JerryScript* 轻量级JavaScript引擎和相关平台中发现了100多个问题。

*   [Domato](https://github.com/googleprojectzero/domato) 是一个通用的语法生成引擎，专门用于对DOM输入进行模糊处理。 它揭示了流行的Web浏览器中的许多安全问题。

当然，编译器和Web浏览器不仅是测试需要语法的领域，而且还是众所周知的语法的领域。 我们在本书中的主张是，语法可用于几乎生成任何输入*，而我们的目标是使您能够准确地做到这一点。*

## 练习

### 练习1：JSON语法

看一下 [JSON规范](http://www.json.org)并从中得出语法：

*   使用*字符类*表示有效字符
*   使用EBNF表示重复和可选部分
*   假使，假设
    *   字符串是数字，ASCII字母，标点和空格字符的序列，不带引号或转义符
    *   空白只是一个空格。
*   使用`is_valid_grammar()`确保语法有效。

将语法输入`simple_grammar_fuzzer()`。 您是否遇到任何错误，为什么？

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

### 练习2：查找错误

名称`simple_grammar_fuzzer()`并非偶然出现：它扩展语法的方式受到多种限制。 如果按上述定义在`nonterminal_grammar`和`expr_grammar`上应用`simple_grammar_fuzzer()`，会发生什么，为什么？

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

### 练习3：带有正则表达式的语法

在以正则表达式扩展的*语法中，我们可以使用特殊形式*

```py
/regex/
```

在扩展中包含正则表达式。 例如，我们可以有一条规则

```py
<integer> ::= /[+-]?[0-9]+/
```

快速表示整数是可选符号，后跟数字序列。

#### 第1部分：转换正则表达式

编写一个使用正则表达式`r`并创建等效语法的转换器`convert_regex(r)`。 支持以下正则表达式构造：

*   `*`，`+`，`?`，`()`应该仅在上述EBNF中起作用。
*   `a|b`应该转换为替代项列表`[a, b]`。
*   `.`应该匹配除换行符之外的任何字符。
*   `[abc]`应翻译为`srange("abc")`
*   `[^abc]`应该转换为ASCII字符集*，除了* `srange("abc")`之外。
*   `[a-b]`应翻译为`crange(a, b)`
*   `[^a-b]`应该转换为ASCII字符集*，除了* `crange(a, b)`之外。

示例：`convert_regex(r"[0-9]+")`应该产生一个语法，例如

```py
{
    "<start>": ["<s1>"],
    "<s1>": [ "<s2>", "<s1><s2>" ],
    "<s2>": crange('0', '9')
}

```

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

#### 第2部分：识别并扩展正则表达式

编写一个转换器`convert_regex_grammar(g)`，该转换器采用EBNF文法`g`，其中包含形式为`/.../`的正则表达式，并创建等效的BNF文法。 支持上面的正则表达式构造。

示例：`convert_regex_grammar({ "<integer>" : "/[+-]?[0-9]+/" })`应该产生一个语法，例如

```py
{
    "<integer>": ["<s1><s3>"],
    "<s1>": [ "", "<s2>" ],
    "<s2>": srange("+-"),
    "<s3>": [ "<s4>", "<s4><s3>" ],
    "<s4>": crange('0', '9')
}

```

可选：支持*以正则表达式转义*：`\c`转换为文字字符`c`； `\/`转换为`/`（因此不结束正则表达式）； `\\`转换为`\`。

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

### 练习4：将语法定义为函数（高级）)

为了获得用于指定语法的更好语法，可以使用Python构造，然后再通过另一种功能将*解析为*。 例如，我们可以想象一个语法定义，它使用`|`作为分隔替代项的手段：

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

```py
def expression_grammar_fn():
    start = "<expr>"
    expr = "<term> + <expr>" | "<term> - <expr>"
    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'

```

如果执行`expression_grammar_fn()`，将产生错误。 然而，`expression_grammar_fn()`的目的不是要执行，而是用作将要构造语法的*数据*。

```py
with ExpectError():
    expression_grammar_fn()

```

```py
Traceback (most recent call last):
  File "<ipython-input-109-612cec5468d3>", line 2, in <module>
    expression_grammar_fn()
  File "<ipython-input-108-f21ab929e5ee>", line 3, in expression_grammar_fn
    expr = "<term> + <expr>" | "<term> - <expr>"
TypeError: unsupported operand type(s) for |: 'str' and 'str' (expected)

```

为此，我们使用了`ast`（抽象语法树）和`inspect`（代码检查）模块。

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

```

首先，我们获得`expression_grammar_fn()`的源代码...

```py
source = inspect.getsource(expression_grammar_fn)
source

```

```py
'def expression_grammar_fn():\n    start = "<expr>"\n    expr = "<term> + <expr>" | "<term> - <expr>"\n    term = "<factor> * <term>" | "<factor> / <term>" | "<factor>"\n    factor = "+<factor>" | "-<factor>" | "(<expr>)" | "<integer>.<integer>" | "<integer>"\n    integer = "<digit><integer>" | "<digit>"\n    digit = \'0\' | \'1\' | \'2\' | \'3\' | \'4\' | \'5\' | \'6\' | \'7\' | \'8\' | \'9\'\n'

```

...然后将其解析为抽象语法树：

```py
tree = ast.parse(source)

```

现在，我们可以分析树以查找运算符和替代方法。 `get_alternatives()`遍历树的所有节点`op`； 如果该节点看起来像二进制*或*（`|`）操作，我们将进行更深入的挖掘并递归。 如果没有，那么我们已经达到了单一生产，并且我们尝试从生产中获得表达。 我们根据要表示产品的方式定义`to_expr`参数。 在这种情况下，我们用单个字符串表示单个产品。

```py
def get_alternatives(op, to_expr=lambda o: o.s):
    if isinstance(op, ast.BinOp) and isinstance(op.op, ast.BitOr):
        return get_alternatives(op.left, to_expr) + [to_expr(op.right)]
    return [to_expr(op)]

```

`funct_parser()`接受函数的抽象语法树（例如`expression_grammar_fn()`）并遍历所有分配：

```py
def funct_parser(tree, to_expr=lambda o: o.s):
    return {assign.targets[0].id: get_alternatives(assign.value, to_expr)
            for assign in tree.body[0].body}

```

结果是我们常规格式的语法：

```py
grammar = funct_parser(tree)
for symbol in grammar:
    print(symbol, "::=", grammar[symbol])

```

```py
start ::= ['<expr>']
expr ::= ['<term> + <expr>', '<term> - <expr>']
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']

```

#### 第1部分（a）：单个功能:-One-Single-Function)

编写单个函数`define_grammar(fn)`，该函数采用定义为函数的语法（例如`expression_grammar_fn()`）并返回常规语法。

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

#### 第1部分（b）：替代表示形式:-Alternative-representations)

我们注意到，我们先前设计的语法表示形式不允许简单生成替代项，例如`srange()`和`crange()`。 此外，可能会发现表达式的字符串表示形式受到限制。 事实证明，扩展我们的语法定义以支持如下语法很简单：

```py
def define_name(o):
    return o.id if isinstance(o, ast.Name) else o.s

```

```py
def define_expr(op):
    if isinstance(op, ast.BinOp) and isinstance(op.op, ast.Add):
        return (*define_expr(op.left), define_name(op.right))
    return (define_name(op),)

```

```py
def define_ex_grammar(fn):
    return define_grammar(fn, define_expr)

```

语法：

```py
@define_ex_grammar
def expression_grammar():
    start   = expr
    expr    = (term + '+' + expr
            |  term + '-' + expr)
    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'

for symbol in expression_grammar:
    print(symbol, "::=", expression_grammar[symbol])

```

**注意。** 这样获得的语法数据结构比标准数据结构更详细。 它表示每个生产为元组。

我们注意到，我们在上述语法中未启用`srange()`或`crange()`。 您将如何添加这些？ （*提示：*包装`define_expr()`以查找`ast.Call`）

#### 第2部分：扩展文法

引入一个带有一对`(min, max)`的运算符`*`，其中`min`和`max`分别是最小和最大重复次数。 缺失值`min`代表零； 无限值的缺失值`max`。

```py
def identifier_grammar_fn():
    identifier = idchar * (1,)

```

使用`*`运算符，我们可以概括EBNF运算符-`?`变为（0,1），`*`变为（0，），`+`变为（1，）。 编写一个转换器，该转换器采用使用`*`定义的扩展语法，对其进行解析，然后将其转换为BNF。

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