# 模糊API

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

到目前为止，我们始终生成*系统输入*，即程序整体通过其输入通道获得的数据。 但是，我们还可以生成直接输入各个功能的输入，从而在处理过程中获得灵活性和速度。 在本章中，我们探索使用语法来合成函数调用的代码，这使您可以生成*程序代码，该代码非常有效地直接调用函数。*

**前提条件**

*   您必须知道语法模糊处理的工作原理，例如 摘自[语法](Grammars.html)一章。
*   我们使用*生成器函数*，如在[关于使用生成器](GeneratorGrammarFuzzer.html)进行模糊化的章节中所讨论的。
*   如[关于概率模糊化](ProbabilisticGrammarFuzzer.html)的一章所述，我们利用了概率。

## 内容提要

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

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

```

然后利用以下功能。

本章提供了用于生成函数调用的语法的语法构造函数。

*   `INT_GRAMMAR`，`FLOAT_GRAMMAR`和`ASCII_STRING_GRAMMAR`分别产生整数，浮点数和字符串。
*   `int_grammar_with_range(start, end)`产生具有值`N`的整数语法，使得`start <= N <= end`。
*   `float_grammar_with_range(start, end)`产生具有值`N`的浮点语法，使得`start <= N <= end`。

语法为[概率](ProbabilisticGrammarFuzzer.html)，并使用[生成器](GeneratorGrammarFuzzer.html)，因此请使用`ProbabilisticGeneratorGrammarFuzzer`作为生成器。

```py
>>> from [GeneratorGrammarFuzzer](GeneratorGrammarFuzzer.html) import ProbabilisticGeneratorGrammarFuzzer
>>> int_grammar = int_grammar_with_range(100, 200)
>>> fuzzer = ProbabilisticGeneratorGrammarFuzzer(int_grammar)
>>> [fuzzer.fuzz() for i in range(10)]
['172', '102', '127', '119', '167', '186', '133', '155', '111', '111']

```

这些值可以立即用于测试函数调用：

```py
>>> from [math](https://docs.python.org/3/library/math.html) import sqrt
>>> eval("sqrt(" + fuzzer.fuzz() + ")")
13.45362404707371

```

这些语法也可以组成更复杂的语法：

*   `list_grammar(object_grammar)`返回产生`object_grammar`定义的对象列表的语法。

```py
>>> int_list_grammar = list_grammar(int_grammar)
>>> fuzzer = ProbabilisticGeneratorGrammarFuzzer(int_list_grammar)
>>> [fuzzer.fuzz() for i in range(5)]
['[194, 118, 169, 164, 169, 190, 172, 144, 174]',
 '[109, 127, 185, 155]',
 '[146, 103, 114, 185, 119, 148, 169, 167, 161]',
 '[]',
 '[138, 123, 147, 112, 139, 190, 114, 112]']
>>> eval("len(" + fuzzer.fuzz() + ")")
2

```

## 模糊函数

让我们从第一个问题开始：如何模糊给定函数？ 对于像Python这样的解释型语言，这非常简单。 我们需要做的就是对要测试的函数生成*调用*。 这是我们可以轻松完成的语法操作。

例如，考虑Python库中的`urlparse()`函数。 `urlparse()`提取一个URL，并将其分解为各个组件。

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

```

```py
from [urllib.parse](https://docs.python.org/3/library/urllib.parse.html) import urlparse

```

```py
urlparse('https://www.fuzzingbook.com/html/APIFuzzer.html')

```

```py
ParseResult(scheme='https', netloc='www.fuzzingbook.com', path='/html/APIFuzzer.html', params='', query='', fragment='')

```

您会看到URL的各个元素– *方案*（`"http"`），*网络位置*（`"www.fuzzingbook.com"`）或路径（`"//html/APIFuzzer.html"`）都如何正确 确定。 其他元素（例如`params`，`query`或`fragment`）为空，因为它们不是我们输入的一部分。

为了测试`urlparse()`，我们想要提供大量不同的URL。 我们可以从在[“语法”](Grammars.html) 一章中定义的URL语法中获得这些内容。

```py
from [Grammars](Grammars.html) import URL_GRAMMAR, is_valid_grammar, START_SYMBOL, new_symbol, opts, extend_grammar
from [GrammarFuzzer](GrammarFuzzer.html) import GrammarFuzzer, display_tree, all_terminals

```

```py
url_fuzzer = GrammarFuzzer(URL_GRAMMAR)

```

```py
for i in range(10):
    url = url_fuzzer.fuzz()
    print(urlparse(url))

```

```py
ParseResult(scheme='https', netloc='user:password@cispa.saarland:8080', path='/', params='', query='', fragment='')
ParseResult(scheme='http', netloc='cispa.saarland:1', path='/', params='', query='', fragment='')
ParseResult(scheme='https', netloc='fuzzingbook.com:7', path='', params='', query='', fragment='')
ParseResult(scheme='https', netloc='user:password@cispa.saarland:80', path='', params='', query='', fragment='')
ParseResult(scheme='ftps', netloc='user:password@fuzzingbook.com', path='', params='', query='', fragment='')
ParseResult(scheme='ftp', netloc='fuzzingbook.com', path='/abc', params='', query='abc=x31&def=x20', fragment='')
ParseResult(scheme='ftp', netloc='user:password@fuzzingbook.com', path='', params='', query='', fragment='')
ParseResult(scheme='https', netloc='www.google.com:80', path='/', params='', query='', fragment='')
ParseResult(scheme='http', netloc='fuzzingbook.com:52', path='/', params='', query='', fragment='')
ParseResult(scheme='ftps', netloc='user:password@cispa.saarland', path='', params='', query='', fragment='')

```

这样，我们可以轻松地测试任何Python函数-通过设置一个运行它的支架。 但是，如果我们想进行可以一次又一次地运行而又不必每次都生成新调用的测试，该如何进行呢？

## 合成代码

上面概述的“脚手架”方法有一个重要的缺点：它将测试生成和测试执行耦合到一个单元中，不允许在不同的时间或使用不同的语言运行。 为了使两者脱钩，我们采用了另一种方法：*而不是生成输入并立即将其输入到函数中，而是合成代码*来调用具有给定输入的函数。

例如，如果我们生成字符串

```py
call = "urlparse('http://www.example.com/')"

```

我们可以随时执行整个字符串（从而运行测试）：

```py
eval(call)

```

```py
ParseResult(scheme='http', netloc='www.example.com', path='/', params='', query='', fragment='')

```

为了系统地生成此类调用，我们可以再次使用语法：

```py
URLPARSE_GRAMMAR = {
    "<call>":
        ['urlparse("<url>")']
}

# Import definitions from URL_GRAMMAR
URLPARSE_GRAMMAR.update(URL_GRAMMAR)
URLPARSE_GRAMMAR["<start>"] = ["<call>"]

assert is_valid_grammar(URLPARSE_GRAMMAR)

```

该语法以`urlparse(<url>)`的形式创建调用，其中`<url>`来自“导入的” URL语法。 这个想法是创建许多这样的调用，并将它们提供给Python解释器。

```py
URLPARSE_GRAMMAR

```

```py
{'<call>': ['urlparse("<url>")'],
 '<start>': ['<call>'],
 '<url>': ['<scheme>://<authority><path><query>'],
 '<scheme>': ['http', 'https', 'ftp', 'ftps'],
 '<authority>': ['<host>',
  '<host>:<port>',
  '<userinfo>@<host>',
  '<userinfo>@<host>:<port>'],
 '<host>': ['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>': ['user:password'],
 '<path>': ['', '/', '/<id>'],
 '<id>': ['abc', 'def', 'x<digit><digit>'],
 '<query>': ['', '?<params>'],
 '<params>': ['<param>', '<param>&<params>'],
 '<param>': ['<id>=<id>', '<id>=<nat>']}

```

现在，我们可以使用此语法来模糊处理和合成对`urlparse)`的调用：

```py
urlparse_fuzzer = GrammarFuzzer(URLPARSE_GRAMMAR)
urlparse_fuzzer.fuzz()

```

```py
'urlparse("http://user:password@fuzzingbook.com:8080?abc=x29")'

```

如上所述，我们可以立即执行这些调用。 为了更好地了解正在发生的事情，我们定义了一个小的辅助函数：

```py
# Call function_name(arg[0], arg[1], ...) as a string
def do_call(call_string):
    print(call_string)
    result = eval(call_string)
    print("\t= " + repr(result))
    return result

```

```py
call = urlparse_fuzzer.fuzz()
do_call(call)

```

```py
urlparse("http://www.google.com?abc=def")
	= ParseResult(scheme='http', netloc='www.google.com', path='', params='', query='abc=def', fragment='')

```

```py
ParseResult(scheme='http', netloc='www.google.com', path='', params='', query='abc=def', fragment='')

```

例如，如果`urlparse()`是一个C函数，我们可以将其调用嵌入到某个（也生成的）C函数中：

```py
URLPARSE_C_GRAMMAR = {
    "<cfile>": ["<cheader><cfunction>"],
    "<cheader>": ['#include "urlparse.h"\n\n'],
    "<cfunction>": ["void test() {\n<calls>}\n"],
    "<calls>": ["<call>", "<calls><call>"],
    "<call>": ['    urlparse("<url>");\n']
}

```

```py
URLPARSE_C_GRAMMAR.update(URL_GRAMMAR)

```

```py
URLPARSE_C_GRAMMAR["<start>"] = ["<cfile>"]

```

```py
assert is_valid_grammar(URLPARSE_C_GRAMMAR)

```

```py
urlparse_fuzzer = GrammarFuzzer(URLPARSE_C_GRAMMAR)
print(urlparse_fuzzer.fuzz())

```

```py
#include "urlparse.h"

void test() {
    urlparse("http://user:password@cispa.saarland:99/x69?x57=abc");
}

```

## 合成Oracle

在我们的`urlparse()`示例中，Python和C变体都只检查`urlparse()`中的*通用*错误； 也就是说，它们仅检测致命的错误和异常。 为了进行全面测试，我们还需要设置一个特定的 *oracle* ，以检查结果是否有效。

我们的计划是检查URL的特定部分是否再次出现在结果中-也就是说，如果方案为`http:`，则返回的`ParseResult`也应该包含`http:`方案。 正如在[关于使用生成器](GeneratorGrammarFuzzer.html)进行模糊处理的章节中所讨论的那样，跨两个符号的诸如`http:`之类的字符串的相等性无法用上下文无关的语法表示。 但是，我们可以使用*生成器函数*（也在有关生成器的模糊化的[一章中介绍）来自动执行此类相等性。](GeneratorGrammarFuzzer.html)

这是一个例子。 在`urlparse()`结果上调用`geturl()`应该返回最初传递给`urlparse()`的URL。

```py
from [GeneratorGrammarFuzzer](GeneratorGrammarFuzzer.html) import GeneratorGrammarFuzzer, ProbabilisticGeneratorGrammarFuzzer

```

```py
URLPARSE_ORACLE_GRAMMAR = extend_grammar(URLPARSE_GRAMMAR,
{
     "<call>": [("assert urlparse('<url>').geturl() == '<url>'",
                 opts(post=lambda url_1, url_2: [None, url_1]))]
})

```

```py
urlparse_oracle_fuzzer = GeneratorGrammarFuzzer(URLPARSE_ORACLE_GRAMMAR)
test = urlparse_oracle_fuzzer.fuzz()
print(test)

```

```py
assert urlparse('https://user:password@cispa.saarland/abc?abc=abc').geturl() == 'https://user:password@cispa.saarland/abc?abc=abc'

```

```py
exec(test)

```

以类似的方式，我们还可以检查结果的各个组成部分：

```py
URLPARSE_ORACLE_GRAMMAR = extend_grammar(URLPARSE_GRAMMAR,
{
     "<call>": [("result = urlparse('<scheme>://<host><path>?<params>')\n"
                 # + "print(result)\n"
                 + "assert result.scheme == '<scheme>'\n"
                 + "assert result.netloc == '<host>'\n"
                 + "assert result.path == '<path>'\n"
                 + "assert result.query == '<params>'",
                 opts(post=lambda scheme_1, authority_1, path_1, params_1,
                      scheme_2, authority_2, path_2, params_2:
                      [None, None, None, None,
                       scheme_1, authority_1, path_1, params_1]))]
})

# Get rid of unused symbols
del URLPARSE_ORACLE_GRAMMAR["<url>"]
del URLPARSE_ORACLE_GRAMMAR["<query>"]
del URLPARSE_ORACLE_GRAMMAR["<authority>"]
del URLPARSE_ORACLE_GRAMMAR["<userinfo>"]
del URLPARSE_ORACLE_GRAMMAR["<port>"]

```

```py
urlparse_oracle_fuzzer = GeneratorGrammarFuzzer(URLPARSE_ORACLE_GRAMMAR)
test = urlparse_oracle_fuzzer.fuzz()
print(test)

```

```py
result = urlparse('https://www.google.com/?def=18&abc=abc')
assert result.scheme == 'https'
assert result.netloc == 'www.google.com'
assert result.path == '/'
assert result.query == 'def=18&abc=abc'

```

```py
exec(test)

```

生成器功能的使用可能会有些麻烦。 确实，如果我们独特地坚持使用Python，我们还可以创建一个*单元测试*，该单元测试直接调用模糊器以生成各个部分：

```py
def fuzzed_url_element(symbol):
    return GrammarFuzzer(URLPARSE_GRAMMAR, start_symbol=symbol).fuzz()

```

```py
scheme = fuzzed_url_element("<scheme>")
authority = fuzzed_url_element("<authority>")
path = fuzzed_url_element("<path>")
query = fuzzed_url_element("<params>")
url = "%s://%s%s?%s" % (scheme, authority, path, query)
result = urlparse(url)
# print(result)
assert result.geturl() == url
assert result.scheme == scheme
assert result.path == path
assert result.query == query

```

使用这样的单元测试可以更容易地表达预言。 但是，我们失去了像 [`GrammarCoverageFuzzer`](GrammarCoverageFuzzer.html) 一样系统地覆盖各个URL元素和替代项的能力，以及与 [`ProbabilisticGrammarFuzzer`](ProbabilisticGrammarFuzzer.html) 一样将生成导向特定元素的能力。 此外，语法使我们能够生成针对任意编程语言和API的测试。

## 合成数据

对于`urlparse()`，我们使用了非常具体的语法来创建非常具体的参数。 但是，许多函数都将基本数据类型作为（某些）参数。 因此，我们定义了精确生成这些参数的语法。 更好的是，我们可以定义*生成针对我们特定需求量身定制的*语法的函数，例如返回特定范围内的值。

### 整数

我们引入一个简单的语法来产生整数。

```py
from [Grammars](Grammars.html) import convert_ebnf_grammar, crange

```

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

```

```py
INT_EBNF_GRAMMAR = {
    "<start>": ["<int>"],
    "<int>": ["<_int>"],
    "<_int>": ["(-)?<leaddigit><digit>*", "0"],
    "<leaddigit>": crange('1', '9'),
    "<digit>": crange('0', '9')
}

assert is_valid_grammar(INT_EBNF_GRAMMAR)

```

```py
INT_GRAMMAR = convert_ebnf_grammar(INT_EBNF_GRAMMAR)
INT_GRAMMAR

```

```py
{'<start>': ['<int>'],
 '<int>': ['<_int>'],
 '<_int>': ['<symbol-1><leaddigit><digit-1>', '0'],
 '<leaddigit>': ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
 '<digit>': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
 '<symbol>': ['-'],
 '<symbol-1>': ['', '<symbol>'],
 '<digit-1>': ['', '<digit><digit-1>']}

```

```py
int_fuzzer = GrammarFuzzer(INT_GRAMMAR)
print([int_fuzzer.fuzz() for i in range(10)])

```

```py
['699', '-44', '321', '-7', '-6', '67', '0', '0', '57', '0']

```

如果我们需要特定范围内的整数，则可以添加一个生成器函数来实现以下目的：

```py
from [Grammars](Grammars.html) import set_opts

```

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

```

```py
def int_grammar_with_range(start, end):
    int_grammar = extend_grammar(INT_GRAMMAR)
    set_opts(int_grammar, "<int>", "<_int>",
        opts(pre=lambda: random.randint(start, end)))
    return int_grammar

```

```py
int_fuzzer = GeneratorGrammarFuzzer(int_grammar_with_range(900, 1000))
[int_fuzzer.fuzz() for i in range(10)]

```

```py
['942', '955', '997', '967', '939', '923', '984', '914', '991', '982']

```

### 浮点数

浮点值的语法与整数语法非常相似。

```py
FLOAT_EBNF_GRAMMAR = {
    "<start>": ["<float>"],
    "<float>": [("<_float>", opts(prob=0.9)), "inf", "NaN"],
    "<_float>": ["<int>(.<digit>+)?<exp>?"],
    "<exp>": ["e<int>"]
}
FLOAT_EBNF_GRAMMAR.update(INT_EBNF_GRAMMAR)
FLOAT_EBNF_GRAMMAR["<start>"] = ["<float>"]

assert is_valid_grammar(FLOAT_EBNF_GRAMMAR)

```

```py
FLOAT_GRAMMAR = convert_ebnf_grammar(FLOAT_EBNF_GRAMMAR)
FLOAT_GRAMMAR

```

```py
{'<start>': ['<float>'],
 '<float>': [('<_float>', {'prob': 0.9}), 'inf', 'NaN'],
 '<_float>': ['<int><symbol-2><exp-1>'],
 '<exp>': ['e<int>'],
 '<int>': ['<_int>'],
 '<_int>': ['<symbol-1-1><leaddigit><digit-1>', '0'],
 '<leaddigit>': ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
 '<digit>': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
 '<symbol>': ['.<digit-2>'],
 '<symbol-1>': ['-'],
 '<symbol-2>': ['', '<symbol>'],
 '<exp-1>': ['', '<exp>'],
 '<symbol-1-1>': ['', '<symbol-1>'],
 '<digit-1>': ['', '<digit><digit-1>'],
 '<digit-2>': ['<digit>', '<digit><digit-2>']}

```

```py
float_fuzzer = ProbabilisticGrammarFuzzer(FLOAT_GRAMMAR)
print([float_fuzzer.fuzz() for i in range(10)])

```

```py
['0', '-4e0', '-3.3', '0.55e0', '0e2', '0.2', '-48.6e0', '0.216', '-4.844', '-6.100']

```

```py
def float_grammar_with_range(start, end):
    float_grammar = extend_grammar(FLOAT_GRAMMAR)
    set_opts(float_grammar, "<float>", "<_float>", opts(
        pre=lambda: start + random.random() * (end - start)))
    return float_grammar

```

```py
float_fuzzer = ProbabilisticGeneratorGrammarFuzzer(
    float_grammar_with_range(900.0, 900.9))
[float_fuzzer.fuzz() for i in range(10)]

```

```py
['900.1695968039919',
 '900.3273891873373',
 '900.225192820568',
 '900.3231805358258',
 '900.4963527393471',
 'inf',
 'inf',
 '900.6037658059212',
 '900.6212350658716',
 '900.3877831415683']

```

### 字符串

最后，我们介绍一种用于产生字符串的语法。

```py
ASCII_STRING_EBNF_GRAMMAR = {
    "<start>": ["<ascii-string>"],
    "<ascii-string>": ['"<ascii-chars>"'],
    "<ascii-chars>": [
        ("", opts(prob=0.05)),
        "<ascii-chars><ascii-char>"
    ],
    "<ascii-char>": crange(" ", "!") + [r'\"'] + crange("#", "~")
}

assert is_valid_grammar(ASCII_STRING_EBNF_GRAMMAR)

```

```py
ASCII_STRING_GRAMMAR = convert_ebnf_grammar(ASCII_STRING_EBNF_GRAMMAR)

```

```py
string_fuzzer = ProbabilisticGrammarFuzzer(ASCII_STRING_GRAMMAR)
print([string_fuzzer.fuzz() for i in range(10)])

```

```py
['"BgY)"', '"j[-64Big65wso(f:wg|}w&*D9JthLX}0@PT^]mr[`69Cq8H713ITYx<#jpml)\\""', '"{);XWZJ@d`\'[h#F{1)C9M?%C`="', '"Y"', '"C4gh`?uzJzD~$\\\\"=|j)jj=SrBLIJ@0IbYiwIvNf5#pT4QUR}[g,35?Wg4i?3TdIsR0|eq3r;ZKuyI\'<\\"[p/x$<$B!\\"_"', '"J0HG33+E(p8JQtKW.;G7 ^?."', '"7r^B:Jf*J.@sqfED|M)3,eJ&OD"', '"c3Hcx^&*~3\\"Jvac}cX"', '"\'IHBQ:N+U:w(OAFn0pHLzX"', '"x4agH>H-2{Q|\\kpYF"']

```

## 合成复合数据

如上所述，根据基本数据，我们还可以在数据结构（例如集合或列表）中生成*复合数据*。 我们在列表中说明了这种生成。

### 列出

```py
LIST_EBNF_GRAMMAR = {
    "<start>": ["<list>"],
    "<list>": [
        ("[]", opts(prob=0.05)),
        "[<list-objects>]"
    ],
    "<list-objects>": [
        ("<list-object>", opts(prob=0.2)),
        "<list-object>, <list-objects>"
    ],
    "<list-object>": ["0"],
}

assert is_valid_grammar(LIST_EBNF_GRAMMAR)

```

```py
LIST_GRAMMAR = convert_ebnf_grammar(LIST_EBNF_GRAMMAR)

```

我们的列表生成器采用产生对象的语法； 然后，使用这些语法中的对象实例化列表语法。

```py
def list_grammar(object_grammar, list_object_symbol=None):
    obj_list_grammar = extend_grammar(LIST_GRAMMAR)
    if list_object_symbol is None:
        # Default: Use the first expansion of <start> as list symbol
        list_object_symbol = object_grammar[START_SYMBOL][0]

    obj_list_grammar.update(object_grammar)
    obj_list_grammar[START_SYMBOL] = ["<list>"]
    obj_list_grammar["<list-object>"] = [list_object_symbol]

    assert is_valid_grammar(obj_list_grammar)

    return obj_list_grammar

```

```py
int_list_fuzzer = ProbabilisticGrammarFuzzer(list_grammar(INT_GRAMMAR))
[int_list_fuzzer.fuzz() for i in range(10)]

```

```py
['[0, -4, 23, 0, 0, 9, 0, -6067681]',
 '[-1, -1, 0, -7]',
 '[-5, 0]',
 '[1, 0, -628088, -6, -811, 0, 99, 0]',
 '[-35, -10, 0, 67]',
 '[-3, 0, -2, 0, 0]',
 '[0, -267, -78, -733, 0, 0, 0, 0]',
 '[0, -6, 71, -9]',
 '[-72, 76, 0, 2]',
 '[0, 9, 0, 0, -572, 29, 8, 8, 0]']

```

```py
string_list_fuzzer = ProbabilisticGrammarFuzzer(
    list_grammar(ASCII_STRING_GRAMMAR))
[string_list_fuzzer.fuzz() for i in range(10)]

```

```py
['["gn-A$j>", "SPX;", "", "", ""]',
 '["_", "Qp"]',
 '["M", "5\\"`X744", "b+5fyM!", "gR`"]',
 '["^h", "8$u", "", "", ""]',
 '["6X;", "", "T1wp%\'t"]',
 '["-?Kk", "@B", "}", "", ""]',
 '["FD<mqK", ")Y4NI3M.&@1/2.p", "]C#c1}z#+5{7ERA[|", "EOFM])BEMFcGM.~k&RMj*,:m8^!5*:vv%ci"]',
 '["", "*B.pKI\\"L", "O)#<Y", "\\", "", "", ""]',
 '["g"]',
 '["", "\\JS;~t", "h)", "k", "", ""]']

```

```py
float_list_fuzzer = ProbabilisticGeneratorGrammarFuzzer(list_grammar(
    float_grammar_with_range(900.0, 900.9)))
[float_list_fuzzer.fuzz() for i in range(10)]

```

```py
['[900.558064701869, 900.6079527708223, 900.1985188111297, 900.5159940886509, 900.1881413629061, 900.4074809145482, 900.8279453113845, 900.1531931708976, 900.2651056125504, inf, 900.828295978669]',
 '[900.4956935906264, 900.8166792417645, 900.2044872129637]',
 '[900.6177668624133, 900.793129850367, 900.5024769009476, 900.5874531663001, inf, 900.3476216137291, 900.5680329060473, 900.1524624203945, 900.1157565249836, 900.0943774301732, 900.1589468212459, 900.8563415304703, 900.2871041191156, 900.2469765832253, 900.408183791468]',
 '[NaN, 900.1152482126347, 900.1139109179966, NaN, 900.0634308730662, 900.1918596242257]',
 '[900.49418992478]',
 '[900.6566851795975, NaN, 900.5585085641878, 900.8678799526169, 900.5580757140183]',
 '[900.6265067760952]',
 '[900.5271187218734, 900.3413004135587, 900.0362652510535, 900.2938223153569, 900.6584186055829, 900.5394909707123, 900.5119630230411, 900.2024669591465]',
 '[900.5068304562362, 900.5173419618334, 900.5268996804168, 900.5247314889621, 900.1082421801126, 900.761200730868, 900.100950598924, 900.1424140649187, inf, inf, 900.4546924838603, 900.7025508468811, 900.5147250716594, 900.4943696257178, 900.814107878577, 900.3540228715348, 900.6165673939341, 900.121833279104, 900.8337503512706, 900.0607374037857, 900.2746253938637, 900.2491844866619, 900.7325728031923]',
 '[900.6962790125643, 900.6055198052603, 900.0950691946015, 900.6283670716376, NaN, 900.112869956762]']

```

字典，集合等的生成器可以用类似的方式定义。 通过将语法生成器连接在一起。 我们可以产生具有任意元素的数据结构。

## 经验教训

*   要模糊各个功能，可以轻松设置产生功能调用的语法。
*   API级别的模糊处理比系统级别的模糊处理要快得多，但是由于违反了隐式前提条件，因此存在错误警报的风险。

## 后续步骤

本章都是关于手动编写测试并控制生成哪些数据的。 [在下一章](Carver.html)中，我们将介绍更高级别的自动化：

*   *雕刻*自动记录程序执行中的函数调用和参数。
*   我们可以将它们转换为*语法*，从而可以使用各种记录值组合来测试这些功能。

使用这些技术，我们可以自动获得已经在应用程序上下文中调用函数的语法，从而使我们更容易地指定它们。

## 背景

首先在QuickCheck [ [Claessen *等人*，2000。](https://doi.org/10.1145/351240.351266)]中探讨了使用生成器函数生成输入结构的想法。 Python的一个很好的实现是[假设程序包](https://hypothesis.readthedocs.io/en/latest/)，它允许编写和组合数据结构生成器以测试API。

## 练习

本章的练习将上述技术与前面介绍的模糊技术结合在一起。

### 练习1：深层争论

在为`urlparse()`生成预言的示例中，未检查诸如`authority`或`port`之类的重要元素。 使用扩展后功能丰富`URLPARSE_ORACLE_GRAMMAR`，该功能将生成的元素存储在符号表中，以便在生成断言时可以对其进行访问。

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

### 练习2：覆盖参数组合

在有关[配置测试](ConfigurationFuzzer.html)的章节中，我们还讨论了*组合测试* –即，对*集*的配置元素的系统介绍。 通过更改语法来实现一种方案，该方案允许覆盖所有*对*参数值。

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

### 练习3：变异参数

要扩大测试期间使用的参数范围，请应用[突变模糊化](MutationFuzzer.html)中引入的*突变方案* –例如，翻转单个字节或从字符串中删除字符。 在语法推断过程中或在调用函数时将其应用为单独步骤。

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