{ "cells": [ { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" }, "tags": [] }, "source": [ "# Code Coverage\n", "\n", "In the [previous chapter](Fuzzer.ipynb), we introduced _basic fuzzing_ – that is, generating random inputs to test programs. How do we measure the effectiveness of these tests? One way would be to check the number (and seriousness) of bugs found; but if bugs are scarce, we need a _proxy for the likelihood of a test to uncover a bug._ In this chapter, we introduce the concept of *code coverage*, measuring which parts of a program are actually executed during a test run. Measuring such coverage is also crucial for test generators that attempt to cover as much code as possible." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:45.905573Z", "iopub.status.busy": "2025-01-16T09:37:45.905477Z", "iopub.status.idle": "2025-01-16T09:37:45.998670Z", "shell.execute_reply": "2025-01-16T09:37:45.998421Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from bookutils import YouTubeVideo\n", "YouTubeVideo('8HxW8j9287A')" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "skip" } }, "source": [ "**Prerequisites**\n", "\n", "* You need some understanding of how a program is executed.\n", "* You should have learned about basic fuzzing in the [previous chapter](Fuzzer.ipynb)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" }, "tags": [] }, "source": [ "## Synopsis\n", "\n", "\n", "To [use the code provided in this chapter](Importing.ipynb), write\n", "\n", "```python\n", ">>> from fuzzingbook.Coverage import \n", "```\n", "\n", "and then make use of the following features.\n", "\n", "\n", "This chapter introduces a `Coverage` class allowing you to measure coverage for Python programs. Within the context of this book, we use coverage information to guide fuzzing towards uncovered locations.\n", "\n", "The typical usage of the `Coverage` class is in conjunction with a `with` clause:\n", "\n", "```python\n", ">>> with Coverage() as cov:\n", ">>> cgi_decode(\"a+b\")\n", "```\n", "Printing out a coverage object shows the covered functions, with non-covered lines prefixed with `#`:\n", "\n", "```python\n", ">>> print(cov)\n", "# 1 def cgi_decode(s: str) -> str:\n", "# 2 \"\"\"Decode the CGI-encoded string `s`:\n", "# 3 * replace '+' by ' '\n", "# 4 * replace \"%xx\" by the character with hex number xx.\n", "# 5 Return the decoded string. Raise `ValueError` for invalid inputs.\"\"\"\n", "# 6 \n", "# 7 # Mapping of hex digits to their integer values\n", " 8 hex_values = {\n", " 9 '0': 0, '1': 1, '2': 2, '3': 3, '4': 4,\n", " 10 '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,\n", " 11 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,\n", " 12 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,\n", "# 13 }\n", "# 14 \n", " 15 t = \"\"\n", " 16 i = 0\n", " 17 while i < len(s):\n", " 18 c = s[i]\n", " 19 if c == '+':\n", " 20 t += ' '\n", " 21 elif c == '%':\n", "# 22 digit_high, digit_low = s[i + 1], s[i + 2]\n", "# 23 i += 2\n", "# 24 if digit_high in hex_values and digit_low in hex_values:\n", "# 25 v = hex_values[digit_high] * 16 + hex_values[digit_low]\n", "# 26 t += chr(v)\n", "# 27 else:\n", "# 28 raise ValueError(\"Invalid encoding\")\n", "# 29 else:\n", " 30 t += c\n", " 31 i += 1\n", " 32 return t\n", "\n", "\n", "```\n", "The `trace()` method returns the _trace_ – that is, the list of locations executed in order. Each location comes as a pair (`function name`, `line`).\n", "\n", "```python\n", ">>> cov.trace()\n", "[('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 12),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 12),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 15),\n", " ('cgi_decode', 16),\n", " ('cgi_decode', 17),\n", " ('cgi_decode', 18),\n", " ('cgi_decode', 19),\n", " ('cgi_decode', 21),\n", " ('cgi_decode', 30),\n", " ('cgi_decode', 31),\n", " ('cgi_decode', 17),\n", " ('cgi_decode', 18),\n", " ('cgi_decode', 19),\n", " ('cgi_decode', 20),\n", " ('cgi_decode', 31),\n", " ('cgi_decode', 17),\n", " ('cgi_decode', 18),\n", " ('cgi_decode', 19),\n", " ('cgi_decode', 21),\n", " ('cgi_decode', 30),\n", " ('cgi_decode', 31),\n", " ('cgi_decode', 17),\n", " ('cgi_decode', 32)]\n", "```\n", "The `coverage()` method returns the _coverage_, that is, the set of locations in the trace executed at least once:\n", "\n", "```python\n", ">>> cov.coverage()\n", "{('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 12),\n", " ('cgi_decode', 15),\n", " ('cgi_decode', 16),\n", " ('cgi_decode', 17),\n", " ('cgi_decode', 18),\n", " ('cgi_decode', 19),\n", " ('cgi_decode', 20),\n", " ('cgi_decode', 21),\n", " ('cgi_decode', 30),\n", " ('cgi_decode', 31),\n", " ('cgi_decode', 32)}\n", "```\n", "Coverage sets can be subject to set operations, such as _intersection_ (which locations are covered in multiple executions) and _difference_ (which locations are covered in run _a_, but not _b_).\n", "\n", "The chapter also discusses how to obtain such coverage from C programs.\n", "\n", "![](PICS/Coverage-synopsis-1.svg)\n", "\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:46.018824Z", "iopub.status.busy": "2025-01-16T09:37:46.018681Z", "iopub.status.idle": "2025-01-16T09:37:46.020955Z", "shell.execute_reply": "2025-01-16T09:37:46.020629Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import bookutils.setup" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:46.022678Z", "iopub.status.busy": "2025-01-16T09:37:46.022570Z", "iopub.status.idle": "2025-01-16T09:37:46.024268Z", "shell.execute_reply": "2025-01-16T09:37:46.023973Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# ignore\n", "from typing import Any, Optional, Callable, List, Type, Set, Tuple" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## A CGI Decoder\n", "\n", "We start by introducing a simple Python function that decodes a CGI-encoded string. CGI encoding is used in URLs (i.e., Web addresses) to encode characters that would be invalid in a URL, such as blanks and certain punctuation:\n", "\n", "* Blanks are replaced by `'+'`\n", "* Other invalid characters are replaced by '`%xx`', where `xx` is the two-digit hexadecimal equivalent.\n", "\n", "In CGI encoding, the string `\"Hello, world!\"` would thus become `\"Hello%2c+world%21\"` where `2c` and `21` are the hexadecimal equivalents of `','` and `'!'`, respectively.\n", "\n", "The function `cgi_decode()` takes such an encoded string and decodes it back to its original form. Our implementation replicates the code from \\cite{Pezze2008}. (It even includes its bugs – but we won't reveal them at this point.)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.026068Z", "iopub.status.busy": "2025-01-16T09:37:46.025964Z", "iopub.status.idle": "2025-01-16T09:37:46.028673Z", "shell.execute_reply": "2025-01-16T09:37:46.028449Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def cgi_decode(s: str) -> str:\n", " \"\"\"Decode the CGI-encoded string `s`:\n", " * replace '+' by ' '\n", " * replace \"%xx\" by the character with hex number xx.\n", " Return the decoded string. Raise `ValueError` for invalid inputs.\"\"\"\n", "\n", " # Mapping of hex digits to their integer values\n", " hex_values = {\n", " '0': 0, '1': 1, '2': 2, '3': 3, '4': 4,\n", " '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,\n", " 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,\n", " 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,\n", " }\n", "\n", " t = \"\"\n", " i = 0\n", " while i < len(s):\n", " c = s[i]\n", " if c == '+':\n", " t += ' '\n", " elif c == '%':\n", " digit_high, digit_low = s[i + 1], s[i + 2]\n", " i += 2\n", " if digit_high in hex_values and digit_low in hex_values:\n", " v = hex_values[digit_high] * 16 + hex_values[digit_low]\n", " t += chr(v)\n", " else:\n", " raise ValueError(\"Invalid encoding\")\n", " else:\n", " t += c\n", " i += 1\n", " return t" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Here is an example of how `cgi_decode()` works:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.030167Z", "iopub.status.busy": "2025-01-16T09:37:46.030071Z", "iopub.status.idle": "2025-01-16T09:37:46.032121Z", "shell.execute_reply": "2025-01-16T09:37:46.031823Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'Hello world'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cgi_decode(\"Hello+world\")" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "source": [ "If we want to systematically test `cgi_decode()`, how would we proceed?" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "skip" } }, "source": [ "The testing literature distinguishes two ways of deriving tests: _Black-box testing_ and _White-box testing._" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Black-Box Testing\n", "\n", "The idea of *black-box testing* is to derive tests from the _specification_. In the above case, we thus would have to test `cgi_decode()` by the features specified and documented, including\n", "\n", "* testing for correct replacement of `'+'`;\n", "* testing for correct replacement of `\"%xx\"`;\n", "* testing for non-replacement of other characters; and\n", "* testing for recognition of illegal inputs.\n", "\n", "Here are four assertions (tests) that cover these four features. We can see that they all pass:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.033830Z", "iopub.status.busy": "2025-01-16T09:37:46.033717Z", "iopub.status.idle": "2025-01-16T09:37:46.035633Z", "shell.execute_reply": "2025-01-16T09:37:46.035393Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert cgi_decode('+') == ' '\n", "assert cgi_decode('%20') == ' '\n", "assert cgi_decode('abc') == 'abc'\n", "\n", "try:\n", " cgi_decode('%?a')\n", " assert False\n", "except ValueError:\n", " pass" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "The advantage of black-box testing is that it finds errors in the _specified_ behavior. It is independent of a given implementation, and thus allows creating tests even before implementation. The downside is that _implemented_ behavior typically covers more ground than _specified_ behavior, and thus tests based on specification alone typically do not cover all implementation details." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## White-Box Testing\n", "\n", "In contrast to black-box testing, *white-box testing* derives tests from the _implementation_, notably the internal structure. White-Box testing is closely tied to the concept of _covering_ structural features of the code. If a statement in the code is not executed during testing, for instance, this means that an error in this statement cannot be triggered either. White-Box testing thus introduces a number of *coverage criteria* that have to be fulfilled before the test can be said to be sufficient. The most frequently used coverage criteria are\n", "\n", "* *Statement coverage* – each statement in the code must be executed by at least one test input.\n", "* *Branch coverage* – each branch in the code must be taken by at least one test input. (This translates to each `if` and `while` decision once being true, and once being false.)\n", "\n", "Besides these, there are far more coverage criteria, including sequences of branches taken, loop iterations taken (zero, one, many), data flows between variable definitions and usages, and many more; \\cite{Pezze2008} has a great overview." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Let us consider `cgi_decode()`, above, and reason what we have to do such that each statement of the code is executed at least once. We'd have to cover\n", "\n", "* The block following `if c == '+'`\n", "* The two blocks following `if c == '%'` (one for valid input, one for invalid)\n", "* The final `else` case for all other characters.\n", "\n", "This results in the same conditions as with black-box testing, above; again, the assertions above indeed would cover every statement in the code. Such a correspondence is actually pretty common, since programmers tend to implement different behaviors in different code locations; and thus, covering these locations will lead to test cases that cover the different (specified) behaviors.\n", "\n", "The advantage of white-box testing is that it finds errors in _implemented_ behavior. It can be conducted even in cases where the specification does not provide sufficient details; actually, it helps in identifying (and thus specifying) corner cases in the specification. The downside is that it may miss _non-implemented_ behavior: If some specified functionality is missing, white-box testing will not find it." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Tracing Executions\n", "\n", "One nice feature of white-box testing is that one can actually automatically assess whether some program feature was covered. To this end, one _instruments_ the execution of the program such that during execution, a special functionality keeps track of which code was executed. After testing, this information can be passed to the programmer, who can then focus on writing tests that cover the yet uncovered code." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "In most programming languages, it is rather difficult to set up programs such that one can trace their execution. Not so in Python. The function `sys.settrace(f)` allows defining a *tracing function* `f()` that is called for each and every line executed. Even better, it gets access to the current function and its name, current variable contents, and more. It is thus an ideal tool for *dynamic analysis* – that is, the analysis of what actually happens during an execution." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "To illustrate how this works, let us again look into a specific execution of `cgi_decode()`." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.037191Z", "iopub.status.busy": "2025-01-16T09:37:46.037091Z", "iopub.status.idle": "2025-01-16T09:37:46.038985Z", "shell.execute_reply": "2025-01-16T09:37:46.038775Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'a b'" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cgi_decode(\"a+b\")" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "To track how the execution proceeds through `cgi_decode()`, we make use of `sys.settrace()`. First, we define the _tracing function_ that will be called for each line. It has three parameters: \n", "\n", "* The `frame` parameter gets you the current _frame_, allowing access to the current location and variables:\n", " * `frame.f_code` is the currently executed code with `frame.f_code.co_name` being the function name;\n", " * `frame.f_lineno` holds the current line number; and\n", " * `frame.f_locals` holds the current local variables and arguments.\n", "* The `event` parameter is a string with values including `\"line\"` (a new line has been reached) or `\"call\"` (a function is being called).\n", "* The `arg` parameter is an additional _argument_ for some events; for `\"return\"` events, for instance, `arg` holds the value being returned." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "We use the tracing function for simply reporting the current line executed, which we access through the `frame` argument." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:46.040404Z", "iopub.status.busy": "2025-01-16T09:37:46.040306Z", "iopub.status.idle": "2025-01-16T09:37:46.041829Z", "shell.execute_reply": "2025-01-16T09:37:46.041569Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from types import FrameType, TracebackType" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.043244Z", "iopub.status.busy": "2025-01-16T09:37:46.043164Z", "iopub.status.idle": "2025-01-16T09:37:46.044909Z", "shell.execute_reply": "2025-01-16T09:37:46.044657Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "coverage = []" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.046322Z", "iopub.status.busy": "2025-01-16T09:37:46.046233Z", "iopub.status.idle": "2025-01-16T09:37:46.048403Z", "shell.execute_reply": "2025-01-16T09:37:46.048145Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def traceit(frame: FrameType, event: str, arg: Any) -> Optional[Callable]:\n", " \"\"\"Trace program execution. To be passed to sys.settrace().\"\"\"\n", " if event == 'line':\n", " global coverage\n", " function_name = frame.f_code.co_name\n", " lineno = frame.f_lineno\n", " coverage.append(lineno)\n", "\n", " return traceit" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "We can switch tracing on and off with `sys.settrace()`:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.050039Z", "iopub.status.busy": "2025-01-16T09:37:46.049904Z", "iopub.status.idle": "2025-01-16T09:37:46.051914Z", "shell.execute_reply": "2025-01-16T09:37:46.051535Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import sys" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.054089Z", "iopub.status.busy": "2025-01-16T09:37:46.053959Z", "iopub.status.idle": "2025-01-16T09:37:46.056059Z", "shell.execute_reply": "2025-01-16T09:37:46.055622Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def cgi_decode_traced(s: str) -> None:\n", " global coverage\n", " coverage = []\n", " sys.settrace(traceit) # Turn on\n", " cgi_decode(s)\n", " sys.settrace(None) # Turn off" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "When we compute `cgi_decode(\"a+b\")`, we can now see how the execution progresses through `cgi_decode()`. After the initialization of `hex_values`, `t`, and `i`, we see that the `while` loop is taken three times – one for every character in the input." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.058038Z", "iopub.status.busy": "2025-01-16T09:37:46.057917Z", "iopub.status.idle": "2025-01-16T09:37:46.060042Z", "shell.execute_reply": "2025-01-16T09:37:46.059790Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 10, 8, 10, 8, 10, 8, 10, 8, 10, 8, 11, 8, 11, 8, 11, 8, 11, 8, 11, 8, 11, 8, 12, 8, 12, 8, 15, 16, 17, 18, 19, 21, 30, 31, 17, 18, 19, 20, 31, 17, 18, 19, 21, 30, 31, 17, 32]\n" ] } ], "source": [ "cgi_decode_traced(\"a+b\")\n", "print(coverage)" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Which lines are these, actually? To this end, we get the source code of `cgi_decode_code` and encode it into an array `cgi_decode_lines`, which we will then annotate with coverage information. First, let us get the source code of `cgi_encode`:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:46.061548Z", "iopub.status.busy": "2025-01-16T09:37:46.061445Z", "iopub.status.idle": "2025-01-16T09:37:46.062945Z", "shell.execute_reply": "2025-01-16T09:37:46.062714Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import inspect" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:46.064367Z", "iopub.status.busy": "2025-01-16T09:37:46.064289Z", "iopub.status.idle": "2025-01-16T09:37:46.066249Z", "shell.execute_reply": "2025-01-16T09:37:46.066021Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "cgi_decode_code = inspect.getsource(cgi_decode)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "`cgi_decode_code` is a string holding the source code. We can print it out with Python syntax highlighting:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:46.067712Z", "iopub.status.busy": "2025-01-16T09:37:46.067635Z", "iopub.status.idle": "2025-01-16T09:37:46.069259Z", "shell.execute_reply": "2025-01-16T09:37:46.069053Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from bookutils import print_content, print_file" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:46.070658Z", "iopub.status.busy": "2025-01-16T09:37:46.070582Z", "iopub.status.idle": "2025-01-16T09:37:46.135917Z", "shell.execute_reply": "2025-01-16T09:37:46.135498Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[34mdef\u001b[39;49;00m \u001b[32mcgi_decode\u001b[39;49;00m(s: \u001b[36mstr\u001b[39;49;00m) -> \u001b[36mstr\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[33m\"\"\"Decode the CGI-encoded string `s`:\u001b[39;49;00m\n", "\u001b[33m * replace '+' by ' '\u001b[39;49;00m\n", "\u001b[33m * replace \"%xx\" by the character with hex number xx.\u001b[39;49;00m\n", "\u001b[33m Return the decoded string. Raise `ValueError` for invalid inputs.\"\"\"\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", "\u001b[37m\u001b[39;49;00m\n", " \u001b[37m# Mapping of hex digits to their integer values\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " hex_v...\u001b[37m\u001b[39;49;00m" ] } ], "source": [ "print_content(cgi_decode_code[:300] + \"...\", \".py\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Using `splitlines()`, we split the code into an array of lines, indexed by line number." ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.137721Z", "iopub.status.busy": "2025-01-16T09:37:46.137484Z", "iopub.status.idle": "2025-01-16T09:37:46.139288Z", "shell.execute_reply": "2025-01-16T09:37:46.139065Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "cgi_decode_lines = [\"\"] + cgi_decode_code.splitlines()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "`cgi_decode_lines[L]` is line L of the source code." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.140657Z", "iopub.status.busy": "2025-01-16T09:37:46.140569Z", "iopub.status.idle": "2025-01-16T09:37:46.142508Z", "shell.execute_reply": "2025-01-16T09:37:46.142292Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'def cgi_decode(s: str) -> str:'" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cgi_decode_lines[1]" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "We see that the first line (9) executed is actually the initialization of `hex_values`..." ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.143903Z", "iopub.status.busy": "2025-01-16T09:37:46.143813Z", "iopub.status.idle": "2025-01-16T09:37:46.145719Z", "shell.execute_reply": "2025-01-16T09:37:46.145497Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[\" '0': 0, '1': 1, '2': 2, '3': 3, '4': 4,\",\n", " \" '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,\",\n", " \" 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,\",\n", " \" 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,\"]" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cgi_decode_lines[9:13]" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "source": [ "... followed by the initialization of `t`:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.147271Z", "iopub.status.busy": "2025-01-16T09:37:46.147168Z", "iopub.status.idle": "2025-01-16T09:37:46.149026Z", "shell.execute_reply": "2025-01-16T09:37:46.148777Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "' t = \"\"'" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cgi_decode_lines[15]" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "To see which lines actually have been covered at least once, we can convert `coverage` into a set:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.150434Z", "iopub.status.busy": "2025-01-16T09:37:46.150331Z", "iopub.status.idle": "2025-01-16T09:37:46.152036Z", "shell.execute_reply": "2025-01-16T09:37:46.151792Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{32, 8, 9, 10, 11, 12, 15, 16, 17, 18, 19, 20, 21, 30, 31}\n" ] } ], "source": [ "covered_lines = set(coverage)\n", "print(covered_lines)" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Let us print out the full code, annotating lines not covered with `#`.\n", "The idea of such an annotation is to direct developer's attention to the non-covered lines." ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.153515Z", "iopub.status.busy": "2025-01-16T09:37:46.153424Z", "iopub.status.idle": "2025-01-16T09:37:46.442824Z", "shell.execute_reply": "2025-01-16T09:37:46.442537Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "# 1 \u001b[34mdef\u001b[39;49;00m \u001b[32mcgi_decode\u001b[39;49;00m(s: \u001b[36mstr\u001b[39;49;00m) -> \u001b[36mstr\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n", "# 2 \u001b[33m\"\"\"\u001b[39;49;00m\u001b[33mDecode the CGI-encoded string `s`:\u001b[39;49;00m\u001b[33m\u001b[39;49;00m\n", "# 3 * replace \u001b[33m'\u001b[39;49;00m\u001b[33m+\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m by \u001b[33m'\u001b[39;49;00m\u001b[33m \u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", "# 4 * replace \u001b[33m\"\u001b[39;49;00m\u001b[33m%x\u001b[39;49;00m\u001b[33mx\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m by the character \u001b[34mwith\u001b[39;49;00m \u001b[36mhex\u001b[39;49;00m number xx.\u001b[37m\u001b[39;49;00m\n", "# 5 Return the decoded string. Raise \u001b[04m\u001b[91m`\u001b[39;49;00m\u001b[36mValueError\u001b[39;49;00m\u001b[04m\u001b[91m`\u001b[39;49;00m \u001b[34mfor\u001b[39;49;00m invalid inputs.\u001b[33m\"\"\"\u001b[39;49;00m\u001b[33m\u001b[39;49;00m\n", "# 6 \u001b[37m\u001b[39;49;00m\n", "# 7 \u001b[37m# Mapping of hex digits to their integer values\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 8 hex_values = {\u001b[37m\u001b[39;49;00m\n", " 9 \u001b[33m'\u001b[39;49;00m\u001b[33m0\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m0\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m1\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m1\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m2\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m2\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m3\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m3\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m4\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m4\u001b[39;49;00m,\u001b[37m\u001b[39;49;00m\n", " 10 \u001b[33m'\u001b[39;49;00m\u001b[33m5\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m5\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m6\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m6\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m7\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m7\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m8\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m8\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33m9\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m9\u001b[39;49;00m,\u001b[37m\u001b[39;49;00m\n", " 11 \u001b[33m'\u001b[39;49;00m\u001b[33ma\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m10\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mb\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m11\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mc\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m12\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33md\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m13\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33me\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m14\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mf\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m15\u001b[39;49;00m,\u001b[37m\u001b[39;49;00m\n", " 12 \u001b[33m'\u001b[39;49;00m\u001b[33mA\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m10\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mB\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m11\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mC\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m12\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mD\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m13\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mE\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m14\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mF\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m: \u001b[34m15\u001b[39;49;00m,\u001b[37m\u001b[39;49;00m\n", "# 13 }\u001b[37m\u001b[39;49;00m\n", "# 14 \u001b[37m\u001b[39;49;00m\n", " 15 t = \u001b[33m\"\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 16 i = \u001b[34m0\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 17 \u001b[34mwhile\u001b[39;49;00m i < \u001b[36mlen\u001b[39;49;00m(s):\u001b[37m\u001b[39;49;00m\n", " 18 c = s[i]\u001b[37m\u001b[39;49;00m\n", " 19 \u001b[34mif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m+\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n", " 20 t += \u001b[33m'\u001b[39;49;00m\u001b[33m \u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 21 \u001b[34melif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m%\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n", "# 22 digit_high, digit_low = s[i + \u001b[34m1\u001b[39;49;00m], s[i + \u001b[34m2\u001b[39;49;00m]\u001b[37m\u001b[39;49;00m\n", "# 23 i += \u001b[34m2\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", "# 24 \u001b[34mif\u001b[39;49;00m digit_high \u001b[35min\u001b[39;49;00m hex_values \u001b[35mand\u001b[39;49;00m digit_low \u001b[35min\u001b[39;49;00m hex_values:\u001b[37m\u001b[39;49;00m\n", "# 25 v = hex_values[digit_high] * \u001b[34m16\u001b[39;49;00m + hex_values[digit_low]\u001b[37m\u001b[39;49;00m\n", "# 26 t += \u001b[36mchr\u001b[39;49;00m(v)\u001b[37m\u001b[39;49;00m\n", "# 27 \u001b[34melse\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n", "# 28 \u001b[34mraise\u001b[39;49;00m \u001b[36mValueError\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33mInvalid encoding\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\u001b[37m\u001b[39;49;00m\n", "# 29 \u001b[34melse\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n", " 30 t += c\u001b[37m\u001b[39;49;00m\n", " 31 i += \u001b[34m1\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 32 \u001b[34mreturn\u001b[39;49;00m t\u001b[37m\u001b[39;49;00m\n" ] } ], "source": [ "for lineno in range(1, len(cgi_decode_lines)):\n", " if lineno not in covered_lines:\n", " print(\"# \", end=\"\")\n", " else:\n", " print(\" \", end=\"\")\n", " print(\"%2d \" % lineno, end=\"\")\n", " print_content(cgi_decode_lines[lineno], '.py')\n", " print()" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "We see that a number of lines (notably comments) have not been executed (marked with `#`), simply because they are not executable.\n", "However, we also see that the lines under `elif c == '%'` have _not_ been executed yet.\n", "If `\"a+b\"` were our only test case so far, this missing coverage would now encourage us to create another test case that actually covers these `#`-marked lines." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## A Coverage Class\n", "\n", "In this book, we will make use of coverage again and again – to _measure_ the effectiveness of different test generation techniques, but also to _guide_ test generation towards code coverage. Our previous implementation with a global `coverage` variable is a bit cumbersome for that. We therefore implement some functionality that will help us measure coverage easily." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "The key idea of getting coverage is to make use of the Python `with` statement. The general form\n", "\n", "```python\n", "with OBJECT [as VARIABLE]:\n", " BODY\n", "```\n", "\n", "executes `BODY` with `OBJECT` being defined (and stored in `VARIABLE`). The interesting thing is that at the beginning and end of `BODY`, the special methods `OBJECT.__enter__()` and `OBJECT.__exit__()` are automatically invoked; even if `BODY` raises an exception. This allows us to define a `Coverage` object where `Coverage.__enter__()` automatically turns on tracing and `Coverage.__exit__()` automatically turns off tracing again. After tracing, we can make use of special methods to access the coverage. This is what this looks like during usage:\n", "\n", "```python\n", "with Coverage() as cov:\n", " function_to_be_traced()\n", "c = cov.coverage()\n", "```\n", "\n", "Here, tracing is automatically turned on during `function_to_be_traced()` and turned off again after the `with` block; afterwards, we can access the set of lines executed." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Here's the full implementation with all its bells and whistles. You don't have to get everything; it suffices that you know how to use it:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:46.444481Z", "iopub.status.busy": "2025-01-16T09:37:46.444398Z", "iopub.status.idle": "2025-01-16T09:37:46.446154Z", "shell.execute_reply": "2025-01-16T09:37:46.445908Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "Location = Tuple[str, int]" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.447566Z", "iopub.status.busy": "2025-01-16T09:37:46.447485Z", "iopub.status.idle": "2025-01-16T09:37:46.451602Z", "shell.execute_reply": "2025-01-16T09:37:46.451334Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Coverage:\n", " \"\"\"Track coverage within a `with` block. Use as\n", " ```\n", " with Coverage() as cov:\n", " function_to_be_traced()\n", " c = cov.coverage()\n", " ```\n", " \"\"\"\n", "\n", " def __init__(self) -> None:\n", " \"\"\"Constructor\"\"\"\n", " self._trace: List[Location] = []\n", "\n", " # Trace function\n", " def traceit(self, frame: FrameType, event: str, arg: Any) -> Optional[Callable]:\n", " \"\"\"Tracing function. To be overloaded in subclasses.\"\"\"\n", " if self.original_trace_function is not None:\n", " self.original_trace_function(frame, event, arg)\n", "\n", " if event == \"line\":\n", " function_name = frame.f_code.co_name\n", " lineno = frame.f_lineno\n", " if function_name != '__exit__': # avoid tracing ourselves:\n", " self._trace.append((function_name, lineno))\n", "\n", " return self.traceit\n", "\n", " def __enter__(self) -> Any:\n", " \"\"\"Start of `with` block. Turn on tracing.\"\"\"\n", " self.original_trace_function = sys.gettrace()\n", " sys.settrace(self.traceit)\n", " return self\n", "\n", " def __exit__(self, exc_type: Type, exc_value: BaseException,\n", " tb: TracebackType) -> Optional[bool]:\n", " \"\"\"End of `with` block. Turn off tracing.\"\"\"\n", " sys.settrace(self.original_trace_function)\n", " return None # default: pass all exceptions\n", "\n", " def trace(self) -> List[Location]:\n", " \"\"\"The list of executed lines, as (function_name, line_number) pairs\"\"\"\n", " return self._trace\n", "\n", " def coverage(self) -> Set[Location]:\n", " \"\"\"The set of executed lines, as (function_name, line_number) pairs\"\"\"\n", " return set(self.trace())\n", "\n", " def function_names(self) -> Set[str]:\n", " \"\"\"The set of function names seen\"\"\"\n", " return set(function_name for (function_name, line_number) in self.coverage())\n", "\n", " def __repr__(self) -> str:\n", " \"\"\"Return a string representation of this object.\n", " Show covered (and uncovered) program code\"\"\"\n", " t = \"\"\n", " for function_name in self.function_names():\n", " # Similar code as in the example above\n", " try:\n", " fun = eval(function_name)\n", " except Exception as exc:\n", " t += f\"Skipping {function_name}: {exc}\"\n", " continue\n", "\n", " source_lines, start_line_number = inspect.getsourcelines(fun)\n", " for lineno in range(start_line_number, start_line_number + len(source_lines)):\n", " if (function_name, lineno) not in self.trace():\n", " t += \"# \"\n", " else:\n", " t += \" \"\n", " t += \"%2d \" % lineno\n", " t += source_lines[lineno - start_line_number]\n", "\n", " return t" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Let us put this to use:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.453068Z", "iopub.status.busy": "2025-01-16T09:37:46.452987Z", "iopub.status.idle": "2025-01-16T09:37:46.455032Z", "shell.execute_reply": "2025-01-16T09:37:46.454800Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{('cgi_decode', 8), ('cgi_decode', 11), ('cgi_decode', 17), ('cgi_decode', 30), ('cgi_decode', 20), ('cgi_decode', 10), ('cgi_decode', 16), ('cgi_decode', 19), ('cgi_decode', 9), ('cgi_decode', 32), ('cgi_decode', 12), ('cgi_decode', 31), ('cgi_decode', 15), ('cgi_decode', 21), ('cgi_decode', 18)}\n" ] } ], "source": [ "with Coverage() as cov:\n", " cgi_decode(\"a+b\")\n", "\n", "print(cov.coverage())" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "source": [ "As you can see, the `Coverage()` class not only keeps track of lines executed, but also of function names. This is useful if you have a program that spans multiple files." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "For interactive use, we can simply print the coverage object, and obtain a listing of the code, again with non-covered lines marked as `#`." ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:46.456580Z", "iopub.status.busy": "2025-01-16T09:37:46.456494Z", "iopub.status.idle": "2025-01-16T09:37:46.458660Z", "shell.execute_reply": "2025-01-16T09:37:46.458429Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "# 1 def cgi_decode(s: str) -> str:\n", "# 2 \"\"\"Decode the CGI-encoded string `s`:\n", "# 3 * replace '+' by ' '\n", "# 4 * replace \"%xx\" by the character with hex number xx.\n", "# 5 Return the decoded string. Raise `ValueError` for invalid inputs.\"\"\"\n", "# 6 \n", "# 7 # Mapping of hex digits to their integer values\n", " 8 hex_values = {\n", " 9 '0': 0, '1': 1, '2': 2, '3': 3, '4': 4,\n", " 10 '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,\n", " 11 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,\n", " 12 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,\n", "# 13 }\n", "# 14 \n", " 15 t = \"\"\n", " 16 i = 0\n", " 17 while i < len(s):\n", " 18 c = s[i]\n", " 19 if c == '+':\n", " 20 t += ' '\n", " 21 elif c == '%':\n", "# 22 digit_high, digit_low = s[i + 1], s[i + 2]\n", "# 23 i += 2\n", "# 24 if digit_high in hex_values and digit_low in hex_values:\n", "# 25 v = hex_values[digit_high] * 16 + hex_values[digit_low]\n", "# 26 t += chr(v)\n", "# 27 else:\n", "# 28 raise ValueError(\"Invalid encoding\")\n", "# 29 else:\n", " 30 t += c\n", " 31 i += 1\n", " 32 return t\n", "\n" ] } ], "source": [ "print(cov)" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Comparing Coverage\n", "\n", "Since we represent coverage as a set of executed lines, we can also apply _set operations_ on these. For instance, we can find out which lines are covered by individual test cases, but not others:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.460173Z", "iopub.status.busy": "2025-01-16T09:37:46.460097Z", "iopub.status.idle": "2025-01-16T09:37:46.462663Z", "shell.execute_reply": "2025-01-16T09:37:46.462444Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{('cgi_decode', 20)}" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with Coverage() as cov_plus:\n", " cgi_decode(\"a+b\")\n", "with Coverage() as cov_standard:\n", " cgi_decode(\"abc\")\n", "\n", "cov_plus.coverage() - cov_standard.coverage()" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "source": [ "This is the single line in the code that is executed only in the `'a+b'` input." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "We can also compare sets to find out which lines still need to be covered. Let us define `cov_max` as the maximum coverage we can achieve. (Here, we do this by executing the \"good\" test cases we already have. In practice, one would statically analyze code structure, which we introduce in [the chapter on symbolic testing](SymbolicFuzzer.ipynb).)" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.464212Z", "iopub.status.busy": "2025-01-16T09:37:46.464124Z", "iopub.status.idle": "2025-01-16T09:37:46.466124Z", "shell.execute_reply": "2025-01-16T09:37:46.465895Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "with Coverage() as cov_max:\n", " cgi_decode('+')\n", " cgi_decode('%20')\n", " cgi_decode('abc')\n", " try:\n", " cgi_decode('%?a')\n", " except Exception:\n", " pass" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "source": [ "Then, we can easily see which lines are _not_ yet covered by a test case:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.467616Z", "iopub.status.busy": "2025-01-16T09:37:46.467516Z", "iopub.status.idle": "2025-01-16T09:37:46.469495Z", "shell.execute_reply": "2025-01-16T09:37:46.469290Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{('cgi_decode', 22),\n", " ('cgi_decode', 23),\n", " ('cgi_decode', 24),\n", " ('cgi_decode', 25),\n", " ('cgi_decode', 26),\n", " ('cgi_decode', 28)}" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cov_max.coverage() - cov_plus.coverage()" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "source": [ "Again, these would be the lines handling `\"%xx\"`, which we have not yet had in the input." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Coverage of Basic Fuzzing\n", "\n", "We can now use our coverage tracing to assess the _effectiveness_ of testing methods – in particular, of course, test _generation_ methods. Our challenge is to achieve maximum coverage in `cgi_decode()` just with random inputs. In principle, we should _eventually_ get there, as eventually, we will have produced every possible string in the universe – but exactly how long is this? To this end, let us run just one fuzzing iteration on `cgi_decode()`:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.470998Z", "iopub.status.busy": "2025-01-16T09:37:46.470902Z", "iopub.status.idle": "2025-01-16T09:37:46.543033Z", "shell.execute_reply": "2025-01-16T09:37:46.542773Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from Fuzzer import fuzzer" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.544913Z", "iopub.status.busy": "2025-01-16T09:37:46.544815Z", "iopub.status.idle": "2025-01-16T09:37:46.547054Z", "shell.execute_reply": "2025-01-16T09:37:46.546809Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'!7#%\"*#0=)$;%6*;>638:*>80\"=(/*:-(2<4 !:5*6856&?\"\"11<7+%<%7,4.8,*+&,,$,.\"'" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sample = fuzzer()\n", "sample" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Here's the invocation and the coverage we achieve. We wrap `cgi_decode()` in a `try...except` block such that we can ignore `ValueError` exceptions raised by illegal `%xx` formats." ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.548482Z", "iopub.status.busy": "2025-01-16T09:37:46.548387Z", "iopub.status.idle": "2025-01-16T09:37:46.550928Z", "shell.execute_reply": "2025-01-16T09:37:46.550681Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 12),\n", " ('cgi_decode', 15),\n", " ('cgi_decode', 16),\n", " ('cgi_decode', 17),\n", " ('cgi_decode', 18),\n", " ('cgi_decode', 19),\n", " ('cgi_decode', 21),\n", " ('cgi_decode', 22),\n", " ('cgi_decode', 23),\n", " ('cgi_decode', 24),\n", " ('cgi_decode', 28),\n", " ('cgi_decode', 30),\n", " ('cgi_decode', 31)}" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with Coverage() as cov_fuzz:\n", " try:\n", " cgi_decode(sample)\n", " except:\n", " pass\n", "cov_fuzz.coverage()" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Is this already the maximum coverage? Apparently, there are still lines missing:" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.552417Z", "iopub.status.busy": "2025-01-16T09:37:46.552316Z", "iopub.status.idle": "2025-01-16T09:37:46.554385Z", "shell.execute_reply": "2025-01-16T09:37:46.554158Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{('cgi_decode', 20),\n", " ('cgi_decode', 25),\n", " ('cgi_decode', 26),\n", " ('cgi_decode', 32)}" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cov_max.coverage() - cov_fuzz.coverage()" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Let us try again, increasing coverage over 100 random inputs. We use an array `cumulative_coverage` to store the coverage achieved over time; `cumulative_coverage[0]` is the total number of lines covered after input 1, \n", "`cumulative_coverage[1]` is the number of lines covered after inputs 1–2, and so on." ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.555892Z", "iopub.status.busy": "2025-01-16T09:37:46.555796Z", "iopub.status.idle": "2025-01-16T09:37:46.557351Z", "shell.execute_reply": "2025-01-16T09:37:46.557094Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "trials = 100" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.558742Z", "iopub.status.busy": "2025-01-16T09:37:46.558661Z", "iopub.status.idle": "2025-01-16T09:37:46.560853Z", "shell.execute_reply": "2025-01-16T09:37:46.560635Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def population_coverage(population: List[str], function: Callable) \\\n", " -> Tuple[Set[Location], List[int]]:\n", " cumulative_coverage: List[int] = []\n", " all_coverage: Set[Location] = set()\n", "\n", " for s in population:\n", " with Coverage() as cov:\n", " try:\n", " function(s)\n", " except:\n", " pass\n", " all_coverage |= cov.coverage()\n", " cumulative_coverage.append(len(all_coverage))\n", "\n", " return all_coverage, cumulative_coverage" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Let us create a hundred inputs to determine how coverage increases:" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.562274Z", "iopub.status.busy": "2025-01-16T09:37:46.562194Z", "iopub.status.idle": "2025-01-16T09:37:46.563870Z", "shell.execute_reply": "2025-01-16T09:37:46.563668Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def hundred_inputs() -> List[str]:\n", " population = []\n", " for i in range(trials):\n", " population.append(fuzzer())\n", " return population" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Here's how the coverage increases with each input:" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.565169Z", "iopub.status.busy": "2025-01-16T09:37:46.565090Z", "iopub.status.idle": "2025-01-16T09:37:46.589092Z", "shell.execute_reply": "2025-01-16T09:37:46.588855Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "all_coverage, cumulative_coverage = \\\n", " population_coverage(hundred_inputs(), cgi_decode)" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.590772Z", "iopub.status.busy": "2025-01-16T09:37:46.590675Z", "iopub.status.idle": "2025-01-16T09:37:46.851408Z", "shell.execute_reply": "2025-01-16T09:37:46.851153Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.853195Z", "iopub.status.busy": "2025-01-16T09:37:46.853070Z", "iopub.status.idle": "2025-01-16T09:37:46.854710Z", "shell.execute_reply": "2025-01-16T09:37:46.854500Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import matplotlib.pyplot as plt # type: ignore" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.856128Z", "iopub.status.busy": "2025-01-16T09:37:46.856031Z", "iopub.status.idle": "2025-01-16T09:37:46.938339Z", "shell.execute_reply": "2025-01-16T09:37:46.938089Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'lines covered')" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkAAAAHHCAYAAABXx+fLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABYCElEQVR4nO3deVhUZfsH8O9hG3YQRZBYXAsVRUNDcE9S0dckTU19wzVbMFNSCyu3TFwySzO3t8Qylyy3zFRyQVFBQSk1xSV3BVcYQAVknt8f/ubkyAwyOgsO3891zXUxZ55zzn0Oy3PzbEcSQggQERERVSJW5g6AiIiIyNSYABEREVGlwwSIiIiIKh0mQERERFTpMAEiIiKiSocJEBEREVU6TICIiIio0mECRERERJUOEyAiIiKqdJgAEVUAmzdvRpMmTWBvbw9JkpCTk2OS87Zr1w7t2rUzyLFq1qyJgQMHGuRYxvCk8V24cAH29vbYs2ePvO21115D7969DRCdbjt37oQkSdi5c2e5y/78889GjcnYJEnCxIkTzR2GzJC/J1RxMAEiDadPn8abb76J2rVrw97eHq6urmjZsiW++uor3Llzx9zhWaQbN26gd+/ecHBwwLx58/DDDz/AycnJ3GHRQyZPnozQ0FC0bNlS3vbBBx/gl19+wZ9//mnSWJYvX44vv/zSpOekioPff8OwMXcAVHH89ttv6NWrFxQKBaKjoxEUFISioiIkJydjzJgxOHr0KBYtWmTuMC3OgQMHkJeXh08//RQREREmPffWrVtNer6n1bVr17B06VIsXbpUY3vTpk3RrFkzzJo1C99//71Rzt2mTRvcuXMHdnZ28rbly5fjyJEjGDlypFHOSZoq2u8Jv/+GwQSIAABnzpzBa6+9hoCAAGzfvh01atSQP4uJicGpU6fw22+/mTFC3W7fvg1HR0dzh/HYrl69CgBwd3c3+bkfrFRJt2XLlsHGxgbdunUr9Vnv3r0xYcIEfPPNN3B2djb4ua2srGBvb2/w45ZXQUFBpW+R5O+JZWIXGAEAZsyYgfz8fHz77bcayY9a3bp18d5778nv7927h08//RR16tSBQqFAzZo1MW7cOBQWFspl/vOf/6B27dpazxcWFoZmzZppbFu2bBlCQkLg4OAADw8PvPbaa7hw4YJGmXbt2iEoKAjp6elo06YNHB0dMW7cOADA+vXr0bVrV/j4+EChUKBOnTr49NNPUVJSUur88+bNQ+3ateHg4IAXXngBu3fv1trPX1hYiAkTJqBu3bpQKBTw8/PD2LFjNa6zLKtXr5avqVq1avjvf/+LS5cuaVzPgAEDAADNmzeHJEmPHKdy6dIlDBkyRL7OWrVq4e2330ZRUZFc5q+//kLbtm3h4OAAX19fTJkyBUuWLIEkSTh79qzG+fUd2yCEwJQpU+Dr6wtHR0e0b98eR48e1Vo2JycHI0eOhJ+fHxQKBerWrYvp06dDpVJplFOpVPjqq6/QqFEj2Nvbw9PTE507d0ZaWppcpjw/c8aKb926dQgNDdWa4Lz00ksoKChAYmJimfetR48eeP755zW2devWDZIkYcOGDfK21NRUSJKE33//HUDpMUDt2rXDb7/9hnPnzkGSJEiShJo1a5a6n5999hl8fX1hb2+PDh064NSpU2XGBwATJ06EJEn4+++/0a9fP1SpUgWtWrUCcP9nauDAgXL3uLe3NwYPHowbN25oPcapU6cwcOBAuLu7w83NDYMGDcLt27c1yhYWFmLUqFHw9PSEi4sLXn75ZVy8eFFrbIcOHUJkZCRcXV3h7OyMDh06ICUlRaNMQkICJElCcnIyRowYAU9PT7i7u+PNN99EUVERcnJyEB0djSpVqqBKlSoYO3YshBCPvC8P/56ovyc//fTTI+/zg3+zwsPD4eDggFq1amHBggVaY3/w9/PBc5X3+z937lw0bNgQjo6OqFKlCpo1a4bly5c/8horI7YAEQDg119/Re3atREeHl6u8kOHDsXSpUvx6quv4v3330dqairi4+Nx7NgxrF27FgDQp08fREdH48CBA2jevLm877lz55CSkoKZM2fK2z777DN88skn6N27N4YOHYpr165h7ty5aNOmDQ4dOqTROnLjxg1ERkbitddew3//+194eXkBuP8HxNnZGbGxsXB2dsb27dsxfvx4KJVKjXPNnz8fw4cPR+vWrTFq1CicPXsWUVFRqFKlCnx9feVyKpUKL7/8MpKTkzFs2DDUr18fhw8fxuzZs3HixAmsW7euzHuUkJCAQYMGoXnz5oiPj0d2dja++uor7NmzR76mjz76CM899xwWLVqEyZMno1atWqhTp47OY16+fBkvvPACcnJyMGzYMAQGBuLSpUv4+eefcfv2bdjZ2eHSpUto3749JElC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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.plot(cumulative_coverage)\n", "plt.title('Coverage of cgi_decode() with random inputs')\n", "plt.xlabel('# of inputs')\n", "plt.ylabel('lines covered')" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "This is just _one_ run, of course; so let's repeat this a number of times and plot the averages." ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:46.940135Z", "iopub.status.busy": "2025-01-16T09:37:46.940024Z", "iopub.status.idle": "2025-01-16T09:37:48.931385Z", "shell.execute_reply": "2025-01-16T09:37:48.931118Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "runs = 100\n", "\n", "# Create an array with TRIALS elements, all zero\n", "sum_coverage = [0] * trials\n", "\n", "for run in range(runs):\n", " all_coverage, coverage = population_coverage(hundred_inputs(), cgi_decode)\n", " assert len(coverage) == trials\n", " for i in range(trials):\n", " sum_coverage[i] += coverage[i]\n", "\n", "average_coverage = []\n", "for i in range(trials):\n", " average_coverage.append(sum_coverage[i] / runs)" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:48.933306Z", "iopub.status.busy": "2025-01-16T09:37:48.933187Z", "iopub.status.idle": "2025-01-16T09:37:48.993668Z", "shell.execute_reply": "2025-01-16T09:37:48.993419Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'lines covered')" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.plot(average_coverage)\n", "plt.title('Average coverage of cgi_decode() with random inputs')\n", "plt.xlabel('# of inputs')\n", "plt.ylabel('lines covered')" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "source": [ "We see that on average, we get full coverage after 40–60 fuzzing inputs." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Getting Coverage from External Programs\n", "\n", "Of course, not all the world is programming in Python. The good news is that the problem of obtaining coverage is ubiquitous, and almost every programming language has some facility to measure coverage. Just as an example, let us therefore demonstrate how to obtain coverage for a C program." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Our C program (again) implements `cgi_decode`; this time as a program to be executed from the command line:\n", "\n", "```shell\n", "$ ./cgi_decode 'Hello+World'\n", "Hello World\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Here comes the C code, first as a Python string. We start with the usual C includes:" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:48.995555Z", "iopub.status.busy": "2025-01-16T09:37:48.995439Z", "iopub.status.idle": "2025-01-16T09:37:48.997243Z", "shell.execute_reply": "2025-01-16T09:37:48.996928Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "cgi_c_code = \"\"\"\n", "/* CGI decoding as C program */\n", "\n", "#include \n", "#include \n", "#include \n", "\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Here comes the initialization of `hex_values`:" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:48.998821Z", "iopub.status.busy": "2025-01-16T09:37:48.998721Z", "iopub.status.idle": "2025-01-16T09:37:49.000381Z", "shell.execute_reply": "2025-01-16T09:37:49.000151Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "cgi_c_code += r\"\"\"\n", "int hex_values[256];\n", "\n", "void init_hex_values() {\n", " for (int i = 0; i < sizeof(hex_values) / sizeof(int); i++) {\n", " hex_values[i] = -1;\n", " }\n", " hex_values['0'] = 0; hex_values['1'] = 1; hex_values['2'] = 2; hex_values['3'] = 3;\n", " hex_values['4'] = 4; hex_values['5'] = 5; hex_values['6'] = 6; hex_values['7'] = 7;\n", " hex_values['8'] = 8; hex_values['9'] = 9;\n", "\n", " hex_values['a'] = 10; hex_values['b'] = 11; hex_values['c'] = 12; hex_values['d'] = 13;\n", " hex_values['e'] = 14; hex_values['f'] = 15;\n", "\n", " hex_values['A'] = 10; hex_values['B'] = 11; hex_values['C'] = 12; hex_values['D'] = 13;\n", " hex_values['E'] = 14; hex_values['F'] = 15;\n", "}\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Here's the actual implementation of `cgi_decode()`, using pointers for input source (`s`) and output target (`t`):" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:49.001917Z", "iopub.status.busy": "2025-01-16T09:37:49.001823Z", "iopub.status.idle": "2025-01-16T09:37:49.003409Z", "shell.execute_reply": "2025-01-16T09:37:49.003184Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "cgi_c_code += r\"\"\"\n", "int cgi_decode(char *s, char *t) {\n", " while (*s != '\\0') {\n", " if (*s == '+')\n", " *t++ = ' ';\n", " else if (*s == '%') {\n", " int digit_high = *++s;\n", " int digit_low = *++s;\n", " if (hex_values[digit_high] >= 0 && hex_values[digit_low] >= 0) {\n", " *t++ = hex_values[digit_high] * 16 + hex_values[digit_low];\n", " }\n", " else\n", " return -1;\n", " }\n", " else\n", " *t++ = *s;\n", " s++;\n", " }\n", " *t = '\\0';\n", " return 0;\n", "}\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Finally, here's a driver which takes the first argument and invokes `cgi_decode` with it:" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:49.004894Z", "iopub.status.busy": "2025-01-16T09:37:49.004808Z", "iopub.status.idle": "2025-01-16T09:37:49.006494Z", "shell.execute_reply": "2025-01-16T09:37:49.006259Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "cgi_c_code += r\"\"\"\n", "int main(int argc, char *argv[]) {\n", " init_hex_values();\n", "\n", " if (argc >= 2) {\n", " char *s = argv[1];\n", " char *t = malloc(strlen(s) + 1); /* output is at most as long as input */\n", " int ret = cgi_decode(s, t);\n", " printf(\"%s\\n\", t);\n", " return ret;\n", " }\n", " else\n", " {\n", " printf(\"cgi_decode: usage: cgi_decode STRING\\n\");\n", " return 1;\n", " }\n", "}\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Let us create the C source code: (Note that the following commands will overwrite the file `cgi_decode.c`, if it already exists in the current working directory. Be aware of this, if you downloaded the notebooks and are working locally.)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:49.008219Z", "iopub.status.busy": "2025-01-16T09:37:49.008119Z", "iopub.status.idle": "2025-01-16T09:37:49.010050Z", "shell.execute_reply": "2025-01-16T09:37:49.009812Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "with open(\"cgi_decode.c\", \"w\") as f:\n", " f.write(cgi_c_code)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "And here we have the C code with its syntax highlighted:" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:49.011611Z", "iopub.status.busy": "2025-01-16T09:37:49.011521Z", "iopub.status.idle": "2025-01-16T09:37:49.013088Z", "shell.execute_reply": "2025-01-16T09:37:49.012848Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from bookutils import print_file" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:49.014411Z", "iopub.status.busy": "2025-01-16T09:37:49.014325Z", "iopub.status.idle": "2025-01-16T09:37:49.034136Z", "shell.execute_reply": "2025-01-16T09:37:49.033828Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[37m/* CGI decoding as C program */\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", "\u001b[37m\u001b[39;49;00m\n", "\u001b[36m#\u001b[39;49;00m\u001b[36minclude\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[37m\u001b[39;49;00m\u001b[36m\u001b[39;49;00m\n", "\u001b[36m#\u001b[39;49;00m\u001b[36minclude\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[37m\u001b[39;49;00m\u001b[36m\u001b[39;49;00m\n", "\u001b[36m#\u001b[39;49;00m\u001b[36minclude\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[37m\u001b[39;49;00m\u001b[36m\u001b[39;49;00m\n", "\u001b[37m\u001b[39;49;00m\n", "\u001b[37m\u001b[39;49;00m\n", "\u001b[36mint\u001b[39;49;00m\u001b[37m \u001b[39;49;00mhex_values[\u001b[34m256\u001b[39;49;00m];\u001b[37m\u001b[39;49;00m\n", "\u001b[37m\u001b[39;49;00m\n", "\u001b[36mvoid\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[32minit_hex_values\u001b[39;49;00m()\u001b[37m \u001b[39;49;00m{\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34mfor\u001b[39;49;00m\u001b[37m \u001b[39;49;00m(\u001b[36mint\u001b[39;49;00m\u001b[37m \u001b[39;49;00mi\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m0\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mi\u001b[37m \u001b[39;49;00m<\u001b[37m \u001b[39;49;00m\u001b[34msizeof\u001b[39;49;00m(hex_values)\u001b[37m \u001b[39;49;00m/\u001b[37m \u001b[39;49;00m\u001b[34msizeof\u001b[39;49;00m(\u001b[36mint\u001b[39;49;00m);\u001b[37m \u001b[39;49;00mi++)\u001b[37m \u001b[39;49;00m{\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00mhex_values[i]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m-1\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m}\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33m0\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m0\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33m1\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m1\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33m2\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m2\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33m3\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m3\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33m4\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m4\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33m5\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m5\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33m6\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m6\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33m7\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m7\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33m8\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m8\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33m9\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m9\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33ma\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m10\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33mb\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m11\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33mc\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m12\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33md\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m13\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33me\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m14\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33mf\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m15\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33mA\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m10\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33mB\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m11\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33mC\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m12\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33mD\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m13\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33mE\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m14\u001b[39;49;00m;\u001b[37m \u001b[39;49;00mhex_values[\u001b[33m'\u001b[39;49;00m\u001b[33mF\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m]\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[34m15\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "}\u001b[37m\u001b[39;49;00m\n", "\u001b[37m\u001b[39;49;00m\n", "\u001b[36mint\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[32mcgi_decode\u001b[39;49;00m(\u001b[36mchar\u001b[39;49;00m\u001b[37m \u001b[39;49;00m*s,\u001b[37m \u001b[39;49;00m\u001b[36mchar\u001b[39;49;00m\u001b[37m \u001b[39;49;00m*t)\u001b[37m \u001b[39;49;00m{\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34mwhile\u001b[39;49;00m\u001b[37m \u001b[39;49;00m(*s\u001b[37m \u001b[39;49;00m!=\u001b[37m \u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33m\\0\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m)\u001b[37m \u001b[39;49;00m{\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34mif\u001b[39;49;00m\u001b[37m \u001b[39;49;00m(*s\u001b[37m \u001b[39;49;00m==\u001b[37m \u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33m+\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m)\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m*t++\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33m \u001b[39;49;00m\u001b[33m'\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34melse\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[34mif\u001b[39;49;00m\u001b[37m \u001b[39;49;00m(*s\u001b[37m \u001b[39;49;00m==\u001b[37m \u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33m%\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m)\u001b[37m \u001b[39;49;00m{\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[36mint\u001b[39;49;00m\u001b[37m \u001b[39;49;00mdigit_high\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m*++s;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[36mint\u001b[39;49;00m\u001b[37m \u001b[39;49;00mdigit_low\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m*++s;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34mif\u001b[39;49;00m\u001b[37m \u001b[39;49;00m(hex_values[digit_high]\u001b[37m \u001b[39;49;00m>=\u001b[37m \u001b[39;49;00m\u001b[34m0\u001b[39;49;00m\u001b[37m \u001b[39;49;00m&&\u001b[37m \u001b[39;49;00mhex_values[digit_low]\u001b[37m \u001b[39;49;00m>=\u001b[37m \u001b[39;49;00m\u001b[34m0\u001b[39;49;00m)\u001b[37m \u001b[39;49;00m{\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m*t++\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00mhex_values[digit_high]\u001b[37m \u001b[39;49;00m*\u001b[37m \u001b[39;49;00m\u001b[34m16\u001b[39;49;00m\u001b[37m \u001b[39;49;00m+\u001b[37m \u001b[39;49;00mhex_values[digit_low];\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m}\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34melse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34mreturn\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[34m-1\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m}\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34melse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m*t++\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m*s;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00ms++;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m}\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m*t\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33m\\0\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34mreturn\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[34m0\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "}\u001b[37m\u001b[39;49;00m\n", "\u001b[37m\u001b[39;49;00m\n", "\u001b[36mint\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[32mmain\u001b[39;49;00m(\u001b[36mint\u001b[39;49;00m\u001b[37m \u001b[39;49;00margc,\u001b[37m \u001b[39;49;00m\u001b[36mchar\u001b[39;49;00m\u001b[37m \u001b[39;49;00m*argv[])\u001b[37m \u001b[39;49;00m{\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00minit_hex_values();\u001b[37m\u001b[39;49;00m\n", "\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34mif\u001b[39;49;00m\u001b[37m \u001b[39;49;00m(argc\u001b[37m \u001b[39;49;00m>=\u001b[37m \u001b[39;49;00m\u001b[34m2\u001b[39;49;00m)\u001b[37m \u001b[39;49;00m{\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[36mchar\u001b[39;49;00m\u001b[37m \u001b[39;49;00m*s\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00margv[\u001b[34m1\u001b[39;49;00m];\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[36mchar\u001b[39;49;00m\u001b[37m \u001b[39;49;00m*t\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00mmalloc(strlen(s)\u001b[37m \u001b[39;49;00m+\u001b[37m \u001b[39;49;00m\u001b[34m1\u001b[39;49;00m);\u001b[37m \u001b[39;49;00m\u001b[37m/* output is at most as long as input */\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[36mint\u001b[39;49;00m\u001b[37m \u001b[39;49;00mret\u001b[37m \u001b[39;49;00m=\u001b[37m \u001b[39;49;00mcgi_decode(s,\u001b[37m \u001b[39;49;00mt);\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00mprintf(\u001b[33m\"\u001b[39;49;00m\u001b[33m%s\u001b[39;49;00m\u001b[33m\\n\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m,\u001b[37m \u001b[39;49;00mt);\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34mreturn\u001b[39;49;00m\u001b[37m \u001b[39;49;00mret;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m}\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34melse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m{\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00mprintf(\u001b[33m\"\u001b[39;49;00m\u001b[33mcgi_decode: usage: cgi_decode STRING\u001b[39;49;00m\u001b[33m\\n\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m);\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m\u001b[34mreturn\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[34m1\u001b[39;49;00m;\u001b[37m\u001b[39;49;00m\n", "\u001b[37m \u001b[39;49;00m}\u001b[37m\u001b[39;49;00m\n", "}\u001b[37m\u001b[39;49;00m" ] } ], "source": [ "print_file(\"cgi_decode.c\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "We can now compile the C code into an executable. The `--coverage` option instructs the C compiler to instrument the code such that at runtime, coverage information will be collected. (The exact options vary from compiler to compiler.)" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:49.035855Z", "iopub.status.busy": "2025-01-16T09:37:49.035723Z", "iopub.status.idle": "2025-01-16T09:37:49.251994Z", "shell.execute_reply": "2025-01-16T09:37:49.251601Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "!cc --coverage -o cgi_decode cgi_decode.c" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "When we now execute the program, coverage information will automatically be collected and stored in auxiliary files:" ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:49.253839Z", "iopub.status.busy": "2025-01-16T09:37:49.253724Z", "iopub.status.idle": "2025-01-16T09:37:49.512943Z", "shell.execute_reply": "2025-01-16T09:37:49.512592Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Send mail to me@fuzzingbook.org\r\n" ] } ], "source": [ "!./cgi_decode 'Send+mail+to+me%40fuzzingbook.org'" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The coverage information is collected by the `gcov` program. For every source file given, it produces a new `.gcov` file with coverage information. This is either stored in `cgi_decode...` or in `cgi_decode-cgi_decode...` files." ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:49.515057Z", "iopub.status.busy": "2025-01-16T09:37:49.514913Z", "iopub.status.idle": "2025-01-16T09:37:50.239452Z", "shell.execute_reply": "2025-01-16T09:37:50.239079Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "cgi_decode.gcno: No such file or directory\r\n", "File 'cgi_decode.c'\r\n", "Lines executed:92.50% of 40\r\n", "Creating 'cgi_decode.c.gcov'\r\n", "\r\n" ] } ], "source": [ "!gcov cgi_decode cgi_decode-cgi_decode" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "In the `.gcov` file, each line is prefixed with the number of times it was called (`-` stands for a non-executable line, `#####` stands for zero) as well as the line number. We can take a look at `cgi_decode()`, for instance, and see that the only code not executed yet is the `return -1` for an illegal input." ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:50.241683Z", "iopub.status.busy": "2025-01-16T09:37:50.241515Z", "iopub.status.idle": "2025-01-16T09:37:50.244820Z", "shell.execute_reply": "2025-01-16T09:37:50.244568Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 1: 26:int cgi_decode(char *s, char *t) {\n", " 32: 27: while (*s != '\\0') {\n", " 31: 28: if (*s == '+')\n", " 3: 29: *t++ = ' ';\n", " 28: 30: else if (*s == '%') {\n", " 1: 31: int digit_high = *++s;\n", " 1: 32: int digit_low = *++s;\n", " 1: 33: if (hex_values[digit_high] >= 0 && hex_values[digit_low] >= 0) {\n", " 1: 34: *t++ = hex_values[digit_high] * 16 + hex_values[digit_low];\n", " 1: 35: }\n", " -: 36: else\n", " #####: 37: return -1;\n", " 1: 38: }\n", " -: 39: else\n", " 27: 40: *t++ = *s;\n", " 31: 41: s++;\n", " -: 42: }\n", " 1: 43: *t = '\\0';\n", " 1: 44: return 0;\n", " 1: 45:}\n" ] } ], "source": [ "lines = open('cgi_decode.c.gcov').readlines()\n", "for i in range(30, 50):\n", " print(lines[i], end='')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Let us read in this file to obtain a coverage set:" ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:50.246891Z", "iopub.status.busy": "2025-01-16T09:37:50.246777Z", "iopub.status.idle": "2025-01-16T09:37:50.249137Z", "shell.execute_reply": "2025-01-16T09:37:50.248898Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def read_gcov_coverage(c_file):\n", " gcov_file = c_file + \".gcov\"\n", " coverage = set()\n", " with open(gcov_file) as file:\n", " for line in file.readlines():\n", " elems = line.split(':')\n", " covered = elems[0].strip()\n", " line_number = int(elems[1].strip())\n", " if covered.startswith('-') or covered.startswith('#'):\n", " continue\n", " coverage.add((c_file, line_number))\n", " return coverage" ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:50.250807Z", "iopub.status.busy": "2025-01-16T09:37:50.250671Z", "iopub.status.idle": "2025-01-16T09:37:50.252966Z", "shell.execute_reply": "2025-01-16T09:37:50.252664Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "coverage = read_gcov_coverage('cgi_decode.c')" ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:50.254814Z", "iopub.status.busy": "2025-01-16T09:37:50.254661Z", "iopub.status.idle": "2025-01-16T09:37:50.257071Z", "shell.execute_reply": "2025-01-16T09:37:50.256795Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[('cgi_decode.c', 53),\n", " ('cgi_decode.c', 62),\n", " ('cgi_decode.c', 16),\n", " ('cgi_decode.c', 13),\n", " ('cgi_decode.c', 19)]" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(coverage)[:5]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "With this set, we can now do the same coverage computations as with our Python programs." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Finding Errors with Basic Fuzzing\n", "\n", "Given sufficient time, we can indeed cover each and every line within `cgi_decode()`, whatever the programming language would be. This does not mean that they would be error-free, though. Since we do not check the result of `cgi_decode()`, the function could return any value without us checking or noticing. To catch such errors, we would have to set up a *results checker* (commonly called an *oracle*) that would verify test results. In our case, we could compare the C and Python implementations of `cgi_decode()` and see whether both produce the same results." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Where fuzzing is great at, though, is in finding _internal errors_ that can be detected even without checking the result. Actually, if one runs our `fuzzer()` on `cgi_decode()`, one quickly finds such an error, as the following code shows:" ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:50.259079Z", "iopub.status.busy": "2025-01-16T09:37:50.258939Z", "iopub.status.idle": "2025-01-16T09:37:50.260686Z", "shell.execute_reply": "2025-01-16T09:37:50.260340Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "from ExpectError import ExpectError" ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:50.262157Z", "iopub.status.busy": "2025-01-16T09:37:50.262050Z", "iopub.status.idle": "2025-01-16T09:37:50.264298Z", "shell.execute_reply": "2025-01-16T09:37:50.264070Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Traceback (most recent call last):\n", " File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_2687/2238772797.py\", line 5, in \n", " cgi_decode(s)\n", " File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_2687/1071239422.py\", line 22, in cgi_decode\n", " digit_high, digit_low = s[i + 1], s[i + 2]\n", " ~^^^^^^^\n", "IndexError: string index out of range (expected)\n" ] } ], "source": [ "with ExpectError():\n", " for i in range(trials):\n", " try:\n", " s = fuzzer()\n", " cgi_decode(s)\n", " except ValueError:\n", " pass" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "source": [ "So, it is possible to cause `cgi_decode()` to crash. Why is that? Let's take a look at its input:" ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:50.265804Z", "iopub.status.busy": "2025-01-16T09:37:50.265682Z", "iopub.status.idle": "2025-01-16T09:37:50.268858Z", "shell.execute_reply": "2025-01-16T09:37:50.267386Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'82 202*&<1&($34\\'\"/\\'.<5/!8\"\\'5:!4))%;'" ] }, "execution_count": 60, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "source": [ "The problem here is at the end of the string. After a `'%'` character, our implementation will always attempt to access two more (hexadecimal) characters, but if these are not there, we will get an `IndexError` exception. " ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "This problem is also present in our C variant, which inherits it from the original implementation \\cite{Pezze2008}:\n", "\n", "```c\n", "int digit_high = *++s;\n", "int digit_low = *++s;\n", "```\n", "\n", "Here, `s` is a pointer to the character to be read; `++` increments it by one character.\n", "In the C implementation, the problem is actually much worse. If the `'%'` character is at the end of the string, the above code will first read a terminating character (`'\\0'` in C strings) and then the following character, which may be any memory content after the string, and which thus may cause the program to fail uncontrollably. The somewhat good news is that `'\\0'` is not a valid hexadecimal character, and thus, the C version will \"only\" read one character beyond the end of the string." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "source": [ "Interestingly enough, none of the manual tests we had designed earlier would trigger this bug. Actually, neither statement nor branch coverage, nor any of the coverage criteria commonly discussed in literature would find it. However, a simple fuzzing run can identify the error with a few runs – _if_ appropriate run-time checks are in place that find such overflows. This definitely calls for more fuzzing!" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Synopsis\n", "\n", "This chapter introduces a `Coverage` class allowing you to measure coverage for Python programs. Within the context of this book, we use coverage information to guide fuzzing towards uncovered locations." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The typical usage of the `Coverage` class is in conjunction with a `with` clause:" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:50.274463Z", "iopub.status.busy": "2025-01-16T09:37:50.274337Z", "iopub.status.idle": "2025-01-16T09:37:50.276565Z", "shell.execute_reply": "2025-01-16T09:37:50.276297Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "with Coverage() as cov:\n", " cgi_decode(\"a+b\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Printing out a coverage object shows the covered functions, with non-covered lines prefixed with `#`:" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:50.278248Z", "iopub.status.busy": "2025-01-16T09:37:50.278141Z", "iopub.status.idle": "2025-01-16T09:37:50.280166Z", "shell.execute_reply": "2025-01-16T09:37:50.279912Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "# 1 def cgi_decode(s: str) -> str:\n", "# 2 \"\"\"Decode the CGI-encoded string `s`:\n", "# 3 * replace '+' by ' '\n", "# 4 * replace \"%xx\" by the character with hex number xx.\n", "# 5 Return the decoded string. Raise `ValueError` for invalid inputs.\"\"\"\n", "# 6 \n", "# 7 # Mapping of hex digits to their integer values\n", " 8 hex_values = {\n", " 9 '0': 0, '1': 1, '2': 2, '3': 3, '4': 4,\n", " 10 '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,\n", " 11 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,\n", " 12 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,\n", "# 13 }\n", "# 14 \n", " 15 t = \"\"\n", " 16 i = 0\n", " 17 while i < len(s):\n", " 18 c = s[i]\n", " 19 if c == '+':\n", " 20 t += ' '\n", " 21 elif c == '%':\n", "# 22 digit_high, digit_low = s[i + 1], s[i + 2]\n", "# 23 i += 2\n", "# 24 if digit_high in hex_values and digit_low in hex_values:\n", "# 25 v = hex_values[digit_high] * 16 + hex_values[digit_low]\n", "# 26 t += chr(v)\n", "# 27 else:\n", "# 28 raise ValueError(\"Invalid encoding\")\n", "# 29 else:\n", " 30 t += c\n", " 31 i += 1\n", " 32 return t\n", "\n" ] } ], "source": [ "print(cov)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The `trace()` method returns the _trace_ – that is, the list of locations executed in order. Each location comes as a pair (`function name`, `line`)." ] }, { "cell_type": "code", "execution_count": 63, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:50.281832Z", "iopub.status.busy": "2025-01-16T09:37:50.281665Z", "iopub.status.idle": "2025-01-16T09:37:50.284766Z", "shell.execute_reply": "2025-01-16T09:37:50.284484Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "[('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 12),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 12),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 15),\n", " ('cgi_decode', 16),\n", " ('cgi_decode', 17),\n", " ('cgi_decode', 18),\n", " ('cgi_decode', 19),\n", " ('cgi_decode', 21),\n", " ('cgi_decode', 30),\n", " ('cgi_decode', 31),\n", " ('cgi_decode', 17),\n", " ('cgi_decode', 18),\n", " ('cgi_decode', 19),\n", " ('cgi_decode', 20),\n", " ('cgi_decode', 31),\n", " ('cgi_decode', 17),\n", " ('cgi_decode', 18),\n", " ('cgi_decode', 19),\n", " ('cgi_decode', 21),\n", " ('cgi_decode', 30),\n", " ('cgi_decode', 31),\n", " ('cgi_decode', 17),\n", " ('cgi_decode', 32)]" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cov.trace()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The `coverage()` method returns the _coverage_, that is, the set of locations in the trace executed at least once:" ] }, { "cell_type": "code", "execution_count": 64, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:50.286328Z", "iopub.status.busy": "2025-01-16T09:37:50.286226Z", "iopub.status.idle": "2025-01-16T09:37:50.288452Z", "shell.execute_reply": "2025-01-16T09:37:50.288219Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "{('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 10),\n", " ('cgi_decode', 11),\n", " ('cgi_decode', 12),\n", " ('cgi_decode', 15),\n", " ('cgi_decode', 16),\n", " ('cgi_decode', 17),\n", " ('cgi_decode', 18),\n", " ('cgi_decode', 19),\n", " ('cgi_decode', 20),\n", " ('cgi_decode', 21),\n", " ('cgi_decode', 30),\n", " ('cgi_decode', 31),\n", " ('cgi_decode', 32)}" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cov.coverage()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Coverage sets can be subject to set operations, such as _intersection_ (which locations are covered in multiple executions) and _difference_ (which locations are covered in run _a_, but not _b_)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The chapter also discusses how to obtain such coverage from C programs." ] }, { "cell_type": "code", "execution_count": 65, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:50.290201Z", "iopub.status.busy": "2025-01-16T09:37:50.290081Z", "iopub.status.idle": "2025-01-16T09:37:50.291817Z", "shell.execute_reply": "2025-01-16T09:37:50.291538Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# ignore\n", "from ClassDiagram import display_class_hierarchy" ] }, { "cell_type": "code", "execution_count": 66, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:50.293313Z", "iopub.status.busy": "2025-01-16T09:37:50.293210Z", "iopub.status.idle": "2025-01-16T09:37:51.267110Z", "shell.execute_reply": "2025-01-16T09:37:51.266673Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Coverage\n", "\n", "\n", "Coverage\n", "\n", "\n", "\n", "__enter__()\n", "\n", "\n", "\n", "__exit__()\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "__repr__()\n", "\n", "\n", "\n", "coverage()\n", "\n", "\n", "\n", "function_names()\n", "\n", "\n", "\n", "trace()\n", "\n", "\n", "\n", "traceit()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Coverage\n", "\n", "\n", "Coverage\n", "\n", "\n", "\n", "__enter__()\n", "\n", "\n", "\n", "__exit__()\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "__repr__()\n", "\n", "\n", "\n", "coverage()\n", "\n", "\n", "\n", "function_names()\n", "\n", "\n", "\n", "trace()\n", "\n", "\n", "\n", "traceit()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# ignore\n", "display_class_hierarchy(Coverage,\n", " public_methods=[\n", " Coverage.__init__,\n", " Coverage.__enter__,\n", " Coverage.__exit__,\n", " Coverage.coverage,\n", " Coverage.trace,\n", " Coverage.function_names,\n", " Coverage.__repr__,\n", " ],\n", " types={'Location': Location},\n", " project='fuzzingbook')" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Lessons Learned\n", "\n", "* Coverage metrics are a simple and fully automated means to approximate how much functionality of a program is actually executed during a test run.\n", "* A number of coverage metrics exist, the most important ones being statement coverage and branch coverage.\n", "* In Python, it is very easy to access the program state during execution, including the currently executed code." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "At the end of the day, let's clean up: (Note that the following commands will delete all files in the current working directory that fit the pattern `cgi_decode.*`. Be aware of this, if you downloaded the notebooks and are working locally.)" ] }, { "cell_type": "code", "execution_count": 67, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.269184Z", "iopub.status.busy": "2025-01-16T09:37:51.268947Z", "iopub.status.idle": "2025-01-16T09:37:51.270830Z", "shell.execute_reply": "2025-01-16T09:37:51.270605Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import os\n", "import glob" ] }, { "cell_type": "code", "execution_count": 68, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.272165Z", "iopub.status.busy": "2025-01-16T09:37:51.272064Z", "iopub.status.idle": "2025-01-16T09:37:51.274626Z", "shell.execute_reply": "2025-01-16T09:37:51.274377Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "for file in (glob.glob(\"cgi_decode\") + \n", " glob.glob(\"cgi_decode.*\") + \n", " glob.glob(\"cgi_decode-*\")):\n", " os.remove(file)" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "skip" } }, "source": [ "## Next Steps\n", "\n", "Coverage is not only a tool to _measure_ test effectiveness, but also a great tool to _guide_ test generation towards specific goals – in particular uncovered code. We use coverage to\n", "\n", "* [guide _mutations_ of existing inputs towards better coverage in the chapter on mutation fuzzing](MutationFuzzer.ipynb)\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Background\n", "\n", "Coverage is a central concept in systematic software testing. For discussions, see the books in the [Introduction to Testing](Intro_Testing.ipynb)." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Exercises" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution": "hidden", "solution2": "hidden", "solution2_first": true, "solution_first": true }, "source": [ "### Exercise 1: Fixing `cgi_decode()`\n", "\n", "Create an appropriate test to reproduce the `IndexError` discussed above. Fix `cgi_decode()` to prevent the bug. Show that your test (and additional `fuzzer()` runs) no longer expose the bug. Do the same for the C variant." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution": "hidden", "solution2": "hidden" }, "source": [ "**Solution.** Here's a test case:" ] }, { "cell_type": "code", "execution_count": 69, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.276316Z", "iopub.status.busy": "2025-01-16T09:37:51.276209Z", "iopub.status.idle": "2025-01-16T09:37:51.278168Z", "shell.execute_reply": "2025-01-16T09:37:51.277940Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Traceback (most recent call last):\n", " File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_2687/1102034435.py\", line 2, in \n", " assert cgi_decode('%') == '%'\n", " ^^^^^^^^^^^^^^^\n", " File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_2687/1071239422.py\", line 22, in cgi_decode\n", " digit_high, digit_low = s[i + 1], s[i + 2]\n", " ~^^^^^^^\n", "IndexError: string index out of range (expected)\n" ] } ], "source": [ "with ExpectError():\n", " assert cgi_decode('%') == '%'" ] }, { "cell_type": "code", "execution_count": 70, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.279679Z", "iopub.status.busy": "2025-01-16T09:37:51.279570Z", "iopub.status.idle": "2025-01-16T09:37:51.281450Z", "shell.execute_reply": "2025-01-16T09:37:51.281150Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Traceback (most recent call last):\n", " File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_2687/2291699482.py\", line 2, in \n", " assert cgi_decode('%4') == '%4'\n", " ^^^^^^^^^^^^^^^^\n", " File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_2687/1071239422.py\", line 22, in cgi_decode\n", " digit_high, digit_low = s[i + 1], s[i + 2]\n", " ~^^^^^^^\n", "IndexError: string index out of range (expected)\n" ] } ], "source": [ "with ExpectError():\n", " assert cgi_decode('%4') == '%4'" ] }, { "cell_type": "code", "execution_count": 71, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.282968Z", "iopub.status.busy": "2025-01-16T09:37:51.282846Z", "iopub.status.idle": "2025-01-16T09:37:51.284543Z", "shell.execute_reply": "2025-01-16T09:37:51.284296Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [], "source": [ "assert cgi_decode('%40') == '@'" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "source": [ "Here's a fix:" ] }, { "cell_type": "code", "execution_count": 72, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.286072Z", "iopub.status.busy": "2025-01-16T09:37:51.285961Z", "iopub.status.idle": "2025-01-16T09:37:51.288691Z", "shell.execute_reply": "2025-01-16T09:37:51.288451Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [], "source": [ "def fixed_cgi_decode(s):\n", " \"\"\"Decode the CGI-encoded string `s`:\n", " * replace \"+\" by \" \"\n", " * replace \"%xx\" by the character with hex number xx.\n", " Return the decoded string. Raise `ValueError` for invalid inputs.\"\"\"\n", "\n", " # Mapping of hex digits to their integer values\n", " hex_values = {\n", " '0': 0, '1': 1, '2': 2, '3': 3, '4': 4,\n", " '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,\n", " 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,\n", " 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,\n", " }\n", "\n", " t = \"\"\n", " i = 0\n", " while i < len(s):\n", " c = s[i]\n", " if c == '+':\n", " t += ' '\n", " elif c == '%' and i + 2 < len(s): # <--- *** FIX ***\n", " digit_high, digit_low = s[i + 1], s[i + 2]\n", " i += 2\n", " if digit_high in hex_values and digit_low in hex_values:\n", " v = hex_values[digit_high] * 16 + hex_values[digit_low]\n", " t += chr(v)\n", " else:\n", " raise ValueError(\"Invalid encoding\")\n", " else:\n", " t += c\n", " i += 1\n", " return t" ] }, { "cell_type": "code", "execution_count": 73, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.290239Z", "iopub.status.busy": "2025-01-16T09:37:51.290138Z", "iopub.status.idle": "2025-01-16T09:37:51.291683Z", "shell.execute_reply": "2025-01-16T09:37:51.291474Z" }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [], "source": [ "assert fixed_cgi_decode('%') == '%'" ] }, { "cell_type": "code", "execution_count": 74, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.293130Z", "iopub.status.busy": "2025-01-16T09:37:51.293042Z", "iopub.status.idle": "2025-01-16T09:37:51.294754Z", "shell.execute_reply": "2025-01-16T09:37:51.294525Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [], "source": [ "assert fixed_cgi_decode('%4') == '%4'" ] }, { "cell_type": "code", "execution_count": 75, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.296186Z", "iopub.status.busy": "2025-01-16T09:37:51.296108Z", "iopub.status.idle": "2025-01-16T09:37:51.297767Z", "shell.execute_reply": "2025-01-16T09:37:51.297546Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [], "source": [ "assert fixed_cgi_decode('%40') == '@'" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "source": [ "Here's the test:" ] }, { "cell_type": "code", "execution_count": 76, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.299351Z", "iopub.status.busy": "2025-01-16T09:37:51.299271Z", "iopub.status.idle": "2025-01-16T09:37:51.302608Z", "shell.execute_reply": "2025-01-16T09:37:51.302401Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [], "source": [ "for i in range(trials):\n", " try:\n", " s = fuzzer()\n", " fixed_cgi_decode(s)\n", " except ValueError:\n", " pass" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "source": [ "For the C variant, the following will do:" ] }, { "cell_type": "code", "execution_count": 77, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.303955Z", "iopub.status.busy": "2025-01-16T09:37:51.303876Z", "iopub.status.idle": "2025-01-16T09:37:51.305501Z", "shell.execute_reply": "2025-01-16T09:37:51.305295Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [], "source": [ "cgi_c_code = cgi_c_code.replace(\n", " r\"if (*s == '%')\", # old code\n", " r\"if (*s == '%' && s[1] != '\\0' && s[2] != '\\0')\" # new code\n", ")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "source": [ "Go back to the above compilation commands and recompile `cgi_decode`." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "### Exercise 2: Branch Coverage\n", "\n", "Besides statement coverage, _branch coverage_ is one of the most frequently used criteria to determine the quality of a test. In a nutshell, branch coverage measures how many _control decisions_ are made in code. In the statement\n", "\n", "```python\n", "if CONDITION:\n", " do_a()\n", "else:\n", " do_b()\n", "```\n", "\n", "for instance, both the cases where `CONDITION` is true (branching to `do_a()`) and where `CONDITION` is false (branching to `do_b()`) have to be covered. This holds for all control statements with a condition (`if`, `while`, etc.).\n", "\n", "How is branch coverage different from statement coverage? In the above example, there is actually no difference. In this one, though, there is:\n", "\n", "```python\n", "if CONDITION:\n", " do_a()\n", "something_else()\n", "```\n", "\n", "Using statement coverage, a single test case where `CONDITION` is true suffices to cover the call to `do_a()`. Using branch coverage, however, we would also have to create a test case where `do_a()` is _not_ invoked." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Using our `Coverage` infrastructure, we can simulate branch coverage by considering _pairs of subsequent lines executed_. The `trace()` method gives us the list of lines executed one after the other:" ] }, { "cell_type": "code", "execution_count": 78, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:51.307034Z", "iopub.status.busy": "2025-01-16T09:37:51.306964Z", "iopub.status.idle": "2025-01-16T09:37:51.309744Z", "shell.execute_reply": "2025-01-16T09:37:51.309479Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 8),\n", " ('cgi_decode', 9),\n", " ('cgi_decode', 8)]" ] }, "execution_count": 78, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with Coverage() as cov:\n", " cgi_decode(\"a+b\")\n", "trace = cov.trace()\n", "trace[:5]" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden", "solution2_first": true }, "source": [ "#### Part 1: Compute branch coverage\n", "\n", "Define a function `branch_coverage()` that takes a trace and returns the set of pairs of subsequent lines in a trace – in the above example, this would be \n", "\n", "```python\n", "set(\n", "(('cgi_decode', 9), ('cgi_decode', 10)),\n", "(('cgi_decode', 10), ('cgi_decode', 11)),\n", "# more_pairs\n", ")\n", "```\n", "\n", "Bonus for advanced Python programmers: Define `BranchCoverage` as a subclass of `Coverage` and make `branch_coverage()` as above a `coverage()` method of `BranchCoverage`." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution": "hidden", "solution2": "hidden" }, "source": [ "**Solution.** Here's a simple definition of `branch_coverage()`:" ] }, { "cell_type": "code", "execution_count": 79, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.311382Z", "iopub.status.busy": "2025-01-16T09:37:51.311269Z", "iopub.status.idle": "2025-01-16T09:37:51.312979Z", "shell.execute_reply": "2025-01-16T09:37:51.312747Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [], "source": [ "def branch_coverage(trace):\n", " coverage = set()\n", " past_line = None\n", " for line in trace:\n", " if past_line is not None:\n", " coverage.add((past_line, line))\n", " past_line = line\n", "\n", " return coverage" ] }, { "cell_type": "code", "execution_count": 80, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.314495Z", "iopub.status.busy": "2025-01-16T09:37:51.314393Z", "iopub.status.idle": "2025-01-16T09:37:51.316881Z", "shell.execute_reply": "2025-01-16T09:37:51.316663Z" }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [ { "data": { "text/plain": [ "{(('cgi_decode', 8), ('cgi_decode', 9)),\n", " (('cgi_decode', 8), ('cgi_decode', 10)),\n", " (('cgi_decode', 8), ('cgi_decode', 11)),\n", " (('cgi_decode', 8), ('cgi_decode', 12)),\n", " (('cgi_decode', 8), ('cgi_decode', 15)),\n", " (('cgi_decode', 9), ('cgi_decode', 8)),\n", " (('cgi_decode', 10), ('cgi_decode', 8)),\n", " (('cgi_decode', 11), ('cgi_decode', 8)),\n", " (('cgi_decode', 12), ('cgi_decode', 8)),\n", " (('cgi_decode', 15), ('cgi_decode', 16)),\n", " (('cgi_decode', 16), ('cgi_decode', 17)),\n", " (('cgi_decode', 17), ('cgi_decode', 18)),\n", " (('cgi_decode', 17), ('cgi_decode', 32)),\n", " (('cgi_decode', 18), ('cgi_decode', 19)),\n", " (('cgi_decode', 19), ('cgi_decode', 20)),\n", " (('cgi_decode', 19), ('cgi_decode', 21)),\n", " (('cgi_decode', 20), ('cgi_decode', 31)),\n", " (('cgi_decode', 21), ('cgi_decode', 30)),\n", " (('cgi_decode', 30), ('cgi_decode', 31)),\n", " (('cgi_decode', 31), ('cgi_decode', 17))}" ] }, "execution_count": 80, "metadata": {}, "output_type": "execute_result" } ], "source": [ "branch_coverage(trace)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "source": [ "Here's a definition as a class:" ] }, { "cell_type": "code", "execution_count": 81, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.318675Z", "iopub.status.busy": "2025-01-16T09:37:51.318559Z", "iopub.status.idle": "2025-01-16T09:37:51.320517Z", "shell.execute_reply": "2025-01-16T09:37:51.320281Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [], "source": [ "class BranchCoverage(Coverage):\n", " def coverage(self):\n", " \"\"\"The set of executed line pairs\"\"\"\n", " coverage = set()\n", " past_line = None\n", " for line in self.trace():\n", " if past_line is not None:\n", " coverage.add((past_line, line))\n", " past_line = line\n", "\n", " return coverage" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden", "solution2_first": true }, "source": [ "#### Part 2: Comparing statement coverage and branch coverage\n", "\n", "Use `branch_coverage()` to repeat the experiments in this chapter with branch coverage rather than statement coverage. Do the manually written test cases cover all branches?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "source": [ "**Solution.** Let's repeat the above experiments with `BranchCoverage`:" ] }, { "cell_type": "code", "execution_count": 82, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.322004Z", "iopub.status.busy": "2025-01-16T09:37:51.321904Z", "iopub.status.idle": "2025-01-16T09:37:51.324001Z", "shell.execute_reply": "2025-01-16T09:37:51.323762Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{(('cgi_decode', 8), ('cgi_decode', 9)), (('cgi_decode', 8), ('cgi_decode', 15)), (('cgi_decode', 30), ('cgi_decode', 31)), (('cgi_decode', 8), ('cgi_decode', 10)), (('cgi_decode', 20), ('cgi_decode', 31)), (('cgi_decode', 16), ('cgi_decode', 17)), (('cgi_decode', 8), ('cgi_decode', 11)), (('cgi_decode', 10), ('cgi_decode', 8)), (('cgi_decode', 17), ('cgi_decode', 32)), (('cgi_decode', 9), ('cgi_decode', 8)), (('cgi_decode', 17), ('cgi_decode', 18)), (('cgi_decode', 21), ('cgi_decode', 30)), (('cgi_decode', 12), ('cgi_decode', 8)), (('cgi_decode', 19), ('cgi_decode', 20)), (('cgi_decode', 11), ('cgi_decode', 8)), (('cgi_decode', 18), ('cgi_decode', 19)), (('cgi_decode', 15), ('cgi_decode', 16)), (('cgi_decode', 8), ('cgi_decode', 12)), (('cgi_decode', 19), ('cgi_decode', 21)), (('cgi_decode', 31), ('cgi_decode', 17))}\n" ] } ], "source": [ "with BranchCoverage() as cov:\n", " cgi_decode(\"a+b\")\n", "\n", "print(cov.coverage())" ] }, { "cell_type": "code", "execution_count": 83, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:51.325468Z", "iopub.status.busy": "2025-01-16T09:37:51.325364Z", "iopub.status.idle": "2025-01-16T09:37:51.328065Z", "shell.execute_reply": "2025-01-16T09:37:51.327848Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [ { "data": { "text/plain": [ "{(('cgi_decode', 19), ('cgi_decode', 20)),\n", " (('cgi_decode', 20), ('cgi_decode', 31))}" ] }, "execution_count": 83, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with BranchCoverage() as cov_plus:\n", " cgi_decode(\"a+b\")\n", "with BranchCoverage() as cov_standard:\n", " cgi_decode(\"abc\")\n", "\n", "cov_plus.coverage() - cov_standard.coverage()" ] }, { "cell_type": "code", "execution_count": 84, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:51.329497Z", "iopub.status.busy": "2025-01-16T09:37:51.329393Z", "iopub.status.idle": "2025-01-16T09:37:51.331396Z", "shell.execute_reply": "2025-01-16T09:37:51.331176Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [], "source": [ "with BranchCoverage() as cov_max:\n", " cgi_decode('+')\n", " cgi_decode('%20')\n", " cgi_decode('abc')\n", " try:\n", " cgi_decode('%?a')\n", " except:\n", " pass" ] }, { "cell_type": "code", "execution_count": 85, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:51.332799Z", "iopub.status.busy": "2025-01-16T09:37:51.332708Z", "iopub.status.idle": "2025-01-16T09:37:51.334832Z", "shell.execute_reply": "2025-01-16T09:37:51.334579Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [ { "data": { "text/plain": [ "{(('cgi_decode', 21), ('cgi_decode', 22)),\n", " (('cgi_decode', 22), ('cgi_decode', 23)),\n", " (('cgi_decode', 23), ('cgi_decode', 24)),\n", " (('cgi_decode', 24), ('cgi_decode', 25)),\n", " (('cgi_decode', 24), ('cgi_decode', 28)),\n", " (('cgi_decode', 25), ('cgi_decode', 26)),\n", " (('cgi_decode', 26), ('cgi_decode', 31)),\n", " (('cgi_decode', 32), ('cgi_decode', 8))}" ] }, "execution_count": 85, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cov_max.coverage() - cov_plus.coverage()" ] }, { "cell_type": "code", "execution_count": 86, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:51.336598Z", "iopub.status.busy": "2025-01-16T09:37:51.336482Z", "iopub.status.idle": "2025-01-16T09:37:51.338854Z", "shell.execute_reply": "2025-01-16T09:37:51.338579Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [ { "data": { "text/plain": [ "'!7#%\"*#0=)$;%6*;>638:*>80\"=(/*:-(2<4 !:5*6856&?\"\"11<7+%<%7,4.8,*+&,,$,.\"'" ] }, "execution_count": 86, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sample" ] }, { "cell_type": "code", "execution_count": 87, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:51.340346Z", "iopub.status.busy": "2025-01-16T09:37:51.340246Z", "iopub.status.idle": "2025-01-16T09:37:51.343489Z", "shell.execute_reply": "2025-01-16T09:37:51.343249Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [ { "data": { "text/plain": [ "{(('cgi_decode', 8), ('cgi_decode', 9)),\n", " (('cgi_decode', 8), ('cgi_decode', 10)),\n", " (('cgi_decode', 8), ('cgi_decode', 11)),\n", " (('cgi_decode', 8), ('cgi_decode', 12)),\n", " (('cgi_decode', 8), ('cgi_decode', 15)),\n", " (('cgi_decode', 9), ('cgi_decode', 8)),\n", " (('cgi_decode', 10), ('cgi_decode', 8)),\n", " (('cgi_decode', 11), ('cgi_decode', 8)),\n", " (('cgi_decode', 12), ('cgi_decode', 8)),\n", " (('cgi_decode', 15), ('cgi_decode', 16)),\n", " (('cgi_decode', 16), ('cgi_decode', 17)),\n", " (('cgi_decode', 17), ('cgi_decode', 18)),\n", " (('cgi_decode', 18), ('cgi_decode', 19)),\n", " (('cgi_decode', 19), ('cgi_decode', 20)),\n", " (('cgi_decode', 19), ('cgi_decode', 21)),\n", " (('cgi_decode', 20), ('cgi_decode', 31)),\n", " (('cgi_decode', 21), ('cgi_decode', 22)),\n", " (('cgi_decode', 21), ('cgi_decode', 30)),\n", " (('cgi_decode', 22), ('cgi_decode', 23)),\n", " (('cgi_decode', 23), ('cgi_decode', 24)),\n", " (('cgi_decode', 24), ('cgi_decode', 28)),\n", " (('cgi_decode', 30), ('cgi_decode', 31)),\n", " (('cgi_decode', 31), ('cgi_decode', 17))}" ] }, "execution_count": 87, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with BranchCoverage() as cov_fuzz:\n", " try:\n", " cgi_decode(s)\n", " except:\n", " pass\n", "cov_fuzz.coverage()" ] }, { "cell_type": "code", "execution_count": 88, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.345138Z", "iopub.status.busy": "2025-01-16T09:37:51.345028Z", "iopub.status.idle": "2025-01-16T09:37:51.347751Z", "shell.execute_reply": "2025-01-16T09:37:51.347469Z" }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [ { "data": { "text/plain": [ "{(('cgi_decode', 17), ('cgi_decode', 32)),\n", " (('cgi_decode', 24), ('cgi_decode', 25)),\n", " (('cgi_decode', 25), ('cgi_decode', 26)),\n", " (('cgi_decode', 26), ('cgi_decode', 31)),\n", " (('cgi_decode', 32), ('cgi_decode', 8))}" ] }, "execution_count": 88, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cov_max.coverage() - cov_fuzz.coverage()" ] }, { "cell_type": "code", "execution_count": 89, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.349497Z", "iopub.status.busy": "2025-01-16T09:37:51.349389Z", "iopub.status.idle": "2025-01-16T09:37:51.351630Z", "shell.execute_reply": "2025-01-16T09:37:51.351348Z" }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [], "source": [ "def population_branch_coverage(population, function):\n", " cumulative_coverage = []\n", " all_coverage = set()\n", "\n", " for s in population:\n", " with BranchCoverage() as cov:\n", " try:\n", " function(s)\n", " except Exception:\n", " pass\n", " all_coverage |= cov.coverage()\n", " cumulative_coverage.append(len(all_coverage))\n", "\n", " return all_coverage, cumulative_coverage" ] }, { "cell_type": "code", "execution_count": 90, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:51.353165Z", "iopub.status.busy": "2025-01-16T09:37:51.353059Z", "iopub.status.idle": "2025-01-16T09:37:51.378250Z", "shell.execute_reply": "2025-01-16T09:37:51.377930Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [], "source": [ "all_branch_coverage, cumulative_branch_coverage = population_branch_coverage(\n", " hundred_inputs(), cgi_decode)" ] }, { "cell_type": "code", "execution_count": 91, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:51.379939Z", "iopub.status.busy": "2025-01-16T09:37:51.379845Z", "iopub.status.idle": "2025-01-16T09:37:51.442497Z", "shell.execute_reply": "2025-01-16T09:37:51.442204Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'line pairs covered')" ] }, "execution_count": 91, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.plot(cumulative_branch_coverage)\n", "plt.title('Branch coverage of cgi_decode() with random inputs')\n", "plt.xlabel('# of inputs')\n", "plt.ylabel('line pairs covered')" ] }, { "cell_type": "code", "execution_count": 92, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.444092Z", "iopub.status.busy": "2025-01-16T09:37:51.443984Z", "iopub.status.idle": "2025-01-16T09:37:51.446042Z", "shell.execute_reply": "2025-01-16T09:37:51.445832Z" }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [ { "data": { "text/plain": [ "28" ] }, "execution_count": 92, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(cov_max.coverage())" ] }, { "cell_type": "code", "execution_count": 93, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.447567Z", "iopub.status.busy": "2025-01-16T09:37:51.447444Z", "iopub.status.idle": "2025-01-16T09:37:51.449680Z", "shell.execute_reply": "2025-01-16T09:37:51.449420Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [ { "data": { "text/plain": [ "set()" ] }, "execution_count": 93, "metadata": {}, "output_type": "execute_result" } ], "source": [ "all_branch_coverage - cov_max.coverage()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "source": [ "The additional coverage comes from the exception raised via an illegal input (say, `%g`)." ] }, { "cell_type": "code", "execution_count": 94, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:37:51.451156Z", "iopub.status.busy": "2025-01-16T09:37:51.451052Z", "iopub.status.idle": "2025-01-16T09:37:51.453034Z", "shell.execute_reply": "2025-01-16T09:37:51.452810Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [ { "data": { "text/plain": [ "{(('cgi_decode', 32), ('cgi_decode', 8))}" ] }, "execution_count": 94, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cov_max.coverage() - all_branch_coverage" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "source": [ "This is an artifact coming from the subsequent execution of `cgi_decode()` when computing `cov_max`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" }, "solution2": "hidden", "solution2_first": true }, "source": [ "#### Part 3: Average coverage\n", "\n", "Again, repeat the above experiments with branch coverage. Does `fuzzer()` cover all branches, and if so, how many tests does it take on average?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "source": [ "**Solution.** We repeat the experiments we ran with line coverage with branch coverage." ] }, { "cell_type": "code", "execution_count": 95, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:51.454607Z", "iopub.status.busy": "2025-01-16T09:37:51.454500Z", "iopub.status.idle": "2025-01-16T09:37:53.596652Z", "shell.execute_reply": "2025-01-16T09:37:53.596290Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [], "source": [ "runs = 100\n", "\n", "# Create an array with TRIALS elements, all zero\n", "sum_coverage = [0] * trials\n", "\n", "for run in range(runs):\n", " all_branch_coverage, coverage = population_branch_coverage(\n", " hundred_inputs(), cgi_decode)\n", " assert len(coverage) == trials\n", " for i in range(trials):\n", " sum_coverage[i] += coverage[i]\n", "\n", "average_coverage = []\n", "for i in range(trials):\n", " average_coverage.append(sum_coverage[i] / runs)" ] }, { "cell_type": "code", "execution_count": 96, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:37:53.598629Z", "iopub.status.busy": "2025-01-16T09:37:53.598514Z", "iopub.status.idle": "2025-01-16T09:37:53.690280Z", "shell.execute_reply": "2025-01-16T09:37:53.689978Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'line pairs covered')" ] }, "execution_count": 96, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAHHCAYAAABZbpmkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABex0lEQVR4nO3deVhU5dsH8O+wDTuIsgoI4i6uuKIipuGWZpqaWu5LCZJmWtav3CrUSlNfMy3FSs19zyVKxRUXFHdxVxRRBGHYt3neP4yTI6AMDMwwfD/XNdfFnPPMOfc8s90825EJIQSIiIiI9JSBtgMgIiIiKktMdoiIiEivMdkhIiIivcZkh4iIiPQakx0iIiLSa0x2iIiISK8x2SEiIiK9xmSHiIiI9BqTHSIiItJrTHYqEZlMhqCgIG2HUSIeHh544403tB0GqWHv3r1o2rQpTE1NIZPJkJSUVC7n9ff3h7+/v0aO5eHhgeHDh2vkWGWhtPHFxMTA1NQUR48elba98847GDBggAaiK9rBgwchk8lw8ODBYpfdtGlTmcZU1mQyGWbMmKHtMCSa/JxUBHqX7Pz444+QyWRo3bq1tkMhqrQSEhIwYMAAmJmZYcmSJfj9999hYWGh7bDoBbNmzULr1q3Rrl07adsnn3yCzZs349y5c+Uay9q1a/HDDz+U6zlJd5T1629UZkfWkjVr1sDDwwMnT57EjRs3UKtWLW2HRFTpnDp1CikpKZg9eza6dOlSruf+66+/yvV8FVV8fDx+/fVX/PrrryrbmzVrhhYtWuD777/Hb7/9Vibn9vPzQ0ZGBkxMTKRta9euxcWLFzFx4sQyOSep0rXPSVm//nrVsnP79m0cO3YM8+fPh729PdasWVPuMSiVSmRmZpb7ectaWlqatkOoVNLT07UdQqk8fvwYAGBra1vu5zYxMVH5EaXCrV69GkZGRujVq1eBfQMGDMCWLVuQmppaJuc2MDCAqakpDAy08xPE77PK9znRq2RnzZo1qFKlCnr27Im3335bJdnJycmBnZ0dRowYUeBxCoUCpqam+Pjjj6VtWVlZmD59OmrVqgW5XA43NzdMnToVWVlZKo/NHwezZs0aNGzYEHK5HHv37gUAfPfdd/D19UXVqlVhZmYGHx+fQvudMzIyEBwcjGrVqsHKygq9e/fGgwcPCu3jffDgAUaOHAlHR0fI5XI0bNgQK1euVLue6tatC1NTU/j4+ODQoUMq+2fMmAGZTIbLly9j8ODBqFKlCtq3bw8AOH/+PIYPH46aNWvC1NQUTk5OGDlyJBISEgo9xo0bNzB8+HDY2trCxsYGI0aMKPSHfPXq1WjVqhXMzc1RpUoV+Pn5Ffqfx5EjR9CqVSuYmpqiZs2axf7PU6lUYuHChWjUqBFMTU1hb2+Pbt264fTp01KZ3NxczJ49G15eXpDL5fDw8MBnn32m8pq/8cYbqFmzZqHnaNu2LVq0aFHgefn4+MDMzAx2dnZ45513EBMTo1LG398f3t7eiIyMhJ+fH8zNzfHZZ58BALZv346ePXvCxcUFcrkcXl5emD17NvLy8gqcf8mSJahZsybMzMzQqlUrHD58uNB++eK+t4uyceNG6TlVq1YN7777Lh48eKDyfIYNGwYAaNmyJWQy2SvHlTx48ACjRo2Snqenpyc++OADZGdnS2XOnz+Pjh07wszMDK6urvjqq68QGhoKmUyGO3fuqJxf3bEIQgh89dVXcHV1hbm5OTp16oRLly4VWjYpKQkTJ06Em5sb5HI5atWqhblz50KpVKqU09R7rqzi27ZtG1q3bg1LS8sCx3j99deRlpaGsLCwl9Zb37590bx5c5VtvXr1gkwmw44dO6RtJ06cgEwmw549ewAUHLPj7++PP//8E3fv3oVMJoNMJoOHh0eB+vz666/h6uoKU1NTdO7cGTdu3HhpfED5f59lZWVh0qRJsLe3l77P79+/X2hsZ8+eRffu3WFtbQ1LS0t07twZERERKmVWrVoFmUyGI0eOIDg4GPb29rC1tcW4ceOQnZ2NpKQkDB06FFWqVEGVKlUwdepUCCFeWS8vfk7yX5MNGza8sp6f/87y9fWFmZkZPD098dNPPxUa+/Ofz+fPVdzXf/HixWjYsKH0+9CiRQusXbv2lc/xeXrVjbVmzRr07dsXJiYmGDRoEJYuXYpTp06hZcuWMDY2xltvvYUtW7Zg2bJlKhnttm3bkJWVhXfeeQfAsw9V7969ceTIEYwdOxb169fHhQsXsGDBAly7dg3btm1TOe/+/fuxYcMGBAUFoVq1atKLtHDhQvTu3RtDhgxBdnY21q1bh/79+2PXrl3o2bOn9Pjhw4djw4YNeO+999CmTRuEh4er7M/36NEjtGnTRkqw7O3tsWfPHowaNQoKhaJYzX/h4eFYv349goODIZfL8eOPP6Jbt244efIkvL29Vcr2798ftWvXxjfffCN9eMLCwnDr1i2MGDECTk5OuHTpEpYvX45Lly4hIiICMplM5RgDBgyAp6cnQkJCcObMGfzyyy9wcHDA3LlzpTIzZ87EjBkz4Ovri1mzZsHExAQnTpzA/v37ERAQIJW7ceMG3n77bYwaNQrDhg3DypUrMXz4cPj4+KBhw4Yvfd6jRo3CqlWr0L17d4wePRq5ubk4fPgwIiIipARl9OjR+PXXX/H2229j8uTJOHHiBEJCQnDlyhVs3boVADBw4EAMHTpUel/lu3v3LiIiIvDtt99K277++mt88cUXGDBgAEaPHo34+HgsXrwYfn5+OHv2rEqrR0JCArp374533nkH7777LhwdHQE8+7KwtLTERx99BEtLS+zfvx9ffvklFAqFyrmWLl2KoKAgdOjQAZMmTcKdO3fQp08fVKlSBa6urlI5dd/bL1q1ahVGjBiBli1bIiQkBI8ePcLChQtx9OhR6Tl9/vnnqFu3LpYvX45Zs2bB09MTXl5eRR4zNjYWrVq1QlJSEsaOHYt69erhwYMH2LRpE9LT02FiYoIHDx6gU6dOkMlkmDZtGiwsLPDLL79ALpe/NN7i+vLLL/HVV1+hR48e6NGjB86cOYOAgACVZAt41uLWsWNHPHjwAOPGjYO7uzuOHTuGadOm4eHDhypjDjT1niuL+HJycnDq1Cl88MEHhdZHgwYNYGZmhqNHj+Ktt94qst46dOiA7du3Q6FQwNraGkIIHD16FAYGBjh8+DB69+4NADh8+DAMDAxUxgY97/PPP0dycjLu37+PBQsWAECBJGzOnDkwMDDAxx9/jOTkZMybNw9DhgzBiRMniozveeX1fTZ69GisXr0agwcPhq+vL/bv31/o9/mlS5fQoUMHWFtbY+rUqTA2NsayZcvg7++P8PDwAuNOJ0yYACcnJ8ycORMRERFYvnw5bG1tcezYMbi7u+Obb77B7t278e2338Lb2xtDhw4tVr28qLj1/PTpU/To0QMDBgzAoEGDsGHDBnzwwQcwMTHByJEj1Trny17/n3/+GcHBwXj77bfx4YcfIjMzE+fPn8eJEycwePDg4p9E6InTp08LACIsLEwIIYRSqRSurq7iww8/lMrs27dPABA7d+5UeWyPHj1EzZo1pfu///67MDAwEIcPH1Yp99NPPwkA4ujRo9I2AMLAwEBcunSpQEzp6ekq97Ozs4W3t7d47bXXpG2RkZECgJg4caJK2eHDhwsAYvr06dK2UaNGCWdnZ/HkyROVsu+8846wsbEpcL4XARAAxOnTp6Vtd+/eFaampuKtt96Stk2fPl0AEIMGDXrlcxJCiD/++EMAEIcOHSpwjJEjR6qUfeutt0TVqlWl+9evXxcGBgbirbfeEnl5eSpllUql9HeNGjUKnOPx48dCLpeLyZMnv/R579+/XwAQwcHBBfblnyMqKkoAEKNHj1bZ//HHHwsAYv/+/UIIIZKTkws957x584RMJhN3794VQghx584dYWhoKL7++muVchcuXBBGRkYq2zt27CgAiJ9++qlAfIXV97hx44S5ubnIzMwUQgiRlZUlqlatKlq2bClycnKkcqtWrRIARMeOHaVt6ry3X5SdnS0cHByEt7e3yMjIkLbv2rVLABBffvmltC00NFQAEKdOnSryePmGDh0qDAwMCi2b//pMmDBByGQycfbsWWlfQkKCsLOzEwDE7du3pe0dO3ZUec6v8vjxY2FiYiJ69uyp8p777LPPBAAxbNgwadvs2bOFhYWFuHbtmsoxPv30U2FoaCju3bsnhNDse64s4rtx44YAIBYvXlxkvdSpU0d07969yP1CCHHq1CkBQOzevVsIIcT58+cFANG/f3/RunVrqVzv3r1Fs2bNpPsHDhwQAMSBAwekbT179hQ1atQocI78svXr1xdZWVnS9oULFwoA4sKFCy+NsTy/z/Jf0/Hjx6uUGzx4cIHv8z59+ggTExNx8+ZNaVtsbKywsrISfn5+0rb8z1LXrl1VXv+2bdsKmUwm3n//fWlbbm6ucHV1Ldb7/8XPiTr1nP+d9f3330vbsrKyRNOmTYWDg4PIzs5Wif35z+fz5yrO6//mm2+Khg0bvvL5vIredGOtWbMGjo6O6NSpE4Bn3UsDBw7EunXrpCb/1157DdWqVcP69eulxz19+hRhYWEYOHCgtG3jxo2oX78+6tWrhydPnki31157DQBw4MABlXN37NgRDRo0KBCTmZmZynmSk5PRoUMHnDlzRtqe3+U1fvx4lcdOmDBB5b4QAps3b0avXr0ghFCJq2vXrkhOTlY5blHatm0LHx8f6b67uzvefPNN7Nu3r0DXyPvvv//S55SZmYknT56gTZs2AFDo+V88RocOHZCQkACFQgHgWauaUqnEl19+WaD//sX/qho0aIAOHTpI9+3t7VG3bl3cunXrpc958+bNkMlkmD59eoF9+efYvXs3AOCjjz5S2T958mQAwJ9//gkAsLa2Rvfu3bFhwwaVpuL169ejTZs2cHd3BwBs2bIFSqUSAwYMUHmtnJycULt27QLvIblcXmgX6/P1nZKSgidPnqBDhw5IT0/H1atXAQCnT59GQkICxowZAyOj/xprhwwZgipVqqgcT9339vNOnz6Nx48fY/z48TA1NZW29+zZE/Xq1ZPqSB1KpRLbtm1Dr169CnQBAv+9Pnv37kXbtm3RtGlTaZ+dnR2GDBmi9jlf9PfffyM7OxsTJkxQec8V1lK6ceNGdOjQAVWqVFGpvy5duiAvL0/qEtbke64s4svvpnnx/fG8/GO8TLNmzWBpaSkd9/Dhw3B1dcXQoUNx5swZpKenQwiBI0eOqHx2S2LEiBEqLfL5x3vV5z9feXyf5b+mwcHBKuVefK3y8vLw119/oU+fPird4s7Ozhg8eDCOHDkiHTPfqFGjVF7/1q1bQwiBUaNGSdsMDQ3RokWLYtdJYYpbz0ZGRhg3bpx038TEBOPGjcPjx48RGRlZ4vO/yNbWFvfv38epU6dKdRy96MbKy8vDunXr0KlTJ9y+fVva3rp1a3z//ff4559/EBAQACMjI/Tr1w9r165FVlYW5HI5tmzZgpycHJVk5/r167hy5Qrs7e0LPV/+4Mt8np6ehZbbtWsXvvrqK0RFRan0wT//hr179y4MDAwKHOPFWWTx8fFISkrC8uXLsXz58mLFVZjatWsX2FanTh2kp6cjPj4eTk5OL31eiYmJmDlzJtatW1fgfMnJyQXK5//458v/cn369Cmsra1x8+ZNGBgYFJosvupY+cd7+vTpSx938+ZNuLi4wM7Orsgy+a/Di/Xu5OQEW1tb3L17V9o2cOBAbNu2DcePH4evry9u3ryJyMhIlS6M69evQwhRaH0DgLGxscr96tWrFzpY8NKlS/jf//6H/fv3F/jyy6/v/NhejN3IyKjAuAd139vPyz9P3bp1C+yrV68ejhw5UuRjixIfHw+FQlGgC7Wwc7dt27bAdk3Mtsx/Xi++Vvb29gWSgevXr+P8+fOvrD9NvufKIr584iVjO4QQBf7heJGhoSHatm2Lw4cPA3iW7HTo0AHt27dHXl4eIiIi4OjoiMTExFInOy/7LimO8vg+y39NX+y2ffEzEx8fj/T09EI/S/Xr14dSqURMTIxK9/yL57axsQEAuLm5Fdhe3DopTHHr2cXFpcByEnXq1AEA3LlzR0oaS+uTTz7B33//jVatWqFWrVoICAjA4MGDi+wSLYpeJDv79+/Hw4cPsW7dOqxbt67A/jVr1khjP9555x0sW7YMe/bsQZ8+fbBhwwbUq1cPTZo0kcorlUo0atQI8+fPL/R8L765nv/vIF9+f7Wfnx9+/PFHODs7w9jYGKGhoWoPrMqPCQDeffddafDnixo3bqz2cV+msOc1YMAAHDt2DFOmTEHTpk1haWkJpVKJbt26FRgACTz7MizMy75ki6LJYxXlVV/uwLMBmObm5tiwYQN8fX2xYcMGGBgYoH///lIZpVIpDcgsLO4XxyMUVtdJSUno2LEjrK2tMWvWLHh5ecHU1BRnzpzBJ598Umh9v4q6721SpVQq8frrr2Pq1KmF7s//sldHcd5zxVXc+KpWrQrg5YnC06dPi0zWn9e+fXt8/fXXyMzMxOHDh/H555/D1tYW3t7eOHz4sDT+rLTJTmk//7r2faauos5d2PbSxKPJ51jUe7uwCRZFqV+/PqKjo7Fr1y7s3bsXmzdvxo8//ogvv/wSM2fOLPZx9CLZWbNmDRwcHLBkyZIC+7Zs2YKtW7fip59+gpmZGfz8/ODs7Iz169ejffv22L9/Pz7//HOVx3h5eeHcuXPo3Llzib+INm/eDFNTU+zbt09lEGVoaKhKuRo1akCpVOL27dsqXywvjn7PH9mfl5dXqnVLrl+/XmDbtWvXYG5uXuR/g/mePn2Kf/75BzNnzsSXX3750mMWl5eXF5RKJS5fvqzSRaFJXl5e2LdvHxITE4v8Tzv/dbh+/Trq168vbX/06BGSkpJQo0YNaZuFhQXeeOMNbNy4EfPnz8f69evRoUMHuLi4qJxTCAFPT88S/QACz2YsJCQkYMuWLfDz85O2P996mR878Ow9k9+NCzyb6XPnzh2VJLg07+3880RHR0vdXvmio6NV6qi47O3tYW1tjYsXL77y3IXNvCnObJxXyY/7+vXrKl0K8fHxBZIBLy8vpKamvvIzqMn3XFnE5+7uDjMzswLvpXy5ubmIiYmRBhi/TIcOHZCdnY0//vgDDx48kJIaPz8/KdmpU6eOlPQURZNJX3GUxfdZ/mt68+ZNlVab6OholXL29vYwNzcvsB0Arl69CgMDA53/xyM2NhZpaWkqrTvXrl0DAKlFOb9V6MXV059vKc/3stffwsICAwcOxMCBA5GdnY2+ffvi66+/xrRp01S61F+mwo/ZycjIwJYtW/DGG2/g7bffLnALCgpCSkqKNA3SwMAAb7/9Nnbu3Inff/8dubm5Kl1YwLNs/8GDB/j5558LPV9x1mgwNDSETCZTyWDv3LlTYLZL165dATxb+fl5ixcvLnC8fv36YfPmzYX+MMTHx78yJgA4fvy4Sl90TEwMtm/fjoCAgCIz+udjAApm+KVZ9bJPnz4wMDDArFmzCvwnpan/lvr16wchRKH/BeSfo0ePHgAKPpf8FpAXZ1MMHDgQsbGx+OWXX3Du3LkC76G+ffvC0NAQM2fOLPA8hBAFprYWprD6zs7OLvBeadGiBapWrYqff/4Zubm50vY1a9YU+DEszXu7RYsWcHBwwE8//aTSLbtnzx5cuXKl0Bknr2JgYIA+ffpg586dKlOy8+U/965du+L48eOIioqS9iUmJmpkLa0uXbrA2NgYixcvVqnrwt7XAwYMwPHjx7Fv374C+5KSkqT61+R7riziMzY2RosWLQqtcwC4fPkyMjMz4evrW+j+57Vu3RrGxsaYO3cu7OzspK6XDh06ICIiAuHh4cVq1bGwsCi066islMX3Wffu3QEAixYteukxDQ0NERAQgO3bt6tMy3706BHWrl2L9u3bw9rausRxlIfc3FwsW7ZMup+dnY1ly5bB3t5eGhea3533/PImeXl5hQ7FKOr1f/G70sTEBA0aNIAQAjk5OcWOt8K37OzYsQMpKSlF/gfSpk0baYHB/B+kgQMHYvHixZg+fToaNWqk8l8VALz33nvYsGED3n//fRw4cADt2rVDXl4erl69ig0bNmDfvn2FDqZ8Xs+ePTF//nx069YNgwcPxuPHj7FkyRLUqlUL58+fl8r5+PigX79++OGHH5CQkCBNPc/PkJ/PdufMmYMDBw6gdevWGDNmDBo0aIDExEScOXMGf//9NxITE19ZX97e3ujatavK1HMAxWoOtLa2hp+fH+bNm4ecnBxUr14df/31V5H/HRZHrVq18Pnnn2P27Nno0KED+vbtC7lcjlOnTsHFxQUhISElPna+Tp064b333sOiRYtw/fp1qYn68OHD6NSpE4KCgtCkSRMMGzYMy5cvl7qPTp48iV9//RV9+vRRaTEBnv1QWVlZ4eOPP5YS0ed5eXnhq6++wrRp06Rp4FZWVrh9+za2bt2KsWPHqqzrVBhfX19UqVIFw4YNQ3BwMGQyGX7//fcCX84mJiaYMWMGJkyYgNdeew0DBgzAnTt3sGrVKnh5eam8h0rz3s7/QRsxYgQ6duyIQYMGSVPPPTw8MGnSJHVeFsk333yDv/76Cx07dpSmwz98+BAbN27EkSNHYGtri6lTp2L16tV4/fXXMWHCBGnqubu7OxITE0vVKmBvb4+PP/4YISEheOONN9CjRw+cPXsWe/bsQbVq1VTKTpkyBTt27MAbb7whLXuQlpaGCxcuYNOmTbhz5w6qVaum0fdcWcQHAG+++SY+//xzadr488LCwmBubo7XX3/9lfVnbm4OHx8fRERESGvsAM9adtLS0pCWllasZMfHxwfr16/HRx99hJYtW8LS0rLQBQ81pSy+z5o2bYpBgwbhxx9/RHJyMnx9ffHPP/8U2gL51VdfISwsDO3bt8f48eNhZGSEZcuWISsrC/PmzSvNUysXLi4umDt3Lu7cuYM6depg/fr1iIqKwvLly6UxiQ0bNkSbNm0wbdo0qZVz3bp1Kv+U5Svq9Q8ICICTkxPatWsHR0dHXLlyBf/3f/+Hnj17wsrKqvgBl3o+l5b16tVLmJqairS0tCLLDB8+XBgbG0tTtpVKpXBzcxMAxFdffVXoY7Kzs8XcuXNFw4YNhVwuF1WqVBE+Pj5i5syZIjk5WSoHQAQGBhZ6jBUrVojatWsLuVwu6tWrJ0JDQ6UpjM9LS0sTgYGBws7OTlhaWoo+ffqI6OhoAUDMmTNHpeyjR49EYGCgcHNzE8bGxsLJyUl07txZLF++/JV1lR/r6tWrpbiaNWumMv1PiP+mWcbHxxc4xv3798Vbb70lbG1thY2Njejfv7+IjY0tMK2yqGMUNRVx5cqVolmzZlJdd+zYUVpGQIhnU8979uxZIJ7iTjPOzc0V3377rahXr54wMTER9vb2onv37iIyMlIqk5OTI2bOnCk8PT2FsbGxcHNzE9OmTZOmeL9oyJAhAoDo0qVLkefdvHmzaN++vbCwsBAWFhaiXr16IjAwUERHR6s8h6KmVh49elS0adNGmJmZCRcXFzF16lRpCYUXX7dFixaJGjVqCLlcLlq1aiWOHj0qfHx8RLdu3VTKFfe9XZT169dLr5WdnZ0YMmSIuH//vkoZdaaeC/FsCYShQ4cKe3t7IZfLRc2aNUVgYKDKFNizZ8+KDh06CLlcLlxdXUVISIhYtGiRACDi4uKkcupOPRdCiLy8PDFz5kzh7OwszMzMhL+/v7h48aKoUaOGytRuIYRISUkR06ZNE7Vq1RImJiaiWrVqwtfXV3z33XfSlFshNPueK4v4Hj16JIyMjMTvv/9eoD5at24t3n333WLX35QpUwQAMXfuXJXttWrVEgBUplcLUfjU49TUVDF48GBha2srAEjTkPPLbty4UeUYt2/fFgBEaGjoS2Mr7++zjIwMERwcLKpWrSosLCxEr169RExMTIFjCiHEmTNnRNeuXYWlpaUwNzcXnTp1EseOHSv0HC9+loqKadiwYcLCwuKldSJE0VPPi1PP+d9Zp0+fFm3bthWmpqaiRo0a4v/+7/8KnOfmzZuiS5cuQi6XC0dHR/HZZ5+JsLCwYr/+y5YtE35+fqJq1apCLpcLLy8vMWXKlGJ9Vz1PJkQ5jKwitUVFRaFZs2ZYvXq1RqbXUuWjVCphb2+Pvn37FtptpQ8mTpyIZcuWITU19ZXdsFTQqFGjcO3aNWk2FfDsu6d58+Y4c+ZMmY2jo4rN398fT548eeVYO11S4cfs6IOMjIwC23744QcYGBioDEwlKkpmZmaB7q3ffvsNiYmJal86QVe9+DlJSEjA77//jvbt2zPRKaHp06fj1KlTOHr0qLRtzpw5ePvtt5nokF6p8GN29MG8efMQGRmJTp06wcjICHv27MGePXswduxYnR+RT7ohIiICkyZNQv/+/VG1alWcOXMGK1asgLe3t8qU+Iqsbdu28Pf3R/369fHo0SOsWLECCoUCX3zxRZGPiY+Pf+k0VxMTk5euhaPv3N3dC1y4uLDlO4gqOiY7OsDX1xdhYWGYPXs2UlNT4e7ujhkzZhSYEk9UFA8PD7i5uWHRokXSQMChQ4dizpw5enNl4x49emDTpk1Yvnw5ZDIZmjdvjhUrVry09bNly5aFTnPN17FjR+lihESkvzhmh4j01tGjRwvtJs5XpUoVlcunEJF+YrJDREREeo0DlImIiEiv6f2YHaVSidjYWFhZWZX7cuRERERUMkIIpKSkwMXFBQYGpWub0ftkJzY2ljOaiIiIKqiYmBi4urqW6hh6n+zkLycdExOj89caISIiomcUCgXc3NzUuyxEEfQ+2cnvurK2tmayQ0REVMFoYggKBygTERGRXmOyQ0RERHqNyQ4RERHpNSY7REREpNeY7BAREZFeY7JDREREeo3JDhEREek1JjtERESk17Sa7ISEhKBly5awsrKCg4MD+vTpg+joaGn/nTt3IJPJCr1t3LhRi5ETERFRRaHVZCc8PByBgYGIiIhAWFgYcnJyEBAQgLS0NACAm5sbHj58qHKbOXMmLC0t0b17d22GTkRERBWETAghtB1Evvj4eDg4OCA8PBx+fn6FlmnWrBmaN2+OFStWFOuYCoUCNjY2SE5O5uUiiIiIKghN/n7r1LWxkpOTAQB2dnaF7o+MjERUVBSWLFlS5DGysrKQlZUl3VcoFJoNkoiIiCoUnUl2lEolJk6ciHbt2sHb27vQMitWrED9+vXh6+tb5HFCQkIwc+bMsgqTiIjKSGZOHp6kZr26IOkcW3MTWMp1JqUoQGe6sT744APs2bMHR44cgaura4H9GRkZcHZ2xhdffIHJkycXeZzCWnbc3NzYjUVEpIOUSoGIWwnYfOYB9l58iLTsPG2HRCXwzVuNMLi1u0aPqXfdWEFBQdi1axcOHTpUaKIDAJs2bUJ6ejqGDh360mPJ5XLI5fKyCJOIqELKys1DeHQ87iakazsUFU9Ss7DzXCxikzOlbSaGBpDJtBgUlYihji9ko9VkRwiBCRMmYOvWrTh48CA8PT2LLLtixQr07t0b9vb25RghEVHFJITA2ZgkbDlzHzvPPURyRo62QyqSlakR3mjsgn7Nq8OnRhXImO2Qhmk12QkMDMTatWuxfft2WFlZIS4uDgBgY2MDMzMzqdyNGzdw6NAh7N69W1uhElEZu/80HdvOPsCha0+QnafUdjgVXkJaFmISM6T7jtZytKlZFYY6lEgYGxrAr449Otd3gKmxobbDIT2m1WRn6dKlAAB/f3+V7aGhoRg+fLh0f+XKlXB1dUVAQEA5RkdEpZGalQvFK1oT8pQCx28lYMuZ+4i4lVhOkVUeZsaG6O7thLeaV4evVzUYGuhOokNUnnRmgHJZ4To7ROUnKzcP+688xuYzD3Aw+jFylcX/epHJgLY1q6J3ExdUs+S4u9IyNjJAixpVYKHDM2SIXkbvBigTUdm78yQNh67HIzu3bLqIbj1Jw5/nVceGmBgaAK9oTKhhZ44+zaqjT7PqqG5r9vLCREQlwGSHSI8lpWdj1/mH2Hr2ASLvPi2XczpZm+Kt5tXRt1l11Ha0KpdzEhG9DJMdIj2UlJ6NWbsuY9e5h9JgXwMZ0NarKuzLqIvI0tQI3b2dnw2C5dgQItIhTHaI9MypO4n48I+z0tol9Z2t0bdZdbzZ1AUO1qZajo6IqPwx2SHSE3lKgSUHbuCHv69BKQDPahb4rn8T+NSoou3QiIi0iskOkR64GqfAjB2XpOnbfZtVx6w+3jp9rRoiovLCb0KiCupxSiZ2RMViy5kHuPxQAQAwNzHE7De90c+n8MuuEBFVRkx2iLRACIGomCRsPfsAl2IVaj8+J0+JS7EK5P27jo2xoQyv1XPA1G714GVvqelwiYgqNCY7ROUoJvHZJRG2nn2AW0/SSn28Zu626NvcFW80ckYVCxMNREhEpH+Y7BCVsZTMHOy5EIfNZ+7jxO3/LolgZmyIbt5O6FTP4dnie2qq62QFz2oWmgyViEgvMdkhKiNn7z3FyqN38NelOGT9u2px/iUR+jZ3RTdvJw4gJiIqB/ymJSoDx24+wfCVp6QF/Wo5WKJv8+ro07Q6XHhJBCKicsVkh0jDLj5IxtjfIpGdp0THOvb4OKAuvKtbQybjqsJERNrAZIdIg+4mpGF46CmkZuWiTU07LHvPB6bGhtoOi4ioUlN/VCQRFepxSibeW3EST1KzUN/ZGsuHtmCiQ0SkA5jsEGlATGI6hq88hXuJ6XCzM8OvI1rC2tRY22ERERHYjUVUYoVNKa9maYLfR7bmBTeJiHQIkx2iVzh+MwH/d+A6cvKEtE2pFLjwIFllSrmvV1V88UYDeHDtGyIincJkh+glElKzMOGPM3iSml3ofi97C/TzceWUciIiHcZkh6gIQgj8b9tFPEnNRh1HS0zsUkdlv7udORq6cEo5EZGuY7JDVIQd52Kx52IcjAxkmD+gKbyr22g7JCIiKgHOxiIqRFxyJr7YdhEAMOG12kx0iIgqMCY7RC8QQuCTzeehyMxFY1cbjO/kpe2QiIioFJjsEL3gj5MxCL8WDxMjA3zfvwmMS3BFciIi0h38Fid6zp/nH2L2rssAgKld66K2o5WWIyIiotLiAGUiABnZeZi16zL+OHkPAOBXxx4j2nlqOSoiItIEJjtU6UXHpWDCH2dw7VEqZDJgvL8XJnapA0MDTiknItIHTHao0roZn4rNkfex4shtZOUqYW8lx4IBTdG+djVth0ZERBrEZIf0WnJ6DpTiv8s8ZObm4e/Lj7D5zANExSRJ2/3q2GP+gCaoZinXQpRERFSWmOyQXsrMycP4NWew/+rjIssYGsjQsY49+jV3RXdvJxiw24qISC8x2SG9k6cU+HDd2SITnYYu1ujb3BW9m7jA3ootOURE+o7JDumV/OtZ7bv0CCaGBlg1siVae1ZVKcOBx0RElQuTHdIrC8Ku4Y+T9yCTAYsGNYWvFwcbExFVdkx2qELIyM5Ddq7ypWW2nL2PRftvAAC+6uONbt7O5REaERHpOCY7pNOycvMwb280Vh27gzylePUDAEzqUgdDWtco48iIiKiiYLJDOuv2kzRM+OMMLj5QFKu8kYEMozvURHDnWmUcGRERVSRMdkgnbT17H//behFp2XmoYm6Mb99uAv+69i99jEwm4+BjIiIqgMkO6QylUiDidgJWR9zF7gtxAIDWnnZY+E4zONmYajk6IiKqqJjskNbdeJyKLWfuY9vZB4hNzgQAGMiADzvXQdBrtdhaQ0REpcJkh7Tqp/CbmLPnqnTfytQIbzR2xuBWNdDI1UaLkRERkb5gskNacy4mCd/uiwYAdKprj7d93NC5vgNMjQ21HBkREekTJjukFZk5eZi88RzylAJvNHbG/w1uru2QiIhITxloOwCqnL7bF40bj1NhbyXH7De9tR0OERHpMSY7VO5O3ErAiqO3AQBz+zVCFQsTLUdERET6jMkOlavUrFx8vOkchAAGtnDDa/UctR0SERHpOa0mOyEhIWjZsiWsrKzg4OCAPn36IDo6ukC548eP47XXXoOFhQWsra3h5+eHjIwMLURMpfXN7iuIScxAdVsz/O+N+toOh4iIKgGtJjvh4eEIDAxEREQEwsLCkJOTg4CAAKSlpUlljh8/jm7duiEgIAAnT57EqVOnEBQUBAMDNkpVNOtO3sPaE/cAAN/2bwwrU2MtR0RERJWBTAhRvKsrloP4+Hg4ODggPDwcfn5+AIA2bdrg9ddfx+zZs0t0TIVCARsbGyQnJ8Pa2lqT4ZIa9l6Mw/g1kVAKIPi1WvgooK62QyIiIh2myd9vnWoeSU5OBgDY2dkBAB4/fowTJ07AwcEBvr6+cHR0RMeOHXHkyJEij5GVlQWFQqFyI+2KuJWA4HVnoRTAOy3dMOn1OtoOiYiIKhGdSXaUSiUmTpyIdu3awdv72VTkW7duAQBmzJiBMWPGYO/evWjevDk6d+6M69evF3qckJAQ2NjYSDc3N7dyew5U0OVYBcb8ehrZuUoENHDEV328IZPx8g9ERFR+dCbZCQwMxMWLF7Fu3Tppm1KpBACMGzcOI0aMQLNmzbBgwQLUrVsXK1euLPQ406ZNQ3JysnSLiYkpl/ipoJjEdAwLPYmUrFy08rDDokHNYGSoM285IiKqJHRiBeWgoCDs2rULhw4dgqurq7Td2dkZANCgQQOV8vXr18e9e/cKPZZcLodcLi+7YKlYnqRm4b0VJxCfkoV6Tlb4eVgLXgaCiIi0Qqv/ZgshEBQUhK1bt2L//v3w9PRU2e/h4QEXF5cC09GvXbuGGjVqlGeopIbUrFyMCD2FOwnpcK1iht9GtoKNGWdeERGRdmi1ZScwMBBr167F9u3bYWVlhbi4OACAjY0NzMzMIJPJMGXKFEyfPh1NmjRB06ZN8euvv+Lq1avYtGmTNkOnImTl5mHc76dx4UEyqlqY4PdRreFgbartsIiIqBLTarKzdOlSAIC/v7/K9tDQUAwfPhwAMHHiRGRmZmLSpElITExEkyZNEBYWBi8vr3KOll4lTynw0YZzOHojARYmhlg1ohU8q1loOywiIqrkdGqdnbLAdXbKhxAC03dcwm/H78LYUIbQ4a3QvnY1bYdFREQVlN6us0MV1+L9N/Db8buQyYAFA5sy0SEiIp3BZIdKbc2Ju5gfdg0AMKNXQ7zR2EXLEREREf2HyQ6Vyt6LD/HFtosAgAmv1cIwXw/tBkRERPQCJjtUYsduPkHwH1FQCmBQK3d8xMtAEBGRDmKyQyVy+k4ixv4Wiew8Jbo25GUgiIhId+nECspUMTxOycSOqFhsOfMAlx8+u8BqK087LHynGQwNmOgQEZFuYrJDLyWEQMStRPx8+BbCr8UjT/lspQJjQxm6NnTC12814mUgiIhIpzHZoUIJIXD4+hMs3n8dp+48lbY3c7dF3+aueKORM6pYmGgxQiIiouJhskMqsnLzsP/KYyw7dAtRMUkAABNDAwxs6YYR7TxQ095SuwESERGpickOQQiBszFJ2HLmPnaee4jkjBwAgKmxAQa3qoFxHWvCkde3IiKiCorJTiV38UEygtedxa34NGmbo7Uc/Zq7YkQ7T9hbybUYHRERUekx2anE0rNzEbT2DO4kpMPM2BDdvZ3Qt7kr2npV5ewqIiLSG0x2KrG5e67iTkI6nG1MsTu4AwccExGRXuKigpXU0RtP8OvxuwCAuf0aM9EhIiK9xWSnElJk5mDKxnMAgHfbuMOvjr2WIyIiIio7THYqoVk7LyM2ORM1qprjsx71tR0OERFRmWKyU8mEXX6ETZH3IZMB3/dvAnMTDtsiIiL9xmSnEklMy8a0LRcAAGP9aqKFh52WIyIiIip7THYqCSEE/rftAp6kZqGOoyUmdamj7ZCIiIjKBZOdSmLHuVjsvhAHIwMZ5g9oyot3EhFRpcFkpxJ4pMjEl9svAQAmvFYb3tVttBwRERFR+WGyo+eEEJi66TySM3LQ2NUG4zt5aTskIiKicsVkR8/9cTIG4dfiYWJkgPkDmsDYkC85ERFVLvzl02PnYpLw1Z+XAQBTu9ZFLQcrLUdERERU/rjIip55nJKJHVGx2HzmAa48VAAAWnnaYWQ7Ty1HRkREpB1MdvREVm4epmw8j13nY6EUz7YZG8rQpb4jZvRuCANexZyIiCopJjt64scDN7HjXCwAoJm7Lfo2d8UbjZx5gU8iIqr0mOzogRuPU/DjwRsAgIXvNMWbTatrOSIiIiLdwQHKFZxSKfDZlovIyRPoVNcevZu4aDskIiIincJkp4LbcDoGJ+8kwszYELP7eEMm49gcIiKi5zHZqcDiU7Lwze4rAIDJAXXgWsVcyxERERHpHiY7FdjsXZehyMyFd3VrDPf10HY4REREOokDlCsgIQR2nIvFjnOxMJABIW81hhFXRiYiIioUk50KRAiBg9HxWLT/Os7eSwIAjGjniUauvLAnERFRUZjsVBAHoh9j/l/XcOFBMgBAbmSAIa1rYErXulqOjIiISLcx2akAzsUkYeSqUxACMDM2xHtta2B0B084WJlqOzQiIiKdx2SnAthzMQ5CAL5eVbF4UDNUtZRrOyQiIqIKg6NaK4Dwa/EAgAEt3JjoEBERqYnJjo57pMjElYcKyGRAh9rVtB0OERFRhcNkR8cd+rdVp3F1G7bqEBERlQCTHR138N9kp2Mdey1HQkREVDEx2dFhuXlKHLn+BADQsa6DlqMhIiKqmJjs6LBz95ORnJEDGzNjNOHCgURERCXCZEeHhUc/BgC0r12Nl4MgIiIqIa3+goaEhKBly5awsrKCg4MD+vTpg+joaJUy/v7+kMlkKrf3339fSxGXr3CO1yEiIio1rSY74eHhCAwMREREBMLCwpCTk4OAgACkpaWplBszZgwePnwo3ebNm6eliMtPQmoWzv97aQh/JjtEREQlptUVlPfu3atyf9WqVXBwcEBkZCT8/Pyk7ebm5nBycirv8LTqyI0nEAKo72wNB2teFoKIiKikipXsLFq0qNgHDA4OLnEwycnPWjLs7OxUtq9ZswarV6+Gk5MTevXqhS+++ALm5uYlPk9FcDCaXVhERESaUKxkZ8GCBSr34+PjkZ6eDltbWwBAUlISzM3N4eDgUOJkR6lUYuLEiWjXrh28vb2l7YMHD0aNGjXg4uKC8+fP45NPPkF0dDS2bNlS6HGysrKQlZUl3VcoFCWKR5uUSiEtJuhfl8kOERFRaRQr2bl9+7b099q1a/Hjjz9ixYoVqFu3LgAgOjoaY8aMwbhx40ocSGBgIC5evIgjR46obB87dqz0d6NGjeDs7IzOnTvj5s2b8PLyKnCckJAQzJw5s8Rx6IJLsQokpGXDUm6E5u5VtB0OERFRhab2AOUvvvgCixcvlhIdAKhbty4WLFiA//3vfyUKIigoCLt27cKBAwfg6ur60rKtW7cGANy4caPQ/dOmTUNycrJ0i4mJKVFM2hR+7dmUc1+vqjAx4pRzIiKi0lB7gPLDhw+Rm5tbYHteXh4ePXqk1rGEEJgwYQK2bt2KgwcPwtPT85WPiYqKAgA4OzsXul8ul0Mur9jXkPr7yrNkx5+rJhMREZWa2s0GnTt3xrhx43DmzBlpW2RkJD744AN06dJFrWMFBgZi9erVWLt2LaysrBAXF4e4uDhkZGQAAG7evInZs2cjMjISd+7cwY4dOzB06FD4+fmhcePG6oZeIVy4n4yomCQYGcjQpT6THSIiotJSO9lZuXIlnJyc0KJFC6kVpVWrVnB0dMQvv/yi1rGWLl2K5ORk+Pv7w9nZWbqtX78eAGBiYoK///4bAQEBqFevHiZPnox+/fph586d6oZdYaw6dgcA0LOxM6ecExERaYDa3Vj29vbYvXs3rl27hqtXrwIA6tWrhzp16qh9ciHES/e7ubkhPDxc7eNWVPEpWdh5LhYAMNzXQ7vBEBER6YkSLyro4eEBIQS8vLxgZKTVtQn1xh8n7yE7T4mmbrZoxllYREREGqF2N1Z6ejpGjRoFc3NzNGzYEPfu3QMATJgwAXPmzNF4gJVFdq4SqyPuAgBGtPPQbjBERER6RO1kZ9q0aTh37hwOHjwIU9P/xpR06dJFGmtD6ttz8SEep2TBwUqO7t6FzzQjIiIi9and/7Rt2zasX78ebdq0gUwmk7Y3bNgQN2/e1GhwlUno0TsAgHfb1ODaOkRERBqk9q9qfHw8HBwKTolOS0tTSX6o+M7ee4qomCSYGBpgUCt3bYdDRESkV9ROdlq0aIE///xTup+f4Pzyyy9o27at5iKrRH79d7p5ryYusLeq2AsiEhER6Rq1u7G++eYbdO/eHZcvX0Zubi4WLlyIy5cv49ixY5VqmrimPFZk4s8LDwFwujkREVFZULtlp3379jh37hxyc3PRqFEj/PXXX3BwcMDx48fh4+NTFjHqtX2X4pCTJ9Dc3RaNXG20HQ4REZHeUatlJycnB+PGjcMXX3yBn3/+uaxiqlSiYpIBAO1r22s5EiIiIv2kVsuOsbExNm/eXFaxVErn7icBAJq6sVWHiIioLKjdjdWnTx9s27atDEKpfBSZObgZnwoAaOxqq91giIiI9JTaA5Rr166NWbNm4ejRo/Dx8YGFhYXK/uDgYI0Fp+8u3k+GEIBrFTNUs+QsLCIiorKgdrKzYsUK2NraIjIyEpGRkSr7ZDIZkx01nLv/bLxOE7bqEBERlRm1k53bt2+XRRyV0rmYJABAE47XISIiKjMlvi5BdnY2oqOjkZubq8l4KpX8wcls2SEiIio7vOq5ljxWZOJhciYMZIB3dbbsEBERlRVe9VxL8sfr1HawgoVc7d5EIiIiKiZe9VxLOF6HiIiofPCq51oijddxs9VqHERERPqOVz3XAiHEfy07HJxMRERUpnjVcy24k5AORWYuTIwMUNfJStvhEBER6bUSXfU8KiqKVz0vhfxWHW8Xaxgblnj2PxERERVDiaYBeXl58arnpRD1b7LD62ERERGVPbWbFbp06YJVq1ZBoVCURTyVwnnpSue2Wo2DiIioMlA72WnYsCGmTZsGJycn9O/fH9u3b0dOTk5ZxKaXcvKUuBj7LFHkTCwiIqKyp3ays3DhQjx48ADbtm2DhYUFhg4dCkdHR4wdO5YDlIshOi4F2blKWJsawaOqubbDISIi0nslGh1rYGCAgIAArFq1Co8ePcKyZctw8uRJvPbaa5qOT+9ESYsJ2nJdIiIionJQqusUxMXFYd26dVi9ejXOnz+PVq1aaSouvXWeF/8kIiIqV2q37CgUCoSGhuL111+Hm5sbli5dit69e+P69euIiIgoixj1yrmYZ9fE4ngdIiKi8qF2y46joyOqVKmCgQMHIiQkBC1atCiLuPRSbp4SN+NTAQANXKy1HA0REVHloHays2PHDnTu3BkGBlwMT12xSZnIVQqYGBnA2dr01Q8gIiKiUlM72Xn99dcBPLsgaHR0NACgbt26sLe312xkeuheYjoAwK2KGQwMODiZiIioPKjdPJOeno6RI0fC2dkZfn5+8PPzg4uLC0aNGoX09PSyiFFv3E1MAwDUqGqh5UiIiIgqD7WTnUmTJiE8PBw7d+5EUlISkpKSsH37doSHh2Py5MllEaPeuJfwLBl0t+P6OkREROVF7W6szZs3Y9OmTfD395e29ejRA2ZmZhgwYACWLl2qyfj0yt1/k50aXEyQiIio3JSoG8vR0bHAdgcHB3ZjvcLdRCY7RERE5U3tZKdt27aYPn06MjMzpW0ZGRmYOXMm2rZtq9Hg9IkQAvcSno3ZcbfjmB0iIqLyonY31sKFC9G1a1e4urqiSZMmAIBz587B1NQU+/bt03iA+iIhLRtp2XmQyQA3OzNth0NERFRpqJ3seHt74/r161izZg2uXr0KABg0aBCGDBkCMzP+iBclf7yOs7Up5EaGWo6GiIio8ijRtbHMzc0xZswYTcei1+79O+3cneN1iIiIypXaY3ZCQkKwcuXKAttXrlyJuXPnaiQofSTNxOJ4HSIionKldrKzbNky1KtXr8D2hg0b4qefftJIUPpIWmOHLTtERETlSu1kJy4uDs7OzgW229vb4+HDhxoJSh/lTzvngoJERETlS+1kx83NDUePHi2w/ejRo3BxcdFIUPqICwoSERFph9oDlMeMGYOJEyciJycHr732GgDgn3/+wdSpU3m5iCKkZeXiSWoWAI7ZISIiKm9qt+xMmTIFo0aNwvjx41GzZk3UrFkTEyZMQHBwMKZNm6bWsUJCQtCyZUtYWVnBwcEBffr0ka6k/iIhBLp37w6ZTIZt27apG7ZW5V/t3MbMGDbmxlqOhoiIqHJRO9mRyWSYO3cu4uPjERERgXPnziExMRFffvml2icPDw9HYGAgIiIiEBYWhpycHAQEBCAtLa1A2R9++AEymUztc+gCdmERERFpT4nW2QEAS0tLtGzZslQn37t3r8r9VatWwcHBAZGRkfDz85O2R0VF4fvvv8fp06cLHRyt62I4OJmIiEhrSpzslIXk5GQAgJ2dnbQtPT0dgwcPxpIlS+Dk5PTKY2RlZSErK0u6r1AoNB+omu7+u6AgW3aIiIjKn9rdWGVFqVRi4sSJaNeuHby9vaXtkyZNgq+vL958881iHSckJAQ2NjbSzc3NraxCLjYuKEhERKQ9OtOyExgYiIsXL+LIkSPSth07dmD//v04e/ZssY8zbdo0fPTRR9J9hUKh9YQnf4AyFxQkIiIqfzrRshMUFIRdu3bhwIEDcHV1lbbv378fN2/ehK2tLYyMjGBk9Cw369evH/z9/Qs9llwuh7W1tcpNm3LzlHjwNAMAu7GIiIi0Qe1k59dff8Wff/4p3Z86dSpsbW3h6+uLu3fvqnUsIQSCgoKwdetW7N+/H56enir7P/30U5w/fx5RUVHSDQAWLFiA0NBQdUPXitikTOQqBUyMDOBoZartcIiIiCodtZOdb775BmZmZgCA48ePY8mSJZg3bx6qVauGSZMmqXWswMBArF69GmvXroWVlRXi4uIQFxeHjIxnLSFOTk7w9vZWuQGAu7t7gcRIV+UPTna3M4eBQcWcOk9ERFSRqT1mJyYmBrVq1QIAbNu2Df369cPYsWPRrl27IruWirJ06VIAKPC40NBQDB8+XN3QdNJ/g5PZhUVERKQNaic7lpaWSEhIgLu7O/766y9pMLCpqanUIlNcQgh1T1+ix2gTBycTERFpl9rJzuuvv47Ro0ejWbNmuHbtGnr06AEAuHTpEjw8PDQdX4V3N+HfNXbYskNERKQVao/ZWbJkCXx9fREfH4/NmzejatWqAIDIyEgMGjRI4wFWdP9dKoJr7BAREWmDWi07ubm5WLRoET755BOVKeIAMHPmTI0Gpg+EEFI3lhtbdoiIiLRCrZYdIyMjzJs3D7m5uWUVj155kpqN9Ow8yGSAm52ZtsMhIiKqlNTuxurcuTPCw8PLIha9c+/faefO1qaQGxlqORoiIqLKSe0Byt27d8enn36KCxcuwMfHBxYWqmNRevfurbHgKrr88TqciUVERKQ9aic748ePBwDMnz+/wD6ZTIa8vLzSR6Un7v97mQh3jtchIiLSGrWTHaVSWRZx6KXHKZkAACdrXiaCiIhIW3TiQqD6Kj4lCwBgbyXXciRERESVV7FadhYtWoSxY8fC1NQUixYtemnZ4OBgjQSmD5jsEBERaV+xkp0FCxZgyJAhMDU1xYIFC4osJ5PJmOw8Jz6VyQ4REZG2FSvZuX37dqF/U9GEEP+17FhyzA4REZG2cMxOGUnNykVmzrPB3NWsTLQcDRERUeWl9mwsALh//z527NiBe/fuITs7W2VfYVPSK6P8Vh1LuRHMTUpUzURERKQBav8K//PPP+jduzdq1qyJq1evwtvbG3fu3IEQAs2bNy+LGCuk/GTHgeN1iIiItErtbqxp06bh448/xoULF2BqaorNmzcjJiYGHTt2RP/+/csixgopf3ByNSY7REREWqV2snPlyhUMHToUwLMLg2ZkZMDS0hKzZs3C3LlzNR5gRfVYwZlYREREukDtZMfCwkIap+Ps7IybN29K+548eaK5yCo4adq5JZMdIiIibVJ7zE6bNm1w5MgR1K9fHz169MDkyZNx4cIFbNmyBW3atCmLGCskLihIRESkG9ROdubPn4/U1FQAwMyZM5Gamor169ejdu3anIn1HCY7REREukHtZKdmzZrS3xYWFvjpp580GpC+YLJDRESkG0q8AMzp06dx5coVAECDBg3g4+OjsaD0AcfsEBER6Qa1k5379+9j0KBBOHr0KGxtbQEASUlJ8PX1xbp16+Dq6qrpGCucPKVAQirX2SEiItIFas/GGj16NHJycnDlyhUkJiYiMTERV65cgVKpxOjRo8sixgonMS0bSgHIZICdBS8VQUREpE1qt+yEh4fj2LFjqFu3rrStbt26WLx4MTp06KDR4Cqq/PE6VS3kMDLk5ceIiIi0Se1fYjc3N+Tk5BTYnpeXBxcXF40EVdFJ43XYhUVERKR1aic73377LSZMmIDTp09L206fPo0PP/wQ3333nUaDq6g4E4uIiEh3qN2NNXz4cKSnp6N169YwMnr28NzcXBgZGWHkyJEYOXKkVDYxMVFzkVYgUrLDmVhERERap3ay88MPP5RBGPrlcUomALbsEBER6QK1k51hw4aVRRx6hd1YREREuoNThcoAkx0iIiLdwWSnDHD1ZCIiIt3BZKcMsGWHiIhIdzDZ0bDMnDykZOYCYLJDRESkC0qc7Ny4cQP79u1DRkYGAEAIobGgKrL8Vh0TIwNYm5b4OqtERESkIWonOwkJCejSpQvq1KmDHj164OHDhwCAUaNGYfLkyRoPsKKJf+4CoDKZTMvREBERkdrJzqRJk2BkZIR79+7B3Nxc2j5w4EDs3btXo8FVRByvQ0REpFvU7mf566+/sG/fPri6uqpsr127Nu7evauxwCoqrp5MRESkW9Ru2UlLS1Np0cmXmJgIuZw/8GzZISIi0i1qJzsdOnTAb7/9Jt2XyWRQKpWYN28eOnXqpNHgKqLHTHaIiIh0itrdWPPmzUPnzp1x+vRpZGdnY+rUqbh06RISExNx9OjRsoixQmHLDhERkW5Ru2XH29sb165dQ/v27fHmm28iLS0Nffv2xdmzZ+Hl5VUWMVYoXD2ZiIhIt5RoIRgbGxt8/vnnmo5FLzxhyw4REZFOKVGyk5SUhJMnT+Lx48dQKpUq+4YOHaqRwCoiIQS7sYiIiHSM2snOzp07MWTIEKSmpsLa2lpl4TyZTKZWshMSEoItW7bg6tWrMDMzg6+vL+bOnYu6detKZcaNG4e///4bsbGxsLS0lMrUq1dP3dDLnCIjF9l5z5K/auzGIiIi0glqj9mZPHkyRo4cidTUVCQlJeHp06fSLTExUa1jhYeHIzAwEBEREQgLC0NOTg4CAgKQlpYmlfHx8UFoaCiuXLmCffv2QQiBgIAA5OXlqRt6mYtPzQQAWJsawdTYUMvREBEREQDIhJoXtbKwsMCFCxdQs2ZNjQcTHx8PBwcHhIeHw8/Pr9Ay58+fR5MmTXDjxo1iDYhWKBSwsbFBcnIyrK2tNR2yimM3n2DwzydQy8ESf3/UsUzPRUREpM80+futdstO165dcfr06VKdtCjJyckAADs7u0L3p6WlITQ0FJ6ennBzcyuTGEqDqycTERHpHrXH7PTs2RNTpkzB5cuX0ahRIxgbG6vs7927d4kCUSqVmDhxItq1awdvb2+VfT/++COmTp2KtLQ01K1bF2FhYTAxMSn0OFlZWcjKypLuKxSKEsVTEhycTEREpHvUTnbGjBkDAJg1a1aBfTKZrMRjaQIDA3Hx4kUcOXKkwL4hQ4bg9ddfx8OHD/Hdd99hwIABOHr0KExNTQuUDQkJwcyZM0sUQ2lJa+ww2SEiItIZandjKZXKIm8lTXSCgoKwa9cuHDhwoMAFRoFn6/rUrl0bfn5+2LRpE65evYqtW7cWeqxp06YhOTlZusXExJQoppJgyw4REZHuKdE6O5oihMCECROwdetWHDx4EJ6ensV6jBBCpavqeXK5XGsXJOWYHSIiIt1TrGRn0aJFGDt2LExNTbFo0aKXlg0ODi72yQMDA7F27Vps374dVlZWiIuLA/CsJcfMzAy3bt3C+vXrERAQAHt7e9y/fx9z5syBmZkZevToUezzlBe27BAREemeYk099/T0xOnTp1G1atWXtr7IZDLcunWr+Cd/bkHC54WGhmL48OGIjY3F6NGjERkZiadPn8LR0RF+fn748ssvVRYefJnynHruMzsMCWnZ2PNhB9R3LttzERER6TNN/n4Xq2Xn9u3bhf5dWq/Ks1xcXLB7926Nna8s5eYpkZieDYCrJxMREekStQcoU+HSsvKQn7vZmhu/vDARERGVm2K17Hz00UfFPuD8+fNLHExFlp6TCwAwNpTB2JA5JBERka4oVrJz9uzZYh2sqDE4lUF69rNp97wmFhERkW4pVrJz4MCBso6jwsv4N9kxN2GyQ0REpEvY36IhGTn5yY5Wly4iIiKiFzDZ0RB2YxEREekmJjsawm4sIiIi3cRkR0My/p2NxWSHiIhItzDZ0RB2YxEREekmJjsawm4sIiIi3cRkR0OY7BAREekmJjsakv7v1HMzY049JyIi0iVMdjQkv2XHzIRVSkREpEv4y6wh/3VjsWWHiIhIlzDZ0ZD/urE4ZoeIiEiXMNnRkIzsZ+vsmHGAMhERkU5hsqMh/10bi8kOERGRLmGyoyH5iwqyG4uIiEi3MNnRkP9mYzHZISIi0iVMdjSE3VhERES6icmOhvzXjcWp50RERLqEyY6G8HIRREREuonJjgYIIZDOqedEREQ6icmOBmTnKaEUz/5mskNERKRbmOxoQH4XFsCp50RERLqGyY4G5A9ONjaUwdiQVUpERKRL+MusARm8LhYREZHOYrKjAbziORERke5isqMB6Vw9mYiISGcx2dEAdmMRERHpLiY7GpDx7xo7XFCQiIhI9zDZ0QB2YxEREekuJjsa8N91sZjsEBER6RomOxqQySueExER6SwmOxrwXzcWp54TERHpGiY7GsBuLCIiIt3FZEcD2I1FRESku5jsaED6v1PPORuLiIhI9zDZ0QB2YxEREekuJjsawG4sIiIi3cVkRwO4qCAREZHuYrKjAezGIiIi0l1MdjQgIzu/G4vr7BAREekaJjsaIF31nN1YREREOofJjgakZ3OAMhERka7SarITEhKCli1bwsrKCg4ODujTpw+io6Ol/YmJiZgwYQLq1q0LMzMzuLu7Izg4GMnJyVqMuqCM/HV2OGaHiIhI52g12QkPD0dgYCAiIiIQFhaGnJwcBAQEIC0tDQAQGxuL2NhYfPfdd7h48SJWrVqFvXv3YtSoUdoMW4UQQurGYssOERGR7pEJIYS2g8gXHx8PBwcHhIeHw8/Pr9AyGzduxLvvvou0tDQYGb16QLBCoYCNjQ2Sk5NhbW2t6ZCRmZOHel/sBQBcmBEAK1NjjZ+DiIiostHk77dOTR/K756ys7N7aRlra+siE52srCxkZWVJ9xUKhWaDfEH+TCyA3VhERES6SGcGKCuVSkycOBHt2rWDt7d3oWWePHmC2bNnY+zYsUUeJyQkBDY2NtLNzc2trEIG8N9MLBNDAxgZ6kx1EhER0b905tc5MDAQFy9exLp16wrdr1Ao0LNnTzRo0AAzZswo8jjTpk1DcnKydIuJiSmjiJ/h6slERES6TSe6sYKCgrBr1y4cOnQIrq6uBfanpKSgW7dusLKywtatW2FsXPS4GLlcDrlcXpbhqsjg6slEREQ6TastO0IIBAUFYevWrdi/fz88PT0LlFEoFAgICICJiQl27NgBU1NTLURatPR/p51zJhYREZFu0mrLTmBgINauXYvt27fDysoKcXFxAAAbGxuYmZlJiU56ejpWr14NhUIhDTi2t7eHoaH2EwyunkxERKTbtJrsLF26FADg7++vsj00NBTDhw/HmTNncOLECQBArVq1VMrcvn0bHh4e5RHmS7Ebi4iISLdpNdl51RI//v7+ryyjbRygTEREpNt0ZjZWRcXVk4mIiHQbk51SypAuAqoTE9uIiIjoBUx2Sim/G8uUY3aIiIh0EpOdUmI3FhERkW5jslNKGVxnh4iISKcx2SkldmMRERHpNiY7pcRuLCIiIt3GZKeU/puNxWSHiIhIFzHZKSV2YxEREek2JjullJ7DdXaIiIh0GZOdUspkNxYREZFOY7JTSuk5z6ae89pYREREuonJTinxqudERES6jclOKXE2FhERkW5jslMKQghpgDK7sYiIiHQTk51SyMpVQohnf7Mbi4iISDcx2SmF/C4sgFPPiYiIdBWTnVLI78IyMTKAoYFMy9EQERFRYZjslEL+Fc/ZhUVERKS7mOyUQjpnYhEREek8JjulIK2xw2SHiIhIZzHZKQVp2jm7sYiIiHQWk51S4IKCREREuo/JTin8143FaedERES6islOKeR3Y5mzG4uIiEhnMdkpBWnqObuxiIiIdBaTnVLIyFYCYLJDRESky5jslEJ6zrOWHXZjERER6S4mO6XAdXaIiIh0H5OdUkhnskNERKTzmOyUQgZnYxEREek8JjulwG4sIiIi3cdkpxTSpannXFSQiIhIVzHZKYWMnGdTz9mNRUREpLuY7JRC/qKCvDYWERGR7mKyUwr5s7FMmewQERHpLCY7pZCZw6ueExER6TomO6WQ37JjbswBykRERLqKyU4JCSGkdXZMTViNREREuoq/0iWUmaOEEM/+NufUcyIiIp3FZKeE8lt1AMCMU8+JiIh0FpOdEspfUNDEyACGBjItR0NERERFYbJTQvmXiuBMLCIiIt3GZKeEeBFQIiKiioHJTglxQUEiIqKKQavJTkhICFq2bAkrKys4ODigT58+iI6OVimzfPly+Pv7w9raGjKZDElJSdoJ9gXsxiIiIqoYtJrshIeHIzAwEBEREQgLC0NOTg4CAgKQlpYmlUlPT0e3bt3w2WefaTHSgv7rxuK0cyIiIl2m1V/qvXv3qtxftWoVHBwcEBkZCT8/PwDAxIkTAQAHDx4s5+heLr8by4wtO0RERDpNp5olkpOTAQB2dnYlPkZWVhaysrKk+wqFotRxFSb/iudcY4eIiEi36cwAZaVSiYkTJ6Jdu3bw9vYu8XFCQkJgY2Mj3dzc3DQY5X/SOWaHiIioQtCZZCcwMBAXL17EunXrSnWcadOmITk5WbrFxMRoKEJV+WN22I1FRESk23SiGysoKAi7du3CoUOH4OrqWqpjyeVyyOVyDUVWtPzZWOzGIiIi0m1aTXaEEJgwYQK2bt2KgwcPwtPTU5vhqIXdWERERBWDVpOdwMBArF27Ftu3b4eVlRXi4uIAADY2NjAzMwMAxMXFIS4uDjdu3AAAXLhwAVZWVnB3dy/VQObSksmeXRfLjFc8JyIi0mkyIYTQ2sllhV9AMzQ0FMOHDwcAzJgxAzNnznxpmZdRKBSwsbFBcnIyrK2tSxNuoYQQRT4PIiIiKhlN/n5rNdkpD2Wd7BAREZHmafL3W2dmYxERERGVBSY7REREpNeY7BAREZFeY7JDREREeo3JDhEREek1JjtERESk15jsEBERkV5jskNERER6jckOERER6TUmO0RERKTXmOwQERGRXmOyQ0RERHqNyQ4RERHpNSNtB1DW8i/qrlAotBwJERERFVf+73b+73hp6H2yk5KSAgBwc3PTciRERESkrpSUFNjY2JTqGDKhiZRJhymVSsTGxsLKygoymUxjx1UoFHBzc0NMTAysra01dlwqHOu7/LCuyw/ruvywrsuPpupaCIGUlBS4uLjAwKB0o270vmXHwMAArq6uZXZ8a2trfnDKEeu7/LCuyw/ruvywrsuPJuq6tC06+ThAmYiIiPQakx0iIiLSa0x2Skgul2P69OmQy+XaDqVSYH2XH9Z1+WFdlx/WdfnRxbrW+wHKREREVLmxZYeIiIj0GpMdIiIi0mtMdoiIiEivMdkhIiIivcZkp4SWLFkCDw8PmJqaonXr1jh58qS2Q6rwQkJC0LJlS1hZWcHBwQF9+vRBdHS0SpnMzEwEBgaiatWqsLS0RL9+/fDo0SMtRaw/5syZA5lMhokTJ0rbWNea8+DBA7z77ruoWrUqzMzM0KhRI5w+fVraL4TAl19+CWdnZ5iZmaFLly64fv26FiOumPLy8vDFF1/A09MTZmZm8PLywuzZs1WurcS6LplDhw6hV69ecHFxgUwmw7Zt21T2F6deExMTMWTIEFhbW8PW1hajRo1Campq+TwBQWpbt26dMDExEStXrhSXLl0SY8aMEba2tuLRo0faDq1C69q1qwgNDRUXL14UUVFRokePHsLd3V2kpqZKZd5//33h5uYm/vnnH3H69GnRpk0b4evrq8WoK76TJ08KDw8P0bhxY/Hhhx9K21nXmpGYmChq1Kghhg8fLk6cOCFu3bol9u3bJ27cuCGVmTNnjrCxsRHbtm0T586dE7179xaenp4iIyNDi5FXPF9//bWoWrWq2LVrl7h9+7bYuHGjsLS0FAsXLpTKsK5LZvfu3eLzzz8XW7ZsEQDE1q1bVfYXp167desmmjRpIiIiIsThw4dFrVq1xKBBg8olfiY7JdCqVSsRGBgo3c/LyxMuLi4iJCREi1Hpn8ePHwsAIjw8XAghRFJSkjA2NhYbN26Uyly5ckUAEMePH9dWmBVaSkqKqF27tggLCxMdO3aUkh3WteZ88sknon379kXuVyqVwsnJSXz77bfStqSkJCGXy8Uff/xRHiHqjZ49e4qRI0eqbOvbt68YMmSIEIJ1rSkvJjvFqdfLly8LAOLUqVNSmT179giZTCYePHhQ5jGzG0tN2dnZiIyMRJcuXaRtBgYG6NKlC44fP67FyPRPcnIyAMDOzg4AEBkZiZycHJW6r1evHtzd3Vn3JRQYGIiePXuq1CnAutakHTt2oEWLFujfvz8cHBzQrFkz/Pzzz9L+27dvIy4uTqWubWxs0Lp1a9a1mnx9ffHPP//g2rVrAIBz587hyJEj6N69OwDWdVkpTr0eP34ctra2aNGihVSmS5cuMDAwwIkTJ8o8Rr2/EKimPXnyBHl5eXB0dFTZ7ujoiKtXr2opKv2jVCoxceJEtGvXDt7e3gCAuLg4mJiYwNbWVqWso6Mj4uLitBBlxbZu3TqcOXMGp06dKrCPda05t27dwtKlS/HRRx/hs88+w6lTpxAcHAwTExMMGzZMqs/CvlNY1+r59NNPoVAoUK9ePRgaGiIvLw9ff/01hgwZAgCs6zJSnHqNi4uDg4ODyn4jIyPY2dmVS90z2SGdFBgYiIsXL+LIkSPaDkUvxcTE4MMPP0RYWBhMTU21HY5eUyqVaNGiBb755hsAQLNmzXDx4kX89NNPGDZsmJaj0y8bNmzAmjVrsHbtWjRs2BBRUVGYOHEiXFxcWNeVHLux1FStWjUYGhoWmJXy6NEjODk5aSkq/RIUFIRdu3bhwIEDcHV1lbY7OTkhOzsbSUlJKuVZ9+qLjIzE48eP0bx5cxgZGcHIyAjh4eFYtGgRjIyM4OjoyLrWEGdnZzRo0EBlW/369XHv3j0AkOqT3ymlN2XKFHz66ad455130KhRI7z33nuYNGkSQkJCALCuy0px6tXJyQmPHz9W2Z+bm4vExMRyqXsmO2oyMTGBj48P/vnnH2mbUqnEP//8g7Zt22oxsopPCIGgoCBs3boV+/fvh6enp8p+Hx8fGBsbq9R9dHQ07t27x7pXU+fOnXHhwgVERUVJtxYtWmDIkCHS36xrzWjXrl2BJRSuXbuGGjVqAAA8PT3h5OSkUtcKhQInTpxgXaspPT0dBgaqP2uGhoZQKpUAWNdlpTj12rZtWyQlJSEyMlIqs3//fiiVSrRu3brsgyzzIdB6aN26dUIul4tVq1aJy5cvi7FjxwpbW1sRFxen7dAqtA8++EDY2NiIgwcPiocPH0q39PR0qcz7778v3N3dxf79+8Xp06dF27ZtRdu2bbUYtf54fjaWEKxrTTl58qQwMjISX3/9tbh+/bpYs2aNMDc3F6tXr5bKzJkzR9ja2ort27eL8+fPizfffJPToUtg2LBhonr16tLU8y1btohq1aqJqVOnSmVY1yWTkpIizp49K86ePSsAiPnz54uzZ8+Ku3fvCiGKV6/dunUTzZo1EydOnBBHjhwRtWvX5tRzXbd48WLh7u4uTExMRKtWrURERIS2Q6rwABR6Cw0NlcpkZGSI8ePHiypVqghzc3Px1ltviYcPH2ovaD3yYrLDutacnTt3Cm9vbyGXy0W9evXE8uXLVfYrlUrxxRdfCEdHRyGXy0Xnzp1FdHS0lqKtuBQKhfjwww+Fu7u7MDU1FTVr1hSff/65yMrKksqwrkvmwIEDhX4/Dxs2TAhRvHpNSEgQgwYNEpaWlsLa2lqMGDFCpKSklEv8MiGeW1qSiIiISM9wzA4RERHpNSY7REREpNeY7BAREZFeY7JDREREeo3JDhEREek1JjtERESk15jsEBERkV5jskNEOuPq1ato06YNTE1N0bRp00LL+Pv7Y+LEieUaFxFVbFxUkIjUFh8fj+rVq+Pp06cwMTGBra0trly5And391Idd+DAgXjy5AlWrlwJS0tLVK1atUCZxMREGBsbw8rKqlTnUteMGTOwbds2REVFlet5iaj0jLQdABFVPMePH0eTJk1gYWGBEydOwM7OrtSJDgDcvHkTPXv2lC6SWRg7O7tSn4eIKhd2YxGR2o4dO4Z27doBAI4cOSL9/TJKpRKzZs2Cq6sr5HI5mjZtir1790r7ZTIZIiMjMWvWLMhkMsyYMaPQ47zYjeXh4YFvvvkGI0eOhJWVFdzd3bF8+XJp/507dyCTybBu3Tr4+vrC1NQU3t7eCA8Pl8qsWrUKtra2KufZtm0bZDKZtH/mzJk4d+4cZDIZZDIZVq1aBSEEZsyYAXd3d8jlcri4uCA4OPiVdUFE5YstO0RULPfu3UPjxo0BAOnp6TA0NMSqVauQkZEBmUwGW1tbDB48GD/++GOhj1+4cCG+//57LFu2DM2aNcPKlSvRu3dvXLp0CbVr18bDhw/RpUsXdOvWDR9//DEsLS2LHdv333+P2bNn47PPPsOmTZvwwQcfoGPHjqhbt65UZsqUKfjhhx/QoEEDzJ8/H7169cLt27cL7Sp70cCBA3Hx4kXs3bsXf//9NwDAxsYGmzdvxoIFC7Bu3To0bNgQcXFxOHfuXLHjJqLywZYdIioWFxcXREVF4dChQwCAEydOIDIyEiYmJvjrr78QFRWFWbNmFfn47777Dp988gneeecd1K1bF3PnzkXTpk3xww8/AACcnJxgZGQES0tLODk5qZXs9OjRA+PHj0etWrXwySefoFq1ajhw4IBKmaCgIPTr1w/169fH0qVLYWNjgxUrVhTr+GZmZrC0tISRkRGcnJzg5OQEMzMz3Lt3D05OTujSpQvc3d3RqlUrjBkzpthxE1H5YLJDRMViZGQEDw8PXL16FS1btkTjxo0RFxcHR0dH+Pn5wcPDA9WqVSv0sQqFArGxsQW6u9q1a4crV66UOrb8FifgWXeYk5MTHj9+rFKmbdu2Ks+lRYsWpT53//79kZGRgZo1a2LMmDHYunUrcnNzS3VMItI8dmMRUbE0bNgQd+/eRU5ODpRKJSwtLZGbm4vc3FxYWlqiRo0auHTpklZiMzY2Vrkvk8mgVCqL/XgDAwO8ODE1JyfnlY9zc3NDdHQ0/v77b4SFhWH8+PH49ttvER4eXiAmItIetuwQUbHs3r0bUVFRcHJywurVqxEVFQVvb2/88MMPiIqKwu7du4t8rLW1NVxcXHD06FGV7UePHkWDBg3KOnQAQEREhPR3bm4uIiMjUb9+fQCAvb09UlJSkJaWJpV5cYq5iYkJ8vLyChzXzMwMvXr1wqJFi3Dw4EEcP34cFy5cKJsnQUQlwpYdIiqWGjVqIC4uDo8ePcKbb74JmUyGS5cuoV+/fnB2dn7l46dMmYLp06fDy8sLTZs2RWhoKKKiorBmzZpyiB5YsmQJateujfr162PBggV4+vQpRo4cCQBo3bo1zM3N8dlnnyE4OBgnTpzAqlWrVB7v4eGB27dvIyoqCq6urrCyssIff/yBvLw86fGrV6+GmZnZS6fOE1H5Y8sOERXbwYMH0bJlS5iamuLkyZNwdXUtVqIDAMHBwfjoo48wefJkNGrUCHv37sWOHTtQu3btMo76mTlz5mDOnDlo0qQJjhw5gh07dkhjjOzs7LB69Wrs3r0bjRo1wh9//FFg6nu/fv3QrVs3dOrUCfb29vjjjz9ga2uLn3/+Ge3atUPjxo3x999/Y+fOncWa4UVE5YcrKBORXrtz5w48PT1x9uzZIi9BQUT6jS07REREpNeY7BAREZFeYzcWERER6TW27BAREZFeY7JDREREeo3JDhEREek1JjtERESk15jsEBERkV5jskNERER6jckOERER6TUmO0RERKTXmOwQERGRXvt/H7nVyDxLufIAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.plot(average_coverage)\n", "plt.title('Average branch coverage of cgi_decode() with random inputs')\n", "plt.xlabel('# of inputs')\n", "plt.ylabel('line pairs covered')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "source": [ "We see that achieving branch coverage takes longer than statement coverage; it simply is a more difficult criterion to satisfy with random inputs." ] } ], "metadata": { "ipub": { "bibliography": "fuzzingbook.bib", "toc": true }, "kernelspec": { "display_name": "3.10.2", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.8" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": true }, "toc-autonumbering": false }, "nbformat": 4, "nbformat_minor": 4 }