{ "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": "2024-01-18T17:14:15.012698Z", "iopub.status.busy": "2024-01-18T17:14:15.012053Z", "iopub.status.idle": "2024-01-18T17:14:15.080809Z", "shell.execute_reply": "2024-01-18T17:14:15.080491Z" }, "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": "2024-01-18T17:14:15.102805Z", "iopub.status.busy": "2024-01-18T17:14:15.102643Z", "iopub.status.idle": "2024-01-18T17:14:15.104757Z", "shell.execute_reply": "2024-01-18T17:14:15.104455Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import bookutils.setup" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2024-01-18T17:14:15.106721Z", "iopub.status.busy": "2024-01-18T17:14:15.106562Z", "iopub.status.idle": "2024-01-18T17:14:15.108312Z", "shell.execute_reply": "2024-01-18T17:14:15.108033Z" }, "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": "2024-01-18T17:14:15.109988Z", "iopub.status.busy": "2024-01-18T17:14:15.109877Z", "iopub.status.idle": "2024-01-18T17:14:15.113197Z", "shell.execute_reply": "2024-01-18T17:14:15.112928Z" }, "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": "2024-01-18T17:14:15.114649Z", "iopub.status.busy": "2024-01-18T17:14:15.114564Z", "iopub.status.idle": "2024-01-18T17:14:15.116576Z", "shell.execute_reply": "2024-01-18T17:14:15.116313Z" }, "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": "2024-01-18T17:14:15.118071Z", "iopub.status.busy": "2024-01-18T17:14:15.117992Z", "iopub.status.idle": "2024-01-18T17:14:15.119734Z", "shell.execute_reply": "2024-01-18T17:14:15.119490Z" }, "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": "2024-01-18T17:14:15.121528Z", "iopub.status.busy": "2024-01-18T17:14:15.121435Z", "iopub.status.idle": "2024-01-18T17:14:15.123680Z", "shell.execute_reply": "2024-01-18T17:14:15.123415Z" }, "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": "2024-01-18T17:14:15.125248Z", "iopub.status.busy": "2024-01-18T17:14:15.125167Z", "iopub.status.idle": "2024-01-18T17:14:15.126784Z", "shell.execute_reply": "2024-01-18T17:14:15.126547Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from types import FrameType, TracebackType" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "button": false, "execution": { "iopub.execute_input": "2024-01-18T17:14:15.128208Z", "iopub.status.busy": "2024-01-18T17:14:15.128126Z", "iopub.status.idle": "2024-01-18T17:14:15.129769Z", "shell.execute_reply": "2024-01-18T17:14:15.129474Z" }, "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": "2024-01-18T17:14:15.131303Z", "iopub.status.busy": "2024-01-18T17:14:15.131222Z", "iopub.status.idle": "2024-01-18T17:14:15.133131Z", "shell.execute_reply": "2024-01-18T17:14:15.132870Z" }, "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": "2024-01-18T17:14:15.134618Z", "iopub.status.busy": "2024-01-18T17:14:15.134538Z", "iopub.status.idle": "2024-01-18T17:14:15.136118Z", "shell.execute_reply": "2024-01-18T17:14:15.135806Z" }, "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": "2024-01-18T17:14:15.137561Z", "iopub.status.busy": "2024-01-18T17:14:15.137473Z", "iopub.status.idle": "2024-01-18T17:14:15.139390Z", "shell.execute_reply": "2024-01-18T17:14:15.139090Z" }, "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": "2024-01-18T17:14:15.141115Z", "iopub.status.busy": "2024-01-18T17:14:15.141027Z", "iopub.status.idle": "2024-01-18T17:14:15.142799Z", "shell.execute_reply": "2024-01-18T17:14:15.142536Z" }, "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": "2024-01-18T17:14:15.144320Z", "iopub.status.busy": "2024-01-18T17:14:15.144235Z", "iopub.status.idle": "2024-01-18T17:14:15.145887Z", "shell.execute_reply": "2024-01-18T17:14:15.145616Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import inspect" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2024-01-18T17:14:15.147347Z", "iopub.status.busy": "2024-01-18T17:14:15.147266Z", "iopub.status.idle": "2024-01-18T17:14:15.149631Z", "shell.execute_reply": "2024-01-18T17:14:15.149383Z" }, "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": "2024-01-18T17:14:15.151188Z", "iopub.status.busy": "2024-01-18T17:14:15.151106Z", "iopub.status.idle": "2024-01-18T17:14:15.152687Z", "shell.execute_reply": "2024-01-18T17:14:15.152441Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from bookutils import print_content, print_file" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2024-01-18T17:14:15.154175Z", "iopub.status.busy": "2024-01-18T17:14:15.154086Z", "iopub.status.idle": "2024-01-18T17:14:15.230618Z", "shell.execute_reply": "2024-01-18T17:14:15.230345Z" }, "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": "2024-01-18T17:14:15.232210Z", "iopub.status.busy": "2024-01-18T17:14:15.232125Z", "iopub.status.idle": "2024-01-18T17:14:15.233837Z", "shell.execute_reply": "2024-01-18T17:14:15.233608Z" }, "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": "2024-01-18T17:14:15.235309Z", "iopub.status.busy": "2024-01-18T17:14:15.235204Z", "iopub.status.idle": "2024-01-18T17:14:15.237209Z", "shell.execute_reply": "2024-01-18T17:14:15.236972Z" }, "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": "2024-01-18T17:14:15.238765Z", "iopub.status.busy": "2024-01-18T17:14:15.238651Z", "iopub.status.idle": "2024-01-18T17:14:15.240934Z", "shell.execute_reply": "2024-01-18T17:14:15.240643Z" }, "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": "2024-01-18T17:14:15.242490Z", "iopub.status.busy": "2024-01-18T17:14:15.242374Z", "iopub.status.idle": "2024-01-18T17:14:15.244343Z", "shell.execute_reply": "2024-01-18T17:14:15.244083Z" }, "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": "2024-01-18T17:14:15.245856Z", "iopub.status.busy": "2024-01-18T17:14:15.245729Z", "iopub.status.idle": "2024-01-18T17:14:15.247418Z", "shell.execute_reply": "2024-01-18T17:14:15.247186Z" }, "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": "2024-01-18T17:14:15.248849Z", "iopub.status.busy": "2024-01-18T17:14:15.248751Z", "iopub.status.idle": "2024-01-18T17:14:16.140400Z", "shell.execute_reply": "2024-01-18T17:14:16.140099Z" }, "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": "2024-01-18T17:14:16.142313Z", "iopub.status.busy": "2024-01-18T17:14:16.142198Z", "iopub.status.idle": "2024-01-18T17:14:16.143912Z", "shell.execute_reply": "2024-01-18T17:14:16.143646Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "Location = Tuple[str, int]" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "button": false, "execution": { "iopub.execute_input": "2024-01-18T17:14:16.145560Z", "iopub.status.busy": "2024-01-18T17:14:16.145456Z", "iopub.status.idle": "2024-01-18T17:14:16.150054Z", "shell.execute_reply": "2024-01-18T17:14:16.149816Z" }, "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": "2024-01-18T17:14:16.151548Z", "iopub.status.busy": "2024-01-18T17:14:16.151465Z", "iopub.status.idle": "2024-01-18T17:14:16.153353Z", "shell.execute_reply": "2024-01-18T17:14:16.153106Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{('cgi_decode', 20), ('cgi_decode', 10), ('cgi_decode', 32), ('cgi_decode', 16), ('cgi_decode', 12), ('cgi_decode', 19), ('cgi_decode', 9), ('cgi_decode', 15), ('cgi_decode', 31), ('cgi_decode', 18), ('cgi_decode', 8), ('cgi_decode', 21), ('cgi_decode', 11), ('cgi_decode', 17), ('cgi_decode', 30)}\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": "2024-01-18T17:14:16.154940Z", "iopub.status.busy": "2024-01-18T17:14:16.154853Z", "iopub.status.idle": "2024-01-18T17:14:16.157538Z", "shell.execute_reply": "2024-01-18T17:14:16.157265Z" }, "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": "2024-01-18T17:14:16.159119Z", "iopub.status.busy": "2024-01-18T17:14:16.159010Z", "iopub.status.idle": "2024-01-18T17:14:16.161501Z", "shell.execute_reply": "2024-01-18T17:14:16.161266Z" }, "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": "2024-01-18T17:14:16.163047Z", "iopub.status.busy": "2024-01-18T17:14:16.162940Z", "iopub.status.idle": "2024-01-18T17:14:16.164814Z", "shell.execute_reply": "2024-01-18T17:14:16.164584Z" }, "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": "2024-01-18T17:14:16.166290Z", "iopub.status.busy": "2024-01-18T17:14:16.166185Z", "iopub.status.idle": "2024-01-18T17:14:16.168176Z", "shell.execute_reply": "2024-01-18T17:14:16.167936Z" }, "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": "2024-01-18T17:14:16.169660Z", "iopub.status.busy": "2024-01-18T17:14:16.169562Z", "iopub.status.idle": "2024-01-18T17:14:16.234522Z", "shell.execute_reply": "2024-01-18T17:14:16.234205Z" }, "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": "2024-01-18T17:14:16.236302Z", "iopub.status.busy": "2024-01-18T17:14:16.236214Z", "iopub.status.idle": "2024-01-18T17:14:16.238473Z", "shell.execute_reply": "2024-01-18T17:14:16.238226Z" }, "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": "2024-01-18T17:14:16.240134Z", "iopub.status.busy": "2024-01-18T17:14:16.240019Z", "iopub.status.idle": "2024-01-18T17:14:16.242591Z", "shell.execute_reply": "2024-01-18T17:14:16.242328Z" }, "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": "2024-01-18T17:14:16.244068Z", "iopub.status.busy": "2024-01-18T17:14:16.243966Z", "iopub.status.idle": "2024-01-18T17:14:16.246095Z", "shell.execute_reply": "2024-01-18T17:14:16.245870Z" }, "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": "2024-01-18T17:14:16.247552Z", "iopub.status.busy": "2024-01-18T17:14:16.247451Z", "iopub.status.idle": "2024-01-18T17:14:16.248937Z", "shell.execute_reply": "2024-01-18T17:14:16.248704Z" }, "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": "2024-01-18T17:14:16.250342Z", "iopub.status.busy": "2024-01-18T17:14:16.250267Z", "iopub.status.idle": "2024-01-18T17:14:16.252554Z", "shell.execute_reply": "2024-01-18T17:14:16.252325Z" }, "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": "2024-01-18T17:14:16.254015Z", "iopub.status.busy": "2024-01-18T17:14:16.253933Z", "iopub.status.idle": "2024-01-18T17:14:16.255748Z", "shell.execute_reply": "2024-01-18T17:14:16.255515Z" }, "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": "2024-01-18T17:14:16.257156Z", "iopub.status.busy": "2024-01-18T17:14:16.257075Z", "iopub.status.idle": "2024-01-18T17:14:16.272471Z", "shell.execute_reply": "2024-01-18T17:14:16.272184Z" }, "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": "2024-01-18T17:14:16.274117Z", "iopub.status.busy": "2024-01-18T17:14:16.274029Z", "iopub.status.idle": "2024-01-18T17:14:16.549045Z", "shell.execute_reply": "2024-01-18T17:14:16.548748Z" }, "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": "2024-01-18T17:14:16.551189Z", "iopub.status.busy": "2024-01-18T17:14:16.551007Z", "iopub.status.idle": "2024-01-18T17:14:16.552800Z", "shell.execute_reply": "2024-01-18T17:14:16.552549Z" }, "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": "2024-01-18T17:14:16.554378Z", "iopub.status.busy": "2024-01-18T17:14:16.554264Z", "iopub.status.idle": "2024-01-18T17:14:16.655109Z", "shell.execute_reply": "2024-01-18T17:14:16.654767Z" }, "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+fLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABYCElEQVR4nO3deVhUZfsH8O9hG3YQRZBYXAsVRUNDcE9S0dckTU19wzVbMFNSCyu3TFwySzO3t8Qylyy3zFRyQVFBQSk1xSV3BVcYQAVknt8f/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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\n", "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": "2024-01-18T17:14:16.656888Z", "iopub.status.busy": "2024-01-18T17:14:16.656765Z", "iopub.status.idle": "2024-01-18T17:14:17.962421Z", "shell.execute_reply": "2024-01-18T17:14:17.962072Z" }, "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": "2024-01-18T17:14:17.964513Z", "iopub.status.busy": "2024-01-18T17:14:17.964375Z", "iopub.status.idle": "2024-01-18T17:14:18.055833Z", "shell.execute_reply": "2024-01-18T17:14:18.055500Z" }, "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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SUjOzIOTtTma+DjKXQ4REZFRKNVppodNnToVr7zyCvbu3YuwsDAAwJEjR7Bt2zZ88803ei+wOtlxrrBXplOAK0xNONGMiIioNHQOM5GRkahfvz7mzZuHdevWAQDq16+PAwcOSOGGdCeEwI6z8QAKx8sQERFR6egcZgAgLCwMq1at0nct1dqZuFTcTsmGlbkJWtflqr9ERESlVaZzGVevXsX//vc/PP/880hMLFzgbevWrTh79qxei6tOtv/bK9PuKRdYmJnIXA0REZHx0DnMREdHIzg4GEeOHMHatWuRnp4OADh16hQ++OADvRdYXWznKSYiIqIy0TnMTJw4ER9//DF27twJc/P/rhvUsWNHHD58WK/FVRfX7qbjcmI6TJUKdKjHVX+JiIh0oXOYOX36NJ577rki211dXXHv3j29FFXdaGYxtfCvAXsrM5mrISIiMi46hxkHBwfcuXOnyPaTJ0+iVq1aeimqutHMYurMU0xEREQ60znMDB48GO+++y7i4+OhUCigVqtx8OBBvP322xg2bFhF1FilJaZm4+StZADAM/W56i8REZGudA4zn376KQICAuDl5YX09HQEBgaibdu2aNmyJf73v/9VRI1V2v7L9yAE0NDTHu72FnKXQ0REZHR0WmdGCIH4+HjMmzcP77//Pk6fPo309HQ0atQIdevWragaq7Q/r94HALSqw7VliIiIykLnMFOnTh2cPXsWdevWhZeXV0XVVS0IIXDoauGg6Zb+vK4VERFRWeh0mkmpVKJu3bq4f/9+RdVTrdy4n4nbKdkwM1GgiY+T3OUQEREZJZ3HzMyYMQMTJkzAmTNnKqKeakVziqmRtyMszbnqLxERUVnofG2mYcOGITMzEyEhITA3N4elpaXW7UlJSXorrqr7k6eYiIiIyk3nMDNnzpwKKKP6KRwvU9gz09Kfg3+JiIjKSucwExERURF1VDuXEtJxPyMXFmZKhHo5yF0OERGR0SrXVbOHDBnCq2aXkeYUU1NfJ5iblunPQERERCjnVbPXrVvHq2aX0Z88xURERKQXvGq2DArUAkeuacIMB/8SERGVB6+aLYNzt1ORmp0PWwtTNPCwk7scIiIio8arZstAM14mzM8JpiYcL0NERFQevGq2DDTjZVpwvAwREVG58arZBpabr8ax64ULC3K8DBERUfnpvM6Mubk5vvnmG0yZMgVn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\n", "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": "2024-01-18T17:14:18.058008Z", "iopub.status.busy": "2024-01-18T17:14:18.057876Z", "iopub.status.idle": "2024-01-18T17:14:18.059821Z", "shell.execute_reply": "2024-01-18T17:14:18.059496Z" }, "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": "2024-01-18T17:14:18.061933Z", "iopub.status.busy": "2024-01-18T17:14:18.061795Z", "iopub.status.idle": "2024-01-18T17:14:18.063839Z", "shell.execute_reply": "2024-01-18T17:14:18.063465Z" }, "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": "2024-01-18T17:14:18.065999Z", "iopub.status.busy": "2024-01-18T17:14:18.065840Z", "iopub.status.idle": "2024-01-18T17:14:18.067972Z", "shell.execute_reply": "2024-01-18T17:14:18.067574Z" }, "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": "2024-01-18T17:14:18.070798Z", "iopub.status.busy": "2024-01-18T17:14:18.070625Z", "iopub.status.idle": "2024-01-18T17:14:18.072869Z", "shell.execute_reply": "2024-01-18T17:14:18.072365Z" }, "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": "2024-01-18T17:14:18.074789Z", "iopub.status.busy": "2024-01-18T17:14:18.074633Z", "iopub.status.idle": "2024-01-18T17:14:18.077032Z", "shell.execute_reply": "2024-01-18T17:14:18.076488Z" }, "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": "2024-01-18T17:14:18.079288Z", "iopub.status.busy": "2024-01-18T17:14:18.079080Z", "iopub.status.idle": "2024-01-18T17:14:18.081247Z", "shell.execute_reply": "2024-01-18T17:14:18.080790Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from bookutils import print_file" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "execution": { "iopub.execute_input": "2024-01-18T17:14:18.083576Z", "iopub.status.busy": "2024-01-18T17:14:18.083399Z", "iopub.status.idle": "2024-01-18T17:14:18.125305Z", "shell.execute_reply": "2024-01-18T17:14:18.125023Z" }, "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": "2024-01-18T17:14:18.127062Z", "iopub.status.busy": "2024-01-18T17:14:18.126933Z", "iopub.status.idle": "2024-01-18T17:14:18.325996Z", "shell.execute_reply": "2024-01-18T17:14:18.325513Z" }, "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": "2024-01-18T17:14:18.328379Z", "iopub.status.busy": "2024-01-18T17:14:18.328138Z", "iopub.status.idle": "2024-01-18T17:14:18.609088Z", "shell.execute_reply": "2024-01-18T17:14:18.607794Z" }, "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." ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "execution": { "iopub.execute_input": "2024-01-18T17:14:18.615971Z", "iopub.status.busy": "2024-01-18T17:14:18.615431Z", "iopub.status.idle": "2024-01-18T17:14:18.752208Z", "shell.execute_reply": "2024-01-18T17:14:18.751199Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "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.c" ] }, { "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": "2024-01-18T17:14:18.758700Z", "iopub.status.busy": "2024-01-18T17:14:18.758269Z", "iopub.status.idle": "2024-01-18T17:14:18.765044Z", "shell.execute_reply": "2024-01-18T17:14:18.764268Z" }, "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": "2024-01-18T17:14:18.768680Z", "iopub.status.busy": "2024-01-18T17:14:18.768520Z", "iopub.status.idle": "2024-01-18T17:14:18.771343Z", "shell.execute_reply": "2024-01-18T17:14:18.770952Z" }, "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": "2024-01-18T17:14:18.773219Z", "iopub.status.busy": "2024-01-18T17:14:18.773071Z", "iopub.status.idle": "2024-01-18T17:14:18.775510Z", "shell.execute_reply": "2024-01-18T17:14:18.775127Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "coverage = read_gcov_coverage('cgi_decode.c')" ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "execution": { "iopub.execute_input": "2024-01-18T17:14:18.777326Z", "iopub.status.busy": "2024-01-18T17:14:18.777194Z", "iopub.status.idle": "2024-01-18T17:14:18.779723Z", "shell.execute_reply": "2024-01-18T17:14:18.779454Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[('cgi_decode.c', 20),\n", " ('cgi_decode.c', 26),\n", " ('cgi_decode.c', 23),\n", " ('cgi_decode.c', 29),\n", " ('cgi_decode.c', 32)]" ] }, "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": "2024-01-18T17:14:18.781368Z", "iopub.status.busy": "2024-01-18T17:14:18.781254Z", "iopub.status.idle": "2024-01-18T17:14:18.782920Z", "shell.execute_reply": "2024-01-18T17:14:18.782677Z" }, "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": "2024-01-18T17:14:18.784505Z", "iopub.status.busy": "2024-01-18T17:14:18.784389Z", "iopub.status.idle": "2024-01-18T17:14:18.786877Z", "shell.execute_reply": "2024-01-18T17:14:18.786590Z" }, "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_72488/2238772797.py\", line 5, in \n", " cgi_decode(s)\n", " File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_72488/1071239422.py\", line 22, in cgi_decode\n", " digit_high, digit_low = s[i + 1], s[i + 2]\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": "2024-01-18T17:14:18.789383Z", "iopub.status.busy": "2024-01-18T17:14:18.789172Z", "iopub.status.idle": "2024-01-18T17:14:18.792191Z", "shell.execute_reply": "2024-01-18T17:14:18.791749Z" }, "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": "2024-01-18T17:14:18.795108Z", "iopub.status.busy": "2024-01-18T17:14:18.794923Z", "iopub.status.idle": "2024-01-18T17:14:18.797163Z", "shell.execute_reply": "2024-01-18T17:14:18.796813Z" }, "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": "2024-01-18T17:14:18.798845Z", "iopub.status.busy": "2024-01-18T17:14:18.798718Z", "iopub.status.idle": "2024-01-18T17:14:18.801705Z", "shell.execute_reply": "2024-01-18T17:14:18.801403Z" }, "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": "2024-01-18T17:14:18.803361Z", "iopub.status.busy": "2024-01-18T17:14:18.803235Z", "iopub.status.idle": "2024-01-18T17:14:18.806687Z", "shell.execute_reply": "2024-01-18T17:14:18.806283Z" }, "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": "2024-01-18T17:14:18.808350Z", "iopub.status.busy": "2024-01-18T17:14:18.808248Z", "iopub.status.idle": "2024-01-18T17:14:18.810935Z", "shell.execute_reply": "2024-01-18T17:14:18.810571Z" }, "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": "2024-01-18T17:14:18.812947Z", "iopub.status.busy": "2024-01-18T17:14:18.812817Z", "iopub.status.idle": "2024-01-18T17:14:18.814695Z", "shell.execute_reply": "2024-01-18T17:14:18.814346Z" }, "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": "2024-01-18T17:14:18.816602Z", "iopub.status.busy": "2024-01-18T17:14:18.816454Z", "iopub.status.idle": "2024-01-18T17:14:19.290406Z", "shell.execute_reply": "2024-01-18T17:14:19.290018Z" }, "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/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": "2024-01-18T17:14:19.292667Z", "iopub.status.busy": "2024-01-18T17:14:19.292524Z", "iopub.status.idle": "2024-01-18T17:14:19.294428Z", "shell.execute_reply": "2024-01-18T17:14:19.294180Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import os\n", "import glob" ] }, { "cell_type": "code", "execution_count": 68, "metadata": { "execution": { "iopub.execute_input": "2024-01-18T17:14:19.295855Z", "iopub.status.busy": "2024-01-18T17:14:19.295743Z", "iopub.status.idle": "2024-01-18T17:14:19.298305Z", "shell.execute_reply": "2024-01-18T17:14:19.298049Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "for file in glob.glob(\"cgi_decode\") + 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": "2024-01-18T17:14:19.299954Z", "iopub.status.busy": "2024-01-18T17:14:19.299830Z", "iopub.status.idle": "2024-01-18T17:14:19.301696Z", "shell.execute_reply": "2024-01-18T17:14:19.301449Z" }, "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_72488/1102034435.py\", line 2, in \n", " assert cgi_decode('%') == '%'\n", " File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_72488/1071239422.py\", line 22, in cgi_decode\n", " digit_high, digit_low = s[i + 1], s[i + 2]\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": "2024-01-18T17:14:19.303415Z", "iopub.status.busy": "2024-01-18T17:14:19.303283Z", "iopub.status.idle": "2024-01-18T17:14:19.305234Z", "shell.execute_reply": "2024-01-18T17:14:19.304876Z" }, "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_72488/2291699482.py\", line 2, in \n", " assert cgi_decode('%4') == '%4'\n", " File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_72488/1071239422.py\", line 22, in cgi_decode\n", " digit_high, digit_low = s[i + 1], s[i + 2]\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": "2024-01-18T17:14:19.306921Z", "iopub.status.busy": "2024-01-18T17:14:19.306801Z", "iopub.status.idle": "2024-01-18T17:14:19.308534Z", "shell.execute_reply": "2024-01-18T17:14:19.308269Z" }, "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": "2024-01-18T17:14:19.310133Z", "iopub.status.busy": "2024-01-18T17:14:19.309998Z", "iopub.status.idle": "2024-01-18T17:14:19.313271Z", "shell.execute_reply": "2024-01-18T17:14:19.312943Z" }, "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": "2024-01-18T17:14:19.314845Z", "iopub.status.busy": "2024-01-18T17:14:19.314755Z", "iopub.status.idle": "2024-01-18T17:14:19.316587Z", "shell.execute_reply": "2024-01-18T17:14:19.316276Z" }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden" }, "outputs": [], "source": [ "assert fixed_cgi_decode('%') == '%'" ] }, { "cell_type": "code", "execution_count": 74, "metadata": { "execution": { "iopub.execute_input": "2024-01-18T17:14:19.317932Z", "iopub.status.busy": "2024-01-18T17:14:19.317849Z", "iopub.status.idle": "2024-01-18T17:14:19.319450Z", "shell.execute_reply": "2024-01-18T17:14:19.319141Z" }, "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": "2024-01-18T17:14:19.320868Z", "iopub.status.busy": "2024-01-18T17:14:19.320768Z", "iopub.status.idle": "2024-01-18T17:14:19.322718Z", "shell.execute_reply": "2024-01-18T17:14:19.322395Z" }, "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": "2024-01-18T17:14:19.324287Z", "iopub.status.busy": "2024-01-18T17:14:19.324191Z", "iopub.status.idle": "2024-01-18T17:14:19.328810Z", "shell.execute_reply": "2024-01-18T17:14:19.328546Z" }, "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": "2024-01-18T17:14:19.330330Z", "iopub.status.busy": "2024-01-18T17:14:19.330238Z", "iopub.status.idle": "2024-01-18T17:14:19.332066Z", "shell.execute_reply": "2024-01-18T17:14:19.331796Z" }, "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": "2024-01-18T17:14:19.333517Z", "iopub.status.busy": "2024-01-18T17:14:19.333439Z", "iopub.status.idle": "2024-01-18T17:14:19.335982Z", "shell.execute_reply": "2024-01-18T17:14:19.335692Z" }, "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": "2024-01-18T17:14:19.337653Z", "iopub.status.busy": "2024-01-18T17:14:19.337543Z", "iopub.status.idle": "2024-01-18T17:14:19.339694Z", "shell.execute_reply": "2024-01-18T17:14:19.339334Z" }, "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": "2024-01-18T17:14:19.341350Z", "iopub.status.busy": "2024-01-18T17:14:19.341223Z", "iopub.status.idle": "2024-01-18T17:14:19.344233Z", "shell.execute_reply": "2024-01-18T17:14:19.343915Z" }, "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": "2024-01-18T17:14:19.345674Z", "iopub.status.busy": "2024-01-18T17:14:19.345582Z", "iopub.status.idle": "2024-01-18T17:14:19.347658Z", "shell.execute_reply": "2024-01-18T17:14:19.347382Z" }, "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": "2024-01-18T17:14:19.349264Z", "iopub.status.busy": "2024-01-18T17:14:19.349186Z", "iopub.status.idle": "2024-01-18T17:14:19.351040Z", "shell.execute_reply": "2024-01-18T17:14:19.350788Z" }, "slideshow": { "slide_type": "fragment" }, "solution2": "hidden" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{(('cgi_decode', 11), ('cgi_decode', 8)), (('cgi_decode', 8), ('cgi_decode', 9)), (('cgi_decode', 8), ('cgi_decode', 15)), (('cgi_decode', 16), ('cgi_decode', 17)), (('cgi_decode', 31), ('cgi_decode', 17)), (('cgi_decode', 8), ('cgi_decode', 10)), (('cgi_decode', 9), ('cgi_decode', 8)), (('cgi_decode', 15), ('cgi_decode', 16)), (('cgi_decode', 17), ('cgi_decode', 32)), (('cgi_decode', 8), ('cgi_decode', 11)), (('cgi_decode', 30), ('cgi_decode', 31)), (('cgi_decode', 21), ('cgi_decode', 30)), (('cgi_decode', 20), ('cgi_decode', 31)), (('cgi_decode', 19), ('cgi_decode', 20)), (('cgi_decode', 17), ('cgi_decode', 18)), (('cgi_decode', 19), ('cgi_decode', 21)), (('cgi_decode', 8), ('cgi_decode', 12)), (('cgi_decode', 10), ('cgi_decode', 8)), (('cgi_decode', 12), ('cgi_decode', 8)), (('cgi_decode', 18), ('cgi_decode', 19))}\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": "2024-01-18T17:14:19.352395Z", "iopub.status.busy": "2024-01-18T17:14:19.352310Z", "iopub.status.idle": "2024-01-18T17:14:19.354978Z", "shell.execute_reply": "2024-01-18T17:14:19.354700Z" }, "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": "2024-01-18T17:14:19.356652Z", "iopub.status.busy": "2024-01-18T17:14:19.356551Z", "iopub.status.idle": "2024-01-18T17:14:19.358742Z", "shell.execute_reply": "2024-01-18T17:14:19.358460Z" }, "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": "2024-01-18T17:14:19.360164Z", "iopub.status.busy": "2024-01-18T17:14:19.360075Z", "iopub.status.idle": "2024-01-18T17:14:19.362744Z", "shell.execute_reply": "2024-01-18T17:14:19.362481Z" }, "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": "2024-01-18T17:14:19.364189Z", "iopub.status.busy": "2024-01-18T17:14:19.364073Z", "iopub.status.idle": "2024-01-18T17:14:19.366114Z", "shell.execute_reply": "2024-01-18T17:14:19.365840Z" }, "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": "2024-01-18T17:14:19.367690Z", "iopub.status.busy": "2024-01-18T17:14:19.367569Z", "iopub.status.idle": "2024-01-18T17:14:19.370988Z", "shell.execute_reply": "2024-01-18T17:14:19.370716Z" }, "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": "2024-01-18T17:14:19.372597Z", "iopub.status.busy": "2024-01-18T17:14:19.372497Z", "iopub.status.idle": "2024-01-18T17:14:19.375100Z", "shell.execute_reply": "2024-01-18T17:14:19.374801Z" }, "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": "2024-01-18T17:14:19.376555Z", "iopub.status.busy": "2024-01-18T17:14:19.376469Z", "iopub.status.idle": "2024-01-18T17:14:19.378716Z", "shell.execute_reply": "2024-01-18T17:14:19.378473Z" }, "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": "2024-01-18T17:14:19.380051Z", "iopub.status.busy": "2024-01-18T17:14:19.379968Z", "iopub.status.idle": "2024-01-18T17:14:19.395589Z", "shell.execute_reply": "2024-01-18T17:14:19.395314Z" }, "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": "2024-01-18T17:14:19.397033Z", "iopub.status.busy": "2024-01-18T17:14:19.396948Z", "iopub.status.idle": "2024-01-18T17:14:19.527135Z", "shell.execute_reply": "2024-01-18T17:14:19.525345Z" }, "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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\n", "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": "2024-01-18T17:14:19.533888Z", "iopub.status.busy": "2024-01-18T17:14:19.533748Z", "iopub.status.idle": "2024-01-18T17:14:19.544077Z", "shell.execute_reply": "2024-01-18T17:14:19.543288Z" }, "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": "2024-01-18T17:14:19.548570Z", "iopub.status.busy": "2024-01-18T17:14:19.548023Z", "iopub.status.idle": "2024-01-18T17:14:19.554254Z", "shell.execute_reply": "2024-01-18T17:14:19.553500Z" }, "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": "2024-01-18T17:14:19.560310Z", "iopub.status.busy": "2024-01-18T17:14:19.559974Z", "iopub.status.idle": "2024-01-18T17:14:19.565840Z", "shell.execute_reply": "2024-01-18T17:14:19.564979Z" }, "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": "2024-01-18T17:14:19.570543Z", "iopub.status.busy": "2024-01-18T17:14:19.569908Z", "iopub.status.idle": "2024-01-18T17:14:21.100035Z", "shell.execute_reply": "2024-01-18T17:14:21.099734Z" }, "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": "2024-01-18T17:14:21.102022Z", "iopub.status.busy": "2024-01-18T17:14:21.101890Z", "iopub.status.idle": "2024-01-18T17:14:21.188379Z", "shell.execute_reply": "2024-01-18T17:14:21.187975Z" }, "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": "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\n", "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": "Python 3 (ipykernel)", "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.10.2" }, "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 }