├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── after-fix.txt ├── cleanup_run_linters_fast_pytests.sh ├── requirements.txt ├── setup.py ├── src └── funfuzz │ ├── __init__.py │ ├── __main__.py │ ├── autobisectjs │ ├── README.md │ ├── __init__.py │ ├── __main__.py │ ├── autobisectjs.py │ ├── examples.md │ ├── faq.md │ └── known_broken_earliest_working.py │ ├── bot.py │ ├── ccoverage │ ├── __init__.py │ ├── gatherer.py │ ├── get_build.py │ └── reporter.py │ ├── js │ ├── .eslintrc.js │ ├── README.md │ ├── __init__.py │ ├── build_options.py │ ├── compare_jit.py │ ├── compile_shell.py │ ├── examples.md │ ├── faq.md │ ├── files_to_link.txt │ ├── inspect_shell.py │ ├── js_interesting.py │ ├── jsfunfuzz │ │ ├── README.md │ │ ├── avoid-known-bugs.js │ │ ├── built-in-constructors.js │ │ ├── detect-engine.js │ │ ├── driver.js │ │ ├── error-reporting.js │ │ ├── gen-asm.js │ │ ├── gen-grammar.js │ │ ├── gen-math.js │ │ ├── gen-proxy.js │ │ ├── gen-recursion.js │ │ ├── gen-regex.js │ │ ├── gen-stomp-on-registers.js │ │ ├── gen-type-aware-code.js │ │ ├── mess-grammar.js │ │ ├── mess-tokens.js │ │ ├── preamble.js │ │ ├── run-in-sandbox.js │ │ ├── run-reduction-marker.js │ │ ├── run.js │ │ ├── tail.js │ │ ├── test-asm.js │ │ ├── test-consistency.js │ │ ├── test-math.js │ │ ├── test-misc.js │ │ └── test-regex.js │ ├── link_fuzzer.py │ ├── loop.py │ ├── package.json │ ├── shared │ │ ├── mersenne-twister.js │ │ ├── random.js │ │ └── testing-functions.js │ ├── shell_flags.py │ └── with_binaryen.py │ ├── loop_bot.py │ ├── run_ccoverage.py │ └── util │ ├── __init__.py │ ├── cdb_cmds.txt │ ├── crashesat.py │ ├── create_collector.py │ ├── file_manipulation.py │ ├── file_system_helpers.py │ ├── fork_join.py │ ├── gdb_cmds.txt │ ├── get_hg_repo.sh │ ├── hg_helpers.py │ ├── lithium_helpers.py │ ├── lock_dir.py │ ├── os_ops.py │ ├── repos_update.py │ ├── s3cache.py │ ├── sm_compile_helpers.py │ └── subprocesses.py ├── tests ├── __init__.py ├── js │ ├── __init__.py │ ├── test_build_options.py │ ├── test_compile_shell.py │ ├── test_link_fuzzer.py │ ├── test_shell_flags.py │ └── test_with_binaryen.py ├── test_run_ccoverage.py └── util │ ├── __init__.py │ ├── test_file_system_helpers.py │ ├── test_fork_join.py │ ├── test_hg_helpers.py │ ├── test_os_ops.py │ └── test_sm_compile_helpers.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore - List of filenames git should ignore 2 | 3 | 4 | ############################### 5 | # Generated by other programs # 6 | ############################### 7 | 8 | *~ 9 | *.pyc 10 | .DS_Store 11 | .gdb_history 12 | .vscode/ 13 | *.swp 14 | .idea/ 15 | 16 | # Code coverage 17 | .coverage* 18 | 19 | # Linting via pytest 20 | /.cache/ 21 | /.pytest_cache/ 22 | 23 | # mypy 24 | /.mypy_cache/ 25 | 26 | # Installing via `pip install --user --upgrade -e` - develop mode 27 | src/funfuzz.egg-info/ 28 | 29 | # Ignore ESLint node_modules 30 | node_modules/ 31 | 32 | ######################## 33 | # Generated by funfuzz # 34 | ######################## 35 | 36 | wlog* 37 | wtmp* 38 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Patched for v8 fuzzing. Please see [Running funfuzz](https://github.com/amarekano/v8-jsfunfuzz#running-funfuzz) 2 | 3 | [![Build Status](https://travis-ci.org/MozillaSecurity/funfuzz.svg?branch=master)](https://travis-ci.org/MozillaSecurity/funfuzz) [![codecov](https://codecov.io/gh/MozillaSecurity/funfuzz/branch/master/graph/badge.svg)](https://codecov.io/gh/MozillaSecurity/funfuzz) 4 | 5 | This repository contains several JavaScript-based fuzzers. [jsfunfuzz](js/jsfunfuzz) tests JavaScript engines and can run in a JavaScript shell, compare_jit compares output from SpiderMonkey using different flags, while randorderfuzz throws in random tests from the mozilla-central directory into generated jsfunfuzz output. 6 | 7 | Most of the code other than testcase generation is written in Python: restarting the program when it exits or crashes, noticing evidence of new bugs from the program's output, [reducing testcases](https://github.com/MozillaSecurity/lithium/), and [identifying when regressions were introduced](src/funfuzz/autobisectjs/README.md). 8 | 9 | 10 | ## Setup 11 | 12 | Install the required pip packages using `pip install -r requirements.txt` (assuming you are in the funfuzz repository). 13 | 14 | Some parts of the fuzzer will only activate if the Python scripts can find your mozilla-central tree: 15 | ``` 16 | mkdir -p ~/trees/ 17 | hg clone https://hg.mozilla.org/mozilla-central/ ~/trees/mozilla-central/ 18 | ``` 19 | 20 | Some parts of the harness assume a clean **Mercurial** clone of the mozilla trees. There is insufficient testing with Git for now - please file an issue if you hit problems with Git repositories of mozilla trees. 21 | 22 | If you want to use these scripts to compile SpiderMonkey, install the usual prerequisites for [building SpiderMonkey](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Build_Documentation). There are [additional requirements for building with Address Sanitizer](https://developer.mozilla.org/en-US/docs/Mozilla/Testing/Firefox_and_Address_Sanitizer). 23 | 24 | ### Windows (only 64-bit supported) 25 | 26 | 1. Install [MozillaBuild](https://wiki.mozilla.org/MozillaBuild) (Using compile_shell for SpiderMonkey requires at least version 3.2). 27 | 2. Install [Git](https://git-scm.com/) to clone these funfuzz repositories. 28 | 3. Install [Debugging Tools for Windows](https://msdn.microsoft.com/en-us/windows/hardware/hh852365.aspx) to get cdb.exe and thus stacks from crashes. 29 | 4. Make sure you install at least Microsoft Visual Studio 2017 (Community Edition is recommended) as per the build instructions above in the Setup section. 30 | 5. Run `start-shell.bat` to get a MSYS shell. You can use Git by calling its absolute path, e.g. `/c/Program\ Files/Git/bin/git.exe`. 31 | 1. Run the batch file with administrator privileges to get gflags analysis working correctly. 32 | 33 | 34 | ### Mac 35 | 36 | 1. If you encounter problems accessing the compiler, try re-running this command: 37 | 38 | ```xcode-select --install``` 39 | 40 | especially after updating major/minor OS versions. This sometimes manifests on Mac OS X Combo updates. 41 | 42 | 2. Install LLVM via Homebrew, to get llvm-symbolizer needed for symbolizing ASan crash stacks. 43 | 44 | ``` 45 | brew install llvm 46 | ``` 47 | 48 | 49 | ### Linux 50 | 51 | 1. To ensure your core dumps don't get mixed up when multiple instances crash at the same time, run: 52 | 53 | ``` 54 | echo -n 1 | sudo tee /proc/sys/kernel/core_uses_pid 55 | ``` 56 | 2. Install 32-bit libraries to compile 32-bit binaries: 57 | * Debian/Ubuntu: ```sudo apt-get install lib32z1 gcc-multilib g++-multilib``` 58 | * Fedora: (Fedora is known to work, however the exact library names are unknown for now.) 59 | 3. Install gdb: 60 | * Debian/Ubuntu: ```sudo apt-get install gdb``` 61 | * Fedora: Please ensure that all development packages are installed (see ```rpm -qa "*devel"```), and run ```yum install gdb``` 62 | 4. Install clang for clang/ASan builds: 63 | * Debian/Ubuntu: ```sudo apt-get install clang``` 64 | * Clang is used for 64-bit builds, while GCC is used for some older 32-bit builds 65 | 66 | 67 | ## Running funfuzz 68 | 69 | To run **only the js fuzzer** on a pre-compiled v8 build 70 | 71 | `python -m funfuzz.js.loop v8 /path/to/v8/shell` 72 | 73 | To run **only the js fuzzers** which compiles shells with random configurations every 8 hours and tests them: 74 | 75 | ` -u funfuzz.loop_bot -b "--random" --target-time 28800 | tee ~/log-loop_botPy.txt` 76 | 77 | To test **a patch** (assuming patch is in `~/patch.diff`) against a specific branch (assuming **Mercurial** mozilla-inbound is in `~/trees/mozilla-inbound`), using a debug 64-bit deterministic shell configuration, every 8 hours: 78 | 79 | ` -u funfuzz.loop_bot -b "--enable-debug --enable-more-deterministic -R ~/trees/mozilla-inbound -P ~/patch.diff" --target-time 28800 | tee ~/log-loop_botPy.txt` 80 | 81 | In js mode, loop_bot makes use of: 82 | 83 | * [compile_shell](js/compile_shell.py) 84 | * [jsfunfuzz](src/funfuzz/js/jsfunfuzz) 85 | * [compare_jit](src/funfuzz/js/compare_jit.py) (if testing deterministic builds) 86 | * randorderfuzz (included in funfuzz, if tests are present in the mozilla repository) 87 | * funbind (Linux-only, included in funfuzz, if [binaryen](https://github.com/WebAssembly/binaryen/releases) can be downloaded) 88 | * [autobisectjs](src/funfuzz/autobisectjs/README.md) (if the mozilla repository is present). 89 | 90 | The parameters in `-b` get passed into [compile_shell](js/compile_shell.py) and [autobisectjs](src/funfuzz/autobisectjs/README.md). 91 | 92 | You will also need to need a `~/.fuzzmanagerconf` file, similar to: 93 | 94 | ``` 95 | [Main] 96 | serverhost = 97 | serverport = 98 | serverproto = https 99 | serverauthtoken = 100 | sigdir = /Users//sigcache/ 101 | tool = jsfunfuzz 102 | ``` 103 | 104 | Replace anything between `<` and `>` with your desired parameters. 105 | 106 | ## FAQ: 107 | 108 | **Q: What platforms does funfuzz run on?** 109 | 110 | **A:** compile_shell has been tested on: 111 | 112 | * Windows 10 with [MozillaBuild 3.2](https://wiki.mozilla.org/MozillaBuild) 113 | * macOS 10.13 and 10.14 114 | * Ubuntu 18.04 LTS (only LTS versions supported going forward) 115 | 116 | Fedora Linux and openSUSE Leap (42.3 and later) have not been tested extensively and there may be a few bugs along the way. 117 | 118 | The following operating systems are less common and while they may still work, be prepared to **expect issues** along the way: 119 | 120 | * Windows 7, 8 / Windows 8.1 121 | * Windows Server 2012 R2 122 | * Ubuntu Linux 16.04 LTS (install Python 3.6 via a PPA) 123 | * Ubuntu Linux 15.10 and prior 124 | 125 | Support for the following operating systems **have been removed**: 126 | 127 | * Windows Vista, Windows XP and earlier 128 | * Mac OS X 10.12 and earlier 129 | * Ubuntu Linux 13.10 and earlier 130 | * Ubuntu (and variants) on [ARM ODROID boards](http://www.hardkernel.com/main/main.php) 131 | 132 | **Q: What version of Python does funfuzz require?** 133 | 134 | **A:** Python 3.6+ 135 | -------------------------------------------------------------------------------- /after-fix.txt: -------------------------------------------------------------------------------- 1 | # https://github.com/jruderman/after-fix 2 | 3 | contents src/funfuzz/*.py 4 | contents src/funfuzz/util/*.py 5 | contents src/funfuzz/js/*.py 6 | contents src/funfuzz/js/shared/*.js 7 | contents src/funfuzz/js/jsfunfuzz/*.js 8 | -------------------------------------------------------------------------------- /cleanup_run_linters_fast_pytests.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -ex 2 | 3 | # Run this here in the funfuzz directory, e.g.: 4 | # ./cleanup_run_linters_fast_pytests.sh 5 | 6 | # Run shellcheck and bashate first 7 | find . -type d \( -name venv \) -prune -o -type f -name "*.sh" -print0 | 8 | xargs -n 1 -0 shellcheck 9 | find . -type d \( -name venv \) -prune -o -type f -name "*.sh" -print0 | 10 | xargs -n 1 -0 bashate 11 | 12 | # This script runs flake8, pytest "not slow" and pylint tests if they are installed in python3 13 | # Define the location of python3 using the $PY3_PATH variable, else `command -v python3` is used 14 | PY3="${PY3_PATH:-$(command -v python3)}" 15 | FLAKE8_EC=0 16 | PYLINT_EC=0 17 | PYTEST_EC=0 18 | 19 | # Remove *.pyc, *.pyo and __pycache__ directories first 20 | CURR_PATHLIB_PATH="for p in __import__('pathlib').Path('.')" 21 | $PY3 -c "$CURR_PATHLIB_PATH.rglob('*.py[co]'): p.unlink()" 22 | $PY3 -c "$CURR_PATHLIB_PATH.rglob('__pycache__'): p.rmdir()" 23 | 24 | # Run flake8 25 | if $PY3 -m flake8 --version > /dev/null 2>&1; then 26 | $PY3 -m flake8 . || { 27 | FLAKE8_EC=$?; 28 | printf '%s\n' "flake8 found errors, exiting early." >&2; 29 | exit "$FLAKE8_EC"; 30 | }; 31 | echo "flake8 finished running."; 32 | else 33 | echo "flake8 module is not installed in $PY3" 34 | fi 35 | 36 | # Run pytest "not slow" tests 37 | if $PY3 -m pytest --version > /dev/null 2>&1; then 38 | $PY3 -m pytest -q -m "not slow" || { 39 | PYTEST_EC=$?; 40 | printf '%s\n' "pytest found \"not slow\" test errors." >&2; 41 | }; 42 | echo "pytest finished running." 43 | else 44 | echo "pytest module is not installed in $PY3" 45 | fi 46 | 47 | # Run pylint 48 | if $PY3 -m pylint --version > /dev/null 2>&1; then 49 | for i in $(echo ./*/); do 50 | $PY3 -m pylint "$i" || { 51 | PYLINT_EC=$?; printf '%s\n' "pylint found errors." >&2; 52 | }; 53 | done 54 | echo "pylint finished running." 55 | else 56 | echo "pytest module is not installed in $PY3" 57 | fi 58 | 59 | # Output exit code 1 if either flake8 or pylint or pytest ran into errors 60 | if (( FLAKE8_EC || PYTEST_EC || PYLINT_EC )); then 61 | exit 1; 62 | fi 63 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | git+https://github.com/MozillaSecurity/FuzzManager@master 2 | git+https://github.com/MozillaSecurity/lithium@master 3 | . 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """setuptools install script""" 8 | 9 | from setuptools import find_packages 10 | from setuptools import setup 11 | 12 | EXTRAS = { 13 | "test": [ 14 | "codecov==2.0.15", 15 | "coverage==4.5.3", 16 | "distro>=1.3.0", 17 | "flake8==3.7.7", 18 | "flake8-commas==2.0.0", 19 | "flake8-isort==2.7.0", 20 | "flake8-quotes==2.0.1", 21 | "isort==4.3.20", 22 | "pylint==2.3.1", 23 | "pytest==4.6.3", 24 | "pytest-cov==2.7.1", 25 | "pytest-flake8==1.0.4", 26 | "pytest-pylint==0.14.0", 27 | ]} 28 | 29 | 30 | if __name__ == "__main__": 31 | setup(name="funfuzz", 32 | version="0.7.0a1", 33 | entry_points={ 34 | "console_scripts": ["funfuzz = funfuzz.bot:main"], 35 | }, 36 | package_data={"funfuzz": [ 37 | "autobisectjs/*", 38 | "ccoverage/*", 39 | "js/*", 40 | "js/jsfunfuzz/*", 41 | "js/shared/*", 42 | "util/*", 43 | ]}, 44 | package_dir={"": "src"}, 45 | packages=find_packages(where="src"), 46 | install_requires=[ 47 | "boto>=2.49.0", 48 | "fasteners>=0.14.1,<0.15", 49 | # https://www.mercurial-scm.org/wiki/SupportedPythonVersions#Python_3.x_support 50 | # "mercurial>=4.7.2", # Mercurial does not support Python 3 yet 51 | "requests>=2.20.1", 52 | ], 53 | extras_require=EXTRAS, 54 | python_requires=">=3.6", 55 | zip_safe=False) 56 | -------------------------------------------------------------------------------- /src/funfuzz/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # flake8: noqa 3 | # pylint: disable=missing-docstring 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | -------------------------------------------------------------------------------- /src/funfuzz/__main__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # pylint: disable=missing-docstring 3 | # 4 | # This Source Code Form is subject to the terms of the Mozilla Public 5 | # License, v. 2.0. If a copy of the MPL was not distributed with this 6 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | 8 | from .bot import main 9 | 10 | main() 11 | -------------------------------------------------------------------------------- /src/funfuzz/autobisectjs/README.md: -------------------------------------------------------------------------------- 1 | autobisectjs will help you to find out when a changeset introduced problems. It can also point at a changeset that may have exposed the issue. 2 | 3 | It helps with work allocation: 4 | 5 | * The engineer that most recently worked on the code is the one most likely to know how to fix the bug. 6 | * If not, the engineer may be able to forward to someone more knowledgeable. 7 | 8 | ## Find changeset that introduced problems using autobisectjs 9 | 10 | For SpiderMonkey, use the following while compiling locally: 11 | 12 | ` -m funfuzz.autobisectjs -p "--fuzzing-safe --no-threads --ion-eager testcase.js" -b "--enable-debug --enable-more-deterministic"` 13 | 14 | assuming the testcase requires "--fuzzing-safe --no-threads --ion-eager" as runtime flags. 15 | 16 | This will take about: 17 | 18 | * **45 - 60 minutes** on a relatively recent powerful computer on Linux / Mac 19 | * assuming each compilation takes about 3 minutes 20 | * we should be able to find the problem within 16+ tests. 21 | * **2 hours** on Windows 22 | * where each compilation is assumed to take 6 minutes. 23 | 24 | If you have an internet connection, and the testcase causes problems with: 25 | 26 | * a [downloaded js shell](https://archive.mozilla.org/pub/mozilla.org/firefox/tinderbox-builds/mozilla-central-macosx64-debug/latest/jsshell-mac64.zip) 27 | * these problems started happening within the last month 28 | 29 | you can try bisecting using downloaded builds: 30 | 31 | ` -m funfuzz.autobisectjs -p "--fuzzing-safe --no-threads --ion-eager testcase.js" -b "--enable-debug" -T` 32 | 33 | This should take < 5 minutes total assuming a fast internet connection, since it does not need to compile shells. 34 | 35 | Refer to [compile_shell documentation](../js/README.md) for parameters to be passed into "-b". 36 | -------------------------------------------------------------------------------- /src/funfuzz/autobisectjs/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # flake8: noqa 3 | # pylint: disable=missing-docstring 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | -------------------------------------------------------------------------------- /src/funfuzz/autobisectjs/__main__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # pylint: disable=missing-docstring 3 | # 4 | # This Source Code Form is subject to the terms of the Mozilla Public 5 | # License, v. 2.0. If a copy of the MPL was not distributed with this 6 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | 8 | from .autobisectjs import main 9 | 10 | if __name__ == "__main__": 11 | main() 12 | -------------------------------------------------------------------------------- /src/funfuzz/autobisectjs/examples.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | 3 | To try this yourself, run the following commands with the testcases from the bug numbers pasted into the file, e.g. "1188586.js" contains the testcase from [bug 1188586](https://bugzilla.mozilla.org/show_bug.cgi?id=1188586). 4 | 5 | * To test when a bug was introduced by **downloading mozilla-inbound builds** from Mozilla: 6 | 7 | ``` -m funfuzz.autobisectjs -p "--fuzzing-safe --no-threads --ion-eager 1188586.js" -b "--enable-debug" -T``` 8 | 9 | However, this only works effectively if the bug was recent, because builds are only stored per-push within the past month. 10 | 11 | * The equivalent command using **local compiled builds** is: 12 | 13 | ``` -m funfuzz.autobisectjs -p "--fuzzing-safe --no-threads --ion-eager 1188586.js" -b "--enable-debug --enable-more-deterministic"``` 14 | 15 | * To **test branches**, e.g. on mozilla-inbound instead (or any other release branch including ESR), assuming the *Mercurial* repository is cloned to "~/trees/mozilla-inbound": 16 | 17 | ``` -m funfuzz.autobisectjs -p "--fuzzing-safe --no-threads --ion-eager 1188586.js" -b "--enable-debug --enable-more-deterministic -R ~/trees/mozilla-inbound"``` 18 | 19 | * During bisection, perhaps the testcase used to crash in the past; however we are only interested in the assertion failure. You can make autobisectjs look out for the **assertion failure message**: 20 | 21 | ``` -m funfuzz.autobisectjs -p "--fuzzing-safe --no-threads --ion-eager 1188586.js" -b "--enable-debug --enable-more-deterministic" -o "Assertion failure"``` 22 | 23 | * To look out for a particular **exit code**, use "-w": 24 | 25 | ``` -m funfuzz.autobisectjs -p "--fuzzing-safe --no-threads --ion-eager 1189137.js" -b "--enable-debug --enable-more-deterministic" -w 3``` 26 | 27 | * To specify **starting and ending revisions**, use "-s" and "-e": 28 | 29 | ``` -m funfuzz.autobisectjs -s 7820fd141998 -e 'parents(322487136b28)' -p "--no-threads --ion-eager --unboxed-objects 1189137.js" -b "--enable-debug --enable-more-deterministic" -o "Assertion failed"``` 30 | 31 | This method can be used to find when a regression was introduced as well as when a bug got fixed. 32 | 33 | * Or, the testcase is **intermittent** and only reproduces once every 5 tries. autobisectjs can be set to use the "range" interestingness test to retest 50 times before concluding if it is interesting or not: 34 | 35 | ``` -m funfuzz.autobisectjs -p "--fuzzing-safe --no-threads --ion-eager 1188586.js" -b "--enable-debug --enable-more-deterministic" -i range 1 50 crashes --timeout=3``` 36 | 37 | Note that this requires the [lithium repository](https://github.com/MozillaSecurity/lithium) to be cloned adjacent to the funfuzz repository. 38 | 39 | You could specify the assertion message this way too: 40 | 41 | ``` -m funfuzz.autobisectjs -p "--fuzzing-safe --no-threads --ion-eager 1188586.js" -b "--enable-debug --enable-more-deterministic" -i range 1 50 outputs --timeout=3 'Assertion failure'``` 42 | 43 | "-i" should be the last argument on the command line. 44 | 45 | * To bisect **bugs found by compare_jit**: 46 | 47 | ``` -m funfuzz.autobisectjs -s 6ec4eb9786d8 -p 1183423.js -b "--enable-debug --enable-more-deterministic -R ~/trees/mozilla-central" -i funfuzz.js.compare_jit --minlevel=6 mozilla-central``` 48 | -------------------------------------------------------------------------------- /src/funfuzz/autobisectjs/faq.md: -------------------------------------------------------------------------------- 1 | ## FAQ 2 | 3 | **Q: Compilation is broken! How do I use autobisectjs to see when the builds broke?** 4 | 5 | Specify the failing revision hash in the "-e" parameter, along with desired "-b" options to build the desired SpiderMonkey build configuration. This failing revision hash must be from **Mercurial**. 6 | 7 | ``` -m funfuzz.autobisectjs -l bad -e FAILINGREV -b "--enable-debug --enable-more-deterministic"``` 8 | 9 | When done, find the first working revision hash after the breakage, as below. 10 | 11 | **Q: Compilation has finished breaking. How do I know when the builds were working again?** 12 | 13 | Similar to the above, but use "-s" instead of "-e". 14 | 15 | ``` -m funfuzz.autobisectjs -l bad -s FAILINGREV -b "--enable-debug --enable-more-deterministic``` 16 | 17 | **Q: What should I do with the known broken changeset ranges to prevent autobisectjs from retesting those revisions?** 18 | 19 | (This assumes you have the first bad and first good revision hashes as per the 2 questions above.) 20 | 21 | You can add them to the known broken range functions in [known_broken_earliest_working](known_broken_earliest_working.py). Add the first bad and first good changeset **Mercurial** hashes of the build breakage and its fix, along with a short comment. 22 | 23 | **Q: The testcase is giving out assorted varied exit codes as it gets executed by older binaries. How can I fixate to a particular interesting exit code?** 24 | 25 | Pass in the "-w" argument along with the desired exit code to autobisectjs. If it is negative, e.g. -11, pass in 128 - (**exit code number**), e.g. 128 - (-11) = 128 + 11 = 139, or "-w 139". 26 | 27 | **Q: The testcase is intermittent and giving weird results! What should I do to try and get more reliable results?** 28 | 29 | You can try using interestingness tests to look out for the desired symptom, see [the examples](examples.md). 30 | 31 | **Q: What happens when a new operating system is released, and we now have a new changeset hash that has to be updated as the earliest known working revision?** 32 | 33 | You can add the earliest known working **Mercurial** revision to the earliestKnownWorkingRev function in [known_broken_earliest_working](known_broken_earliest_working.py). 34 | 35 | **Q: Does autobisectjs work on nightly SpiderMonkey js shells yet?** 36 | 37 | No, not yet. Currently it only uses ["tinderbox-builds" js shells](https://archive.mozilla.org/pub/mozilla.org/firefox/tinderbox-builds/mozilla-inbound-macosx64-debug/) by default, which are stored on a per-checkin basis only for the past month. Patches accepted! 38 | 39 | **Q: How does autobisectjs compare with [mozregression](http://mozilla.github.io/mozregression/)?** 40 | 41 | When autobisectjs was proposed and written in 2009, mozregression did not exist yet. Since 2010, both have been developed independently of each other. 42 | 43 | autobisectjs was [first written](https://bugzilla.mozilla.org/show_bug.cgi?id=482536) (in Bash) [with results](https://bugzilla.mozilla.org/show_bug.cgi?id=476655#c8) in March 2009. 44 | 45 | mozregression had its [first landing](https://github.com/mozilla/mozregression/commit/d50509b36cb6ba45d7c54917f528bdf482d2c5e6) in February 2010. 46 | 47 | autobisectjs supports bisections using compiled and downloaded (tinderbox-builds) SpiderMonkey js shells, while mozregression supports nightly and inbound builds [for various Mozilla products](http://mozilla.github.io/mozregression/). 48 | -------------------------------------------------------------------------------- /src/funfuzz/autobisectjs/known_broken_earliest_working.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Known broken changeset ranges of SpiderMonkey are specified in this file. 8 | """ 9 | 10 | import platform 11 | 12 | from pkg_resources import parse_version 13 | 14 | 15 | def hgrange(first_bad, first_good): # pylint: disable=missing-param-doc,missing-return-doc,missing-return-type-doc 16 | # pylint: disable=missing-type-doc 17 | """Like "first_bad::first_good", but includes branches/csets that never got the first_good fix.""" 18 | # NB: mercurial's descendants(x) includes x 19 | # So this revset expression includes first_bad, but does not include first_good. 20 | # NB: hg log -r "(descendants(id(badddddd)) - descendants(id(baddddddd)))" happens to return the empty set, 21 | # like we want" 22 | return f"(descendants(id({first_bad}))-descendants(id({first_good})))" 23 | 24 | 25 | def known_broken_ranges(options): # pylint: disable=missing-param-doc,missing-return-doc,missing-return-type-doc 26 | # pylint: disable=missing-type-doc 27 | """Return a list of revsets corresponding to known-busted revisions.""" 28 | # Paste numbers into: https://hg.mozilla.org/mozilla-central/rev/ to get hgweb link. 29 | # To add to the list: 30 | # - (1) will tell you when the brokenness started 31 | # - (1) -m funfuzz.autobisectjs --compilationFailedLabel=bad -e FAILINGREV 32 | # - (2) will tell you when the brokenness ended 33 | # - (2) -m funfuzz.autobisectjs --compilationFailedLabel=bad -s FAILINGREV 34 | 35 | # ANCIENT FIXME: It might make sense to avoid (or note) these in checkBlameParents. 36 | 37 | skips = [ 38 | # Fx60, broken spidermonkey 39 | hgrange("4c72627cfc6c2dafb4590637fe1f3b5a24e133a4", "926f80f2c5ccaa5b0374b48678d62c304cbc9a68"), 40 | # Fx63, broken spidermonkey 41 | hgrange("1fb7ddfad86d5e085c4f2af23a2519d37e45a3e4", "5202cfbf8d60ffbb1ad9c385eda725992fc43d7f"), 42 | # Fx64, broken spidermonkey 43 | hgrange("aae4f349fa588aa844cfb14fae278b776aed6cb7", "c5fbbf959e23a4f33d450cb6c64ef739e09fbe13"), 44 | # Fx66, broken spidermonkey 45 | hgrange("f611bc50d11cae1f48cc44d1468f2c34ec46e287", "39d0c50a2209e0f0c982b1d121765c9dc950e161"), 46 | ] 47 | 48 | if platform.system() == "Darwin": 49 | skips.extend([ 50 | # Fx68, see bug 1544418 51 | hgrange("3d0236f985f83c6b2f4800f814c004e0a2902468", "32cef42080b1f7443dfe767652ea44e0dafbfd9c"), 52 | ]) 53 | 54 | if platform.system() == "Linux": 55 | skips.extend([ 56 | # Fx56-57, failure specific to GCC 5 (and probably earlier) - supposedly works on GCC 6, see bug 1386011 57 | hgrange("e94dceac80907abd4b579ddc8b7c202bbf461ec7", "516c01f62d840744648768b6fac23feb770ffdc1"), 58 | ]) 59 | if platform.machine() == "aarch64": 60 | skips.extend([ 61 | # Fx54, see bug 1336344 62 | hgrange("e8bb22053e65e2a82456e9243a07af023a8ebb13", "999757e9e5a576c884201746546a3420a92f7447"), 63 | ]) 64 | if not options.disableProfiling: 65 | skips.extend([ 66 | # Fx54-55, to bypass the following month-long breakage, use "--disable-profiling", see bug 1339190 67 | hgrange("aa1da5ed8a0719e0ab424e672d2f477b70ef593c", "5a03382283ae0a020b2a2d84bbbc91ff13cb2130"), 68 | ]) 69 | 70 | if platform.system() == "Windows": 71 | skips.extend([ 72 | # Fx69, see bug 1560432 73 | hgrange("b314f6c6148efb8909c3483eb2a49117049a06cd", "e996920037965b669fe3fd6306d6f8bee0ebc8bf"), 74 | ]) 75 | 76 | if not options.enableDbg: 77 | skips.extend([ 78 | # Fx58-59, broken opt builds w/ --enable-gczeal 79 | hgrange("c5561749c1c64793c31699d46bbf12cc0c69815c", "f4c15a88c937e8b3940f5c1922142a6ffb137320"), 80 | # Fx66, broken opt builds w/ --enable-gczeal 81 | hgrange("247e265373eb26566e94303fa42b1237b80295d9", "e4aa68e2a85b027c5498bf8d8f379b06d07df6c2"), 82 | ]) 83 | 84 | if options.enableMoreDeterministic: 85 | skips.extend([ 86 | # Fx68, see bug 1542980 87 | hgrange("427b854cdb1c47ce6a643f83245914d66dca4382", "4c4e45853808229f832e32f6bcdbd4c92a72b13b"), 88 | ]) 89 | 90 | if options.enableSimulatorArm32: 91 | skips.extend([ 92 | # Fx57-61, broken 32-bit ARM-simulator builds 93 | hgrange("284002382c21842a7ebb39dcf53d5d34fd3f7692", "05669ce25b032bf83ca38e082e6f2c1bf683ed19"), 94 | ]) 95 | 96 | return skips 97 | 98 | 99 | def earliest_known_working_rev(_options, flags, skip_revs): # pylint: disable=missing-param-doc,missing-return-doc 100 | # pylint: disable=missing-return-type-doc,missing-type-doc,too-many-branches,too-complex,too-many-statements 101 | """Return a revset which evaluates to the first revision of the shell that compiles with |options| 102 | and runs jsfunfuzz successfully with |flags|.""" 103 | # Only support at least Mac OS X 10.13 104 | assert (not platform.system() == "Darwin") or (parse_version(platform.mac_ver()[0]) >= parse_version("10.13")) 105 | 106 | cpu_count_flag = False 107 | for entry in flags: # flags is a list of flags, and the option must exactly match. 108 | if "--cpu-count=" in entry: 109 | cpu_count_flag = True 110 | 111 | required = [] 112 | 113 | # These should be in descending order, or bisection will break at earlier changesets. 114 | if "--enable-experimental-fields" in flags: # 1st w/--enable-experimental-fields, see bug 1529758 115 | required.append("7a1ad6647c22bd34a6c70e67dc26e5b83f71cea4") # m-c 463705 Fx67 116 | # Note that m-c rev 457581:4b74d76e55a819852c8fa925efd25c57fdf35c9d is the first with BigInt on by default 117 | if set(["--wasm-compiler=none", "--wasm-compiler=baseline+ion", "--wasm-compiler=baseline", "--wasm-compiler=ion", 118 | "--wasm-compiler=cranelift"]).intersection(flags): # 1st w/--wasm-compiler=none/, see bug 1509441 119 | required.append("48dc14f79fb0a51ca796257a4179fe6f16b71b14") # m-c 455252 Fx66 120 | if "--more-compartments" in flags: # 1st w/--more-compartments, see bug 1518753 121 | required.append("450b8f0cbb4e494b399ebcf23a33b8d9cb883245") # m-c 453627 Fx66 122 | if "--no-streams" in flags: # 1st w/ working --no-streams, see bug 1501734 123 | required.append("c6a8b4d451afa922c4838bd202749c7e131cf05e") # m-c 442977 Fx65 124 | if platform.system() == "Windows": # 1st w/ working Windows builds with a recent Win10 SDK, see bug 1485224 125 | required.append("b2a536ba5d4bbf0be909652caee1d2d4d63ddcb4") # m-c 436503 Fx64 126 | if "--wasm-gc" in flags: # 1st w/--wasm-gc, see bug 1445272 127 | required.append("302befe7689abad94a75f66ded82d5e71b558dc4") # m-c 413255 Fx61 128 | if "--nursery-strings=on" in flags or \ 129 | "--nursery-strings=off" in flags: # 1st w/--nursery-strings=on, see bug 903519 130 | required.append("321c29f4850882a2f0220a4dc041c53992c47992") # m-c 406115 Fx60 131 | if "--spectre-mitigations=on" in flags or \ 132 | "--spectre-mitigations=off" in flags: # 1st w/--spectre-mitigations=on, see bug 1430053 133 | required.append("a98f615965d73f6462924188fc2b1f2a620337bb") # m-c 399868 Fx59 134 | if "--test-wasm-await-tier2" in flags: # 1st w/--test-wasm-await-tier2, see bug 1388785 135 | required.append("b1dc87a94262c1bf2747d2bf560e21af5deb3174") # m-c 387188 Fx58 136 | if platform.system() == "Darwin": # 1st w/ successful Xcode 9 builds, see bug 1366564 137 | required.append("e2ecf684f49e9a6f6d072c289df68ef679968c4c") # m-c 383101 Fx58 138 | if cpu_count_flag: # 1st w/--cpu-count=, see bug 1206770 139 | required.append("1b55231e6628e70f0c2ee2b2cb40a1e9861ac4b4") # m-c 380023 Fx57 140 | # 1st w/ revised template literals, see bug 1317375 141 | required.append("bb868860dfc35876d2d9c421c037c75a4fb9b3d2") # m-c 330353 Fx53 142 | 143 | return f"first(({common_descendants(required)}) - ({skip_revs}))" 144 | 145 | 146 | def common_descendants(revs): # pylint: disable=missing-docstring,missing-return-doc,missing-return-type-doc 147 | return " and ".join(f"descendants({r})" for r in revs) 148 | -------------------------------------------------------------------------------- /src/funfuzz/ccoverage/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # flake8: noqa 3 | # pylint: disable=missing-docstring 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | -------------------------------------------------------------------------------- /src/funfuzz/ccoverage/gatherer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Gathers coverage data. 8 | """ 9 | 10 | import configparser 11 | import io 12 | import logging 13 | import platform 14 | import subprocess 15 | 16 | from ..bot import JS_SHELL_DEFAULT_TIMEOUT 17 | from ..js.loop import many_timed_runs 18 | from ..util import create_collector 19 | 20 | RUN_COV_LOG = logging.getLogger("funfuzz") 21 | RUN_COV_TIME = 85000 # 85,000 seconds is just under a day 22 | 23 | 24 | def gather_coverage(dirpath): 25 | """Gathers coverage data. 26 | 27 | Args: 28 | dirpath (Path): Directory in which build is to be downloaded in. 29 | 30 | Returns: 31 | Path: Path to the coverage results file 32 | """ 33 | RUN_COV_LOG.info("Coverage build is being run in the following directory: %s", str(dirpath)) 34 | bin_name = f'js{".exe" if platform.system() == "Windows" else ""}' 35 | cov_build_bin_path = dirpath / "cov-build" / "dist" / "bin" / bin_name 36 | assert cov_build_bin_path.is_file() 37 | loop_args = ["--compare-jit", "--random-flags", 38 | str(JS_SHELL_DEFAULT_TIMEOUT), "KNOWNPATH", str(cov_build_bin_path), "--fuzzing-safe"] 39 | 40 | RUN_COV_LOG.info("Fuzzing a coverage build for %s seconds...", str(RUN_COV_TIME)) 41 | many_timed_runs(RUN_COV_TIME, dirpath, loop_args, create_collector.make_collector(), True) 42 | RUN_COV_LOG.info("Finished fuzzing the coverage build") 43 | 44 | fm_conf = configparser.ConfigParser() 45 | fm_conf.read(str(dirpath / "cov-build" / "dist" / "bin" / "js.fuzzmanagerconf")) 46 | RUN_COV_LOG.info("Generating grcov data...") 47 | cov_output = subprocess.run([str(dirpath / "grcov-bin" / "grcov"), str(dirpath), 48 | "-t", "coveralls+", 49 | "--commit-sha", fm_conf.get("Main", "product_version"), 50 | "--token", "NONE", 51 | "--guess-directory-when-missing", 52 | "-p", "/srv/jenkins/jobs/coverage-clone-mozilla-central/workspace/"], 53 | check=True, 54 | stdout=subprocess.PIPE).stdout.decode("utf-8", errors="replace") 55 | RUN_COV_LOG.info("Finished generating grcov data") 56 | 57 | RUN_COV_LOG.info("Writing grcov data to disk...") 58 | cov_output_file = dirpath / "results_cov.json" 59 | with io.open(str(cov_output_file), "w", encoding="utf-8", errors="replace") as f: 60 | f.write(cov_output) 61 | RUN_COV_LOG.info("Finished writing grcov data to disk") 62 | 63 | return cov_output_file 64 | -------------------------------------------------------------------------------- /src/funfuzz/ccoverage/get_build.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Downloads coverage builds and other coverage utilities, such as grcov. 8 | """ 9 | 10 | import io 11 | import logging 12 | from pathlib import Path 13 | import platform 14 | import tarfile 15 | import zipfile 16 | 17 | import requests 18 | 19 | from ..js.inspect_shell import queryBuildConfiguration 20 | 21 | RUN_COV_LOG = logging.getLogger("funfuzz") 22 | 23 | 24 | def get_coverage_build(dirpath, args): 25 | """Gets a coverage build from a specified server. 26 | 27 | Args: 28 | dirpath (Path): Directory in which build is to be downloaded in. 29 | args (class): Command line arguments. 30 | 31 | Returns: 32 | Path: Path to the js coverage build 33 | """ 34 | RUN_COV_LOG.info("Downloading coverage build zip file into %s from %s", str(dirpath), args.url) 35 | with requests.get(args.url, stream=True) as f: 36 | build_request_data = io.BytesIO(f.content) 37 | 38 | RUN_COV_LOG.info("Extracting coverage build zip file...") 39 | build_zip = zipfile.ZipFile(build_request_data) 40 | extract_folder = dirpath / "cov-build" 41 | extract_folder.mkdir(parents=True, exist_ok=True) # Ensure this dir has been created 42 | # In 3.5 <= Python < 3.6, .extractall does not automatically create intermediate folders that do not exist 43 | build_zip.extractall(str(extract_folder.resolve())) 44 | RUN_COV_LOG.info("Coverage build zip file extracted to this folder: %s", extract_folder.resolve()) 45 | 46 | js_cov_bin_name = f'js{".exe" if platform.system() == "Windows" else ""}' 47 | js_cov_bin = extract_folder / "dist" / "bin" / js_cov_bin_name 48 | 49 | Path.chmod(js_cov_bin, Path.stat(js_cov_bin).st_mode | 0o111) # Ensure the js binary is executable 50 | assert js_cov_bin.is_file() 51 | 52 | # Check that the binary is non-debug. 53 | assert not queryBuildConfiguration(js_cov_bin, "debug") 54 | assert queryBuildConfiguration(js_cov_bin, "coverage") 55 | 56 | js_cov_fmconf = extract_folder / "dist" / "bin" / f"{js_cov_bin_name}.fuzzmanagerconf" 57 | assert js_cov_fmconf.is_file() 58 | 59 | # Check that a coverage build with *.gcno files are present 60 | js_cov_unified_gcno = extract_folder / "js" / "src" / "Unified_cpp_js_src0.gcno" 61 | assert js_cov_unified_gcno.is_file() 62 | 63 | return js_cov_bin 64 | 65 | 66 | def get_grcov(dirpath, args): 67 | """Gets a grcov binary. 68 | 69 | Args: 70 | dirpath (Path): Directory in which build is to be downloaded in. 71 | args (class): Command line arguments. 72 | 73 | Raises: 74 | OSError: Raises if the current platform is neither Windows, Linux nor macOS 75 | 76 | Returns: 77 | Path: Path to the grcov binary file 78 | """ 79 | append_os = "win" if platform.system() == "Windows" else ("osx" if platform.system() == "Darwin" else "linux") 80 | grcov_filename_with_ext = f"grcov-{append_os}-x86_64.tar.bz2" 81 | 82 | grcov_url = f"https://github.com/marco-c/grcov/releases/download/v{args.grcov_ver}/{grcov_filename_with_ext}" 83 | 84 | RUN_COV_LOG.info("Downloading grcov into %s from %s", str(dirpath), grcov_url) 85 | with requests.get(grcov_url, allow_redirects=True, stream=True) as grcov_request: 86 | RUN_COV_LOG.info("Extracting grcov tarball...") 87 | grcov_bin_folder = dirpath / "grcov-bin" 88 | grcov_bin_folder.mkdir(parents=True, exist_ok=True) # Ensure this dir has been created for Python 3.5 reasons 89 | with tarfile.open(fileobj=io.BytesIO(grcov_request.content), mode="r:bz2") as f: 90 | f.extractall(str(grcov_bin_folder.resolve())) 91 | 92 | RUN_COV_LOG.info("grcov tarball extracted to this folder: %s", grcov_bin_folder.resolve()) 93 | grcov_bin = grcov_bin_folder / f'grcov{".exe" if platform.system() == "Windows" else ""}' 94 | assert grcov_bin.is_file() 95 | 96 | return grcov_bin 97 | -------------------------------------------------------------------------------- /src/funfuzz/ccoverage/reporter.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Reports coverage build results to CovManager. 8 | """ 9 | 10 | from copy import deepcopy 11 | import logging 12 | import os 13 | 14 | from CovReporter import CovReporter 15 | from EC2Reporter import EC2Reporter 16 | 17 | RUN_COV_LOG = logging.getLogger("funfuzz") 18 | 19 | 20 | def disable_pool(): 21 | """Disables coverage pool on collection completion.""" 22 | spotman_env_var_name = "EC2SPOTMANAGER_POOLID" 23 | test_env = deepcopy(os.environ) 24 | if spotman_env_var_name in test_env: # pragma: no cover 25 | pool_id = test_env[spotman_env_var_name] 26 | RUN_COV_LOG.info("About to disable EC2SpotManager pool ID: %s", pool_id) 27 | EC2Reporter.main(argv=["--disable", str(pool_id)]) 28 | RUN_COV_LOG.info("Pool disabled!") 29 | else: 30 | RUN_COV_LOG.info("No pools were disabled, as the %s environment variable was not found", spotman_env_var_name) 31 | 32 | 33 | def report_coverage(cov_results): 34 | """Reports coverage results. 35 | 36 | Args: 37 | cov_results (Path): Path to the coverage .json results 38 | """ 39 | RUN_COV_LOG.info("Submitting to CovManager...") 40 | assert not CovReporter.main(argv=["--repository", "mozilla-central", 41 | "--tool", "jsfunfuzz", 42 | "--submit", str(cov_results)]) 43 | RUN_COV_LOG.info("Submission complete!") 44 | -------------------------------------------------------------------------------- /src/funfuzz/js/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "parserOptions": { 3 | "ecmaVersion": 2018, 4 | "ecmaFeatures": { 5 | "jsx": true 6 | }, 7 | // "sourceType": "module" 8 | }, 9 | 10 | "env": { 11 | "es6": true, 12 | // "node": true, 13 | "mocha": true 14 | }, 15 | 16 | "plugins": [ 17 | "import", 18 | "jsdoc", 19 | "node", 20 | "promise", 21 | "standard" 22 | ], 23 | 24 | "globals": { 25 | "o": true, 26 | "document": false, 27 | "navigator": false, 28 | "window": false 29 | }, 30 | 31 | "rules": { 32 | // Adapted from https://github.com/pyoor/DOMfuzz2/blob/612fc89851fd1179324179affbed6b3be3fe5bea/.eslintrc.json 33 | // Standard style related rules 34 | "accessor-pairs": "error", 35 | "arrow-spacing": ["error", { "before": true, "after": true }], 36 | "block-spacing": ["error", "always"], 37 | "brace-style": ["error", "1tbs", { "allowSingleLine": true }], 38 | "camelcase": ["error", { "properties": "never" }], 39 | "comma-dangle": ["error", { 40 | "arrays": "never", 41 | "objects": "never", 42 | "imports": "never", 43 | "exports": "never", 44 | "functions": "never" 45 | }], 46 | "comma-spacing": ["error", { "before": false, "after": true }], 47 | "comma-style": ["off"], 48 | "constructor-super": "error", 49 | "curly": ["error", "multi-line"], 50 | "dot-location": ["error", "property"], 51 | "eol-last": "error", 52 | "eqeqeq": ["error", "always", { "null": "ignore" }], 53 | "func-call-spacing": ["error", "never"], 54 | "generator-star-spacing": ["error", { "before": true, "after": true }], 55 | "handle-callback-err": ["error", "^(err|error)$" ], 56 | "indent": ["error", 2, { 57 | "SwitchCase": 1, 58 | "VariableDeclarator": 1, 59 | "outerIIFEBody": 1, 60 | "MemberExpression": 1, 61 | "FunctionDeclaration": { "parameters": 1, "body": 1 }, 62 | "FunctionExpression": { "parameters": 1, "body": 1 }, 63 | "CallExpression": { "arguments": 1 }, 64 | "ArrayExpression": 1, 65 | "ObjectExpression": 1, 66 | "ImportDeclaration": 1, 67 | "flatTernaryExpressions": false, 68 | "ignoreComments": false 69 | }], 70 | "key-spacing": ["error", { "beforeColon": false, "afterColon": true }], 71 | "keyword-spacing": ["error", { "before": true, "after": true }], 72 | "new-cap": ["error", { "newIsCap": true, "capIsNew": false }], 73 | "new-parens": "error", 74 | "no-array-constructor": "error", 75 | "no-caller": "error", 76 | "no-class-assign": "error", 77 | "no-compare-neg-zero": "error", 78 | "no-cond-assign": "error", 79 | "no-const-assign": "error", 80 | "no-constant-condition": ["error", { "checkLoops": false }], 81 | "no-control-regex": "error", 82 | "no-debugger": "error", 83 | "no-delete-var": "error", 84 | "no-dupe-args": "error", 85 | "no-dupe-class-members": "error", 86 | "no-dupe-keys": "error", 87 | "no-duplicate-case": "error", 88 | "no-empty-character-class": "error", 89 | "no-empty-pattern": "error", 90 | "no-eval": "error", 91 | "no-ex-assign": "error", 92 | "no-extend-native": "error", 93 | "no-extra-bind": "error", 94 | "no-extra-boolean-cast": "error", 95 | "no-extra-parens": ["error", "functions"], 96 | "no-fallthrough": "error", 97 | "no-floating-decimal": "error", 98 | "no-func-assign": "error", 99 | "no-global-assign": "error", 100 | "no-implied-eval": "error", 101 | "no-inner-declarations": ["error", "functions"], 102 | "no-invalid-regexp": "error", 103 | "no-irregular-whitespace": "error", 104 | "no-iterator": "error", 105 | "no-label-var": "error", 106 | "no-labels": ["error", { "allowLoop": false, "allowSwitch": false }], 107 | "no-lone-blocks": "error", 108 | "no-mixed-operators": ["error", { 109 | "groups": [ 110 | ["==", "!=", "===", "!==", ">", ">=", "<", "<="], 111 | ["&&", "||"], 112 | ["in", "instanceof"] 113 | ], 114 | "allowSamePrecedence": true 115 | }], 116 | "no-mixed-spaces-and-tabs": "error", 117 | "no-multi-spaces": "error", 118 | "no-multi-str": "error", 119 | "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }], 120 | "no-negated-in-lhs": "error", 121 | "no-new": "error", 122 | "no-new-func": "error", 123 | "no-new-object": "error", 124 | "no-new-require": "error", 125 | "no-new-symbol": "error", 126 | "no-new-wrappers": "error", 127 | "no-obj-calls": "error", 128 | "no-octal": "error", 129 | "no-octal-escape": "error", 130 | "no-path-concat": "error", 131 | "no-proto": "error", 132 | "no-redeclare": "error", 133 | "no-regex-spaces": "error", 134 | "no-return-assign": ["error", "except-parens"], 135 | "no-return-await": "error", 136 | "no-self-assign": "error", 137 | "no-self-compare": "error", 138 | "no-sequences": "error", 139 | "no-shadow": "error", 140 | "no-shadow-restricted-names": "error", 141 | "no-sparse-arrays": "error", 142 | "no-tabs": "error", 143 | "no-template-curly-in-string": "error", 144 | "no-this-before-super": "error", 145 | "no-throw-literal": "error", 146 | "no-trailing-spaces": "error", 147 | "no-undef": "error", 148 | "no-undef-init": "error", 149 | "no-unexpected-multiline": "error", 150 | "no-unmodified-loop-condition": "error", 151 | "no-unneeded-ternary": ["error", { "defaultAssignment": false }], 152 | "no-unreachable": "error", 153 | "no-unsafe-finally": "error", 154 | "no-unsafe-negation": "error", 155 | "no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true, "allowTaggedTemplates": true }], 156 | "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }], 157 | "no-use-before-define": ["error", { "functions": false, "classes": false, "variables": false }], 158 | "no-useless-call": "error", 159 | "no-useless-computed-key": "error", 160 | "no-useless-constructor": "error", 161 | "no-useless-escape": "error", 162 | "no-useless-rename": "error", 163 | "no-useless-return": "error", 164 | "no-whitespace-before-property": "error", 165 | "no-with": "error", 166 | "object-curly-spacing": ["error", "always"], 167 | "object-property-newline": ["error", { "allowMultiplePropertiesPerLine": true }], 168 | "one-var": ["error", { "initialized": "never" }], 169 | // jsfunfuzz has mostly been written in the current style for a long time, may help line-based reducer Lithium too 170 | "operator-linebreak": "off", 171 | "padded-blocks": ["error", { "blocks": "never", "switches": "never", "classes": "never" }], 172 | "prefer-promise-reject-errors": "error", 173 | // jsfunfuzz has mostly been written in the current style for a long time 174 | "quotes": ["error", "double", { "avoidEscape": true, "allowTemplateLiterals": true }], 175 | "rest-spread-spacing": ["error", "never"], 176 | "semi": ["error", "always"], 177 | "semi-spacing": ["error", { "before": false, "after": true }], 178 | "space-before-blocks": ["error", "always"], 179 | "space-before-function-paren": ["error", "always"], 180 | "space-in-parens": ["error", "never"], 181 | "space-infix-ops": "error", 182 | "space-unary-ops": ["error", { "words": true, "nonwords": false }], 183 | "spaced-comment": ["error", "always", { 184 | "line": { "markers": ["*package", "!", "/", ",", "="] }, 185 | "block": { "balanced": true, "markers": ["*package", "!", ",", ":", "::", "flow-include"], "exceptions": ["*"] } 186 | }], 187 | "symbol-description": "error", 188 | "template-curly-spacing": ["error", "never"], 189 | "template-tag-spacing": ["error", "never"], 190 | "unicode-bom": ["error", "never"], 191 | "use-isnan": "error", 192 | "valid-typeof": ["error", { "requireStringLiterals": true }], 193 | "wrap-iife": ["error", "any", { "functionPrototypeMethods": true }], 194 | "yield-star-spacing": ["error", "both"], 195 | "yoda": ["error", "never"], 196 | 197 | "import/export": "error", 198 | "import/first": "error", 199 | "import/no-duplicates": "error", 200 | "import/no-named-default": "error", 201 | "import/no-webpack-loader-syntax": "error", 202 | 203 | "node/no-deprecated-api": "error", 204 | "node/process-exit-as-throw": "error", 205 | 206 | "promise/param-names": "error", 207 | 208 | "standard/array-bracket-even-spacing": ["error", "either"], 209 | "standard/computed-property-even-spacing": ["error", "even"], 210 | "standard/no-callback-literal": "error", 211 | "standard/object-curly-even-spacing": ["error", "either"], 212 | 213 | // JSDoc related rules 214 | "jsdoc/check-param-names": 1, 215 | "jsdoc/check-tag-names": 1, 216 | "jsdoc/check-types": 1, 217 | "jsdoc/newline-after-description": 1, 218 | "jsdoc/require-description-complete-sentence": 0, 219 | "jsdoc/require-hyphen-before-param-description": 1, 220 | "jsdoc/require-param": 1, 221 | "jsdoc/require-param-description": 1, 222 | "jsdoc/require-param-type": 1, 223 | "jsdoc/require-returns-description": 0, 224 | "jsdoc/require-returns-type": 1, 225 | "require-jsdoc": [ 226 | 1, 227 | { 228 | "require": { 229 | "FunctionDeclaration": true, 230 | "MethodDefinition": true, 231 | "ClassDeclaration": true, 232 | "ArrowFunctionExpression": false 233 | } 234 | } 235 | ], 236 | "valid-jsdoc": [ 237 | "error", 238 | { 239 | "requireParamDescription": false, 240 | "requireReturnDescription": false, 241 | "requireReturn": false 242 | } 243 | ] 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/funfuzz/js/README.md: -------------------------------------------------------------------------------- 1 | ## Compile SpiderMonkey using compile_shell 2 | 3 | To compile a SpiderMonkey shell, run: 4 | 5 | ` -m funfuzz.js.compile_shell -b "--enable-debug --enable-more-deterministic -R ~/trees/mozilla-central"` 6 | 7 | in order to get a debug 64-bit deterministic shell, off the **Mercurial** repository located at `~/trees/mozilla-central`. 8 | 9 | Clone the repository to that location using: 10 | 11 | `hg clone https://hg.mozilla.org/mozilla-central/ ~/trees/mozilla-central` 12 | 13 | assuming the `~/trees` folder is created and present. 14 | 15 | ## Additional information 16 | * compile_shell 17 | * [More examples](examples.md) 18 | * [FAQ](faq.md) 19 | -------------------------------------------------------------------------------- /src/funfuzz/js/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # flake8: noqa 3 | # pylint: disable=missing-docstring 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | -------------------------------------------------------------------------------- /src/funfuzz/js/examples.md: -------------------------------------------------------------------------------- 1 | ### Examples: 2 | 3 | * To compile a debug 64-bit deterministic shell, do: 4 | 5 | ` -m funfuzz.js.compile_shell -b "--enable-debug --enable-more-deterministic -R ~/trees/mozilla-central"` 6 | 7 | * To compile an optimized 32-bit shell, do: 8 | 9 | ` -m funfuzz.js.compile_shell -b "--32 --enable-optimize -R ~/trees/mozilla-central"` 10 | 11 | By default, js should compile an optimized shell even without --enable-optimize explicitly specified. 12 | 13 | * To compile a debug 32-bit ARM-simulator shell, do: 14 | 15 | ` -m funfuzz.js.compile_shell -b "--32 --enable-debug --enable-simulator=arm -R ~/trees/mozilla-central"` 16 | 17 | * To compile a debug 64-bit shell with AddressSanitizer (ASan) support, do: 18 | 19 | ` -m funfuzz.js.compile_shell -b "--enable-debug --enable-address-sanitizer -R ~/trees/mozilla-central"` 20 | 21 | Note that this uses git to clone a specific known working revision of LLVM into `~/llvm`, compiles it, then uses this specific revision to compile SpiderMonkey. 22 | 23 | * To compile an optimized 64-bit shell with Valgrind support, do: 24 | 25 | ` -m funfuzz.js.compile_shell -b "--enable-optimize --enable-valgrind -R ~/trees/mozilla-central"` 26 | 27 | * To test a patch with a debug 64-bit deterministic shell, do: 28 | 29 | ` -m funfuzz.js.compile_shell -b "--enable-debug --enable-more-deterministic -R ~/trees/mozilla-central -P "` 30 | 31 | Note that this **requires mq to be activated** in Mercurial and assumes that there are **no patches** in the patch queue. 32 | 33 | * To compile a debug 64-bit deterministic shell from a specific mozilla-central revision, do: 34 | 35 | ` -m funfuzz.js.compile_shell -b "--enable-debug --enable-more-deterministic -R ~/trees/mozilla-central" -r ` 36 | 37 | -------------------------------------------------------------------------------- /src/funfuzz/js/faq.md: -------------------------------------------------------------------------------- 1 | ### FAQ: 2 | 3 | **Q: Why is "--enable-more-deterministic" recommended?** 4 | 5 | Fuzzing with this mode on allows us to run compare_jit, which runs testcases generated by jsfunfuzz using different flags and compares the output. In order to compare successfully, the shell should generate consistent output everytime with a fixed input. Since we do not ship deterministic shells by default, if testing deterministic shells, we do not run with compare_jit. 6 | 7 | **Q: What are average build times for SpiderMonkey?** 8 | 9 | On a decent Linux machine or a powerful Mac, both with 4 or more cores, 3-4 minutes on average. On Windows, probably 5-10 minutes. On an ARM ODROID board, up to an hour. 10 | 11 | **Q: How do I get a shell with a patch to be compiled together?** 12 | 13 | Use the -P notation, e.g.: 14 | 15 | ` -m funfuzz.js.compile_shell -b "--enable-debug --enable-more-deterministic -R ~/trees/mozilla-inbound -P ~/patch.diff"` 16 | 17 | assuming: 18 | * mq is activated in `~/.hgrc` 19 | * There are no other patches in the mq stack 20 | * Patch is in `~/patch.diff` and can be applied cleanly 21 | * Test by first doing `patch -p1 --dry-run < ~/patch.diff` in the base directory of the repository listed by -R, or the default. 22 | * There is only one patch needed. If more than one patch is needed, first do a roll-up patch. 23 | 24 | **Q: Do these build configure flags get passed into the js configure scripts?** 25 | 26 | No, they are independent. We only implemented the flags that are most useful for fuzzing in the harness. 27 | 28 | **Q: Will the gecko-dev Git mirror of mozilla-central be supported?** 29 | 30 | The "-R" flag assumes a Mercurial clone of mozilla-central is passed in as an argument. Git repositories are not yet supported fully, and especially not for autobisectjs. See [issue #2](https://github.com/MozillaSecurity/funfuzz/issues/2). 31 | 32 | **Q: Can I run multiple instances of compile_shell?** 33 | 34 | This is not recommended as it will slow down your computer. Running one instance of compile_shell will use the maximum number of cores as found by cpu_count() from the Python multiprocessing module, with the exception of ARM boards that tend to have slower cores. 35 | 36 | **Q: What kind of build does compile_shell do, and what files do it store on my machine?** 37 | 38 | It creates a clobber build, compiling in the `~/shell-cache` directory by default. There may also be a bunch of tempfiles created in the system temporary directory. 39 | 40 | **Q: How do I check if the SpiderMonkey build created is the one I specified?** 41 | 42 | Post-compilation, the harness does a bunch of verification tests to ensure that the desired build is created. To double check, run `getBuildConfiguration();` within the SpiderMonkey shell. If compile_shell isn't compiling as desired, file an issue! 43 | 44 | **Q: What happens if the build I want to compile causes a compilation error?** 45 | 46 | A `.busted` file is created in the `~/shell-cache` directory. This will notify the harness not to retry in the future since it is busted. However, if there is an error in the harness, file an issue detailing the build configurations and steps to reproduce, and once it is fixed, remove the corresponding file/directory in `~/shell-cache` and retry again. 47 | 48 | **Q: After compiling many shells, I'm now running out of disk space! What should I do?** 49 | 50 | Oops! You can remove the `~/shell-cache` directory to reclaim space, and reboot to clear system temporary directories. 51 | -------------------------------------------------------------------------------- /src/funfuzz/js/files_to_link.txt: -------------------------------------------------------------------------------- 1 | jsfunfuzz/preamble.js 2 | 3 | jsfunfuzz/detect-engine.js 4 | jsfunfuzz/avoid-known-bugs.js 5 | jsfunfuzz/error-reporting.js 6 | 7 | shared/random.js 8 | shared/mersenne-twister.js 9 | shared/testing-functions.js 10 | 11 | jsfunfuzz/built-in-constructors.js 12 | 13 | jsfunfuzz/mess-tokens.js 14 | jsfunfuzz/mess-grammar.js 15 | 16 | jsfunfuzz/gen-asm.js 17 | jsfunfuzz/gen-math.js 18 | jsfunfuzz/gen-grammar.js 19 | jsfunfuzz/gen-proxy.js 20 | jsfunfuzz/gen-recursion.js 21 | jsfunfuzz/gen-regex.js 22 | jsfunfuzz/gen-stomp-on-registers.js 23 | jsfunfuzz/gen-type-aware-code.js 24 | 25 | jsfunfuzz/test-asm.js 26 | jsfunfuzz/test-math.js 27 | jsfunfuzz/test-regex.js 28 | jsfunfuzz/test-consistency.js 29 | jsfunfuzz/test-misc.js 30 | 31 | jsfunfuzz/driver.js 32 | 33 | jsfunfuzz/run-reduction-marker.js 34 | 35 | jsfunfuzz/run-in-sandbox.js 36 | jsfunfuzz/run.js 37 | jsfunfuzz/tail.js 38 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/README.md: -------------------------------------------------------------------------------- 1 | *jsfunfuzz* creates random JavaScript function bodies (including invalid ones) to test many parts of JavaScript engines. 2 | 3 | The largest module of jsfunfuzz is [gen-grammar.js](gen-grammar.js). thinking loosely in terms of "statements", "expressions", "lvalues", "literals", etc. It's almost a context-free grammar fuzzer... |cat| and |totallyRandom| especially make it seem like one. 4 | 5 | Once it creates a function body, it does the following things with it: 6 | * Splits it in half and tries to compile each half, mostly to find bugs in the compiler's error-handling. 7 | * Compiles it 8 | * Executes it 9 | * If executing returned a generator, loops through the generator. 10 | 11 | 12 | ## Running jsfunfuzz 13 | 14 | To test an existing SpiderMonkey shell called `./js`, run: 15 | 16 | ` -m funfuzz.js.loop --random-flags --compare-jit 20 mozilla-central ./js` 17 | 18 | * `--random-flags` tells it to use [shell_flags](../shell_flags.py) to 19 | * `--compare-jit` tells it to run [compare_jit](../compare_jit.py) on most of the generated code, detecting bugs where adding optimization flags like --ion-eager changes the output. 20 | * `20` tells it to kill any instance that runs for more than 20 seconds 21 | * `mozilla-central` or any other string is no longer used, and this argument will be removed in the future. 22 | 23 | If loop detects a new bug, it will run [Lithium](https://github.com/MozillaSecurity/lithium/) to reduce the testcase. It will call Lithium with either [js_interesting](../js_interesting.py) or [compare_jit](../compare_jit.py) as the "interestingness test". 24 | 25 | Using [funfuzz.bot](../../bot.py), you can automate downloading or building new versions of the SpiderMonkey shell, and running several instances of loop for parallelism. 26 | 27 | Through randorderfuzz, if the harness detects tests in the mozilla-central tree, it may load or incorporate tests into its fuzzing input in a random order. 28 | 29 | 30 | ## Contributors 31 | 32 | * [Jesse Ruderman](https://twitter.com/jruderman) wrote most of the fuzzer 33 | * [Gary Kwong](https://twitter.com/nth10sd) wrote a lot of the Python 34 | * [Christian Holler](https://twitter.com/mozdeco) improved the compilation scripts 35 | * [Jan de Mooij](https://twitter.com/jandemooij) prototyped [stress-testing objects and PICs](https://bugzilla.mozilla.org/show_bug.cgi?id=6309960) 36 | * [David Keeler](https://twitter.com/mozkeeler) modified the regular expression generator to also generate (almost-)matching strings, based on an idea from [Oliver Hunt](https://twitter.com/ohunt). 37 | * [Jesse Schwartzentruber](https://github.com/jschwartzentruber/) reviewed a lot of the Python harness improvements 38 | * [The SpiderMonkey team](https://twitter.com/SpiderMonkeyJS) fixed over 2000 of our bugs, so we could keep fuzzing! 39 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/avoid-known-bugs.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported whatToTest */ 7 | /* global engine, ENGINE_JAVASCRIPTCORE, ENGINE_SPIDERMONKEY_TRUNK, gcIsQuiet, jsshell */ 8 | 9 | /* eslint-disable complexity, no-multi-spaces */ 10 | function whatToTestSpidermonkeyTrunk (code) { /* eslint-disable-line require-jsdoc */ 11 | // regexps can't match across lines, so replace whitespace with spaces. 12 | var codeL = code.replace(/\s/g, " "); 13 | 14 | return { 15 | 16 | allowParse: true, 17 | 18 | allowExec: unlikelyToHang(code) 19 | && (jsshell || code.indexOf("nogeckoex") === -1) 20 | , 21 | 22 | // Ideally we'd detect whether the shell was compiled with --enable-more-deterministic 23 | // Ignore both within-process & across-process, e.g. nestTest mismatch & compare_jit 24 | expectConsistentOutput: true 25 | && (gcIsQuiet || code.indexOf("gc") === -1) 26 | && code.indexOf("/*NODIFF*/") === -1 // Ignore diff testing on these labels 27 | && code.indexOf(".script") === -1 // Debugger; see bug 1237464 28 | && code.indexOf(".parameterNames") === -1 // Debugger; see bug 1237464 29 | && code.indexOf(".environment") === -1 // Debugger; see bug 1237464 30 | && code.indexOf(".onNewGlobalObject") === -1 // Debugger; see bug 1238246 31 | && code.indexOf(".takeCensus") === -1 // Debugger; see bug 1247863 32 | && code.indexOf(".findScripts") === -1 // Debugger; see bug 1250863 33 | && code.indexOf("Date") === -1 // time marches on 34 | && code.indexOf("backtrace") === -1 // shows memory addresses 35 | && code.indexOf("drainAllocationsLog") === -1 // drainAllocationsLog returns an object with a timestamp, see bug 1066313 36 | && code.indexOf("dumpObject") === -1 // shows heap addresses 37 | && code.indexOf("dumpHeap") === -1 // shows heap addresses 38 | && code.indexOf("dumpStringRepresentation") === -1 // shows memory addresses 39 | && code.indexOf("evalInCooperativeThread") === -1 // causes diffs especially in --no-threads 40 | && code.indexOf("evalInWorker") === -1 // causes diffs in --no-threads vs --ion-offthread-compile=off 41 | && code.indexOf("getBacktrace") === -1 // getBacktrace returns memory addresses which differs depending on flags 42 | && code.indexOf("getLcovInfo") === -1 43 | && code.indexOf("load") === -1 // load()ed regression test might output dates, etc 44 | && code.indexOf("offThreadCompileScript") === -1 // causes diffs in --no-threads vs --ion-offthread-compile=off 45 | && code.indexOf("oomAfterAllocations") === -1 46 | && code.indexOf("oomAtAllocation") === -1 47 | && code.indexOf("oomTest") === -1 // causes diffs in --ion-eager vs --baseline-eager 48 | && code.indexOf("printProfilerEvents") === -1 // causes diffs in --ion-eager vs --baseline-eager 49 | && code.indexOf("promiseID") === -1 // Promise IDs are for debugger-use only 50 | && code.indexOf("runOffThreadScript") === -1 51 | && code.indexOf("shortestPaths") === -1 // See bug 1308743 52 | && code.indexOf("inIon") === -1 // may become true after several iterations, or return a string with --no-ion 53 | && code.indexOf("inJit") === -1 // may become true after several iterations, or return a string with --no-baseline 54 | && code.indexOf("random") === -1 55 | && code.indexOf("timeout") === -1 // time runs and crawls 56 | , 57 | 58 | expectConsistentOutputAcrossIter: true 59 | // within-process, e.g. ignore the following items for nestTest mismatch 60 | && code.indexOf("options") === -1 // options() is per-cx, and the js shell doesn't create a new cx for each sandbox/compartment 61 | , 62 | 63 | expectConsistentOutputAcrossJITs: true 64 | // across-process (e.g. running js shell with different run-time options) e.g. compare_jit 65 | && code.indexOf("isAsmJSCompilationAvailable") === -1 // Causes false positives with --no-asmjs 66 | && code.indexOf("'strict") === -1 // see bug 743425 67 | && code.indexOf("disassemble") === -1 // see bug 1237403 (related to asm.js) 68 | && code.indexOf("sourceIsLazy") === -1 // see bug 1286407 69 | && code.indexOf("getAllocationMetadata") === -1 // see bug 1296243 70 | && code.indexOf(".length") === -1 // bug 1027846 71 | /* eslint-disable no-control-regex */ 72 | && !(codeL.match(/\/.*[\u0000\u0080-\uffff]/)) // doesn't stay valid utf-8 after going through python (?) 73 | /* eslint-enable no-control-regex */ 74 | 75 | }; 76 | } 77 | /* eslint-enable complexity, no-multi-spaces */ 78 | 79 | function whatToTestJavaScriptCore (code) { /* eslint-disable-line require-jsdoc */ 80 | return { 81 | 82 | allowParse: true, 83 | allowExec: unlikelyToHang(code), 84 | expectConsistentOutput: false, 85 | expectConsistentOutputAcrossIter: false, 86 | expectConsistentOutputAcrossJITs: false 87 | 88 | }; 89 | } 90 | 91 | function whatToTestGeneric (code) { /* eslint-disable-line require-jsdoc */ 92 | return { 93 | allowParse: true, 94 | allowExec: unlikelyToHang(code), 95 | expectConsistentOutput: false, 96 | expectConsistentOutputAcrossIter: false, 97 | expectConsistentOutputAcrossJITs: false 98 | }; 99 | } 100 | 101 | var whatToTest; 102 | if (engine === ENGINE_SPIDERMONKEY_TRUNK) { whatToTest = whatToTestSpidermonkeyTrunk; } else if (engine === ENGINE_JAVASCRIPTCORE) { whatToTest = whatToTestJavaScriptCore; } else { whatToTest = whatToTestGeneric; } 103 | 104 | function unlikelyToHang (code) { /* eslint-disable-line require-jsdoc */ 105 | var codeL = code.replace(/\s/g, " "); 106 | 107 | // Things that are likely to hang in all JavaScript engines 108 | return true 109 | && code.indexOf("infloop") === -1 110 | && !(codeL.match(/for.*in.*uneval/)) // can be slow to loop through the huge string uneval(this), for example 111 | && !(codeL.match(/for.*for.*for/)) // nested for loops (including for..in, array comprehensions, etc) can take a while 112 | && !(codeL.match(/for.*for.*gc/)) 113 | ; 114 | } 115 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/built-in-constructors.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* global dumpln, jsshell, print, Proxy, quit, uneval */ 7 | 8 | /* 9 | It might be more interesting to use Object.getOwnPropertyDescriptor to find out if 10 | a thing is exposed as a getter (like Debugger.prototype.enabled). But there are exceptions: 11 | 12 | why is Array.prototype.length not a getter? http://pastebin.mozilla.org/1990723 13 | backward compatibility 14 | ES3 already allowed programs to create objects with arbitrary __proto__ 15 | .length was specified to work as a data property; accessor properties inherit differently, especially when setting 16 | maybe only when setting, come to think of it 17 | I guess it could've been made an accessor property without breaking anything important. I didn't realize it at the time. 18 | */ 19 | 20 | var constructors = []; // "Array" 21 | var builtinFunctions = []; // "Array.prototype.sort" 22 | var builtinProperties = []; // "Array", "Array.prototype", "Array.prototype.length" 23 | var allMethodNames = []; // "sort" 24 | var allPropertyNames = []; // "length" 25 | 26 | var builtinObjectNames = []; // "Array", "Array.prototype", ... (indexes into the builtinObjects) 27 | var builtinObjects = {}; // { "Array.prototype": ["sort", "length", ...], ... } 28 | 29 | (function exploreBuiltins (glob, debugMode) { 30 | function exploreDeeper (a, an) { /* eslint-disable-line require-jsdoc */ 31 | if (!a) { return; } 32 | var hns = Object.getOwnPropertyNames(a); 33 | var propertyNames = []; 34 | for (var j = 0; j < hns.length; ++j) { 35 | var hn = hns[j]; 36 | propertyNames.push(hn); 37 | allPropertyNames.push(hn); 38 | 39 | var fullName = `${an}.${hn}`; 40 | builtinProperties.push(fullName); 41 | 42 | var h; 43 | try { 44 | h = a[hn]; 45 | } catch (e) { 46 | if (debugMode) { 47 | dumpln(`Threw: ${fullName}`); 48 | } 49 | h = null; 50 | } 51 | 52 | if (typeof h === "function" && hn !== "constructor") { 53 | allMethodNames.push(hn); 54 | builtinFunctions.push(fullName); 55 | } 56 | } 57 | builtinObjects[an] = propertyNames; 58 | builtinObjectNames.push(an); 59 | } 60 | 61 | function exploreConstructors () { /* eslint-disable-line require-jsdoc */ 62 | var gns = Object.getOwnPropertyNames(glob); 63 | for (var i = 0; i < gns.length; ++i) { 64 | var gn = gns[i]; 65 | // Assume that most uppercase names are constructors. 66 | // Skip Worker in shell (removed in bug 771281). 67 | if (gn.charCodeAt(0) > 0x40 && gn.charCodeAt(0) < 0x60 && gn !== "PerfMeasurement" && !(jsshell && gn === "Worker")) { 68 | var g = glob[gn]; 69 | if (typeof g === "function" && g.toString().indexOf("[native code]") !== -1) { 70 | constructors.push(gn); 71 | builtinProperties.push(gn); 72 | builtinFunctions.push(gn); 73 | exploreDeeper(g, gn); 74 | exploreDeeper(g.prototype, `${gn}.prototype`); 75 | } 76 | } 77 | } 78 | } 79 | 80 | exploreConstructors(); 81 | 82 | exploreDeeper(Math, "Math"); 83 | exploreDeeper(JSON, "JSON"); 84 | exploreDeeper(Proxy, "Proxy"); 85 | 86 | if (debugMode) { 87 | for (let x of constructors) print(`^^^^^ ${x}`); 88 | for (let x of builtinProperties) print(`***** ${x}`); 89 | for (let x of builtinFunctions) print(`===== ${x}`); 90 | for (let x of allMethodNames) print(`!!!!! ${x}`); 91 | for (let x of allPropertyNames) print(`&&&&& ${x}`); 92 | print(uneval(builtinObjects)); 93 | quit(); 94 | } 95 | })(this, false); 96 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/detect-engine.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported gcIsQuiet, loopCount, loopModulo, readline, xpcshell */ 7 | /* global console, debug, gc, print, readline:writable, rnd, uneval:writable, verifyprebarriers, wasmIsSupported */ 8 | /* XPCNativeWrapper */ 9 | 10 | // jsfunfuzz is best run in a command-line shell. It can also run in 11 | // a web browser, but you might have trouble reproducing bugs that way. 12 | 13 | var ENGINE_UNKNOWN = 0; 14 | var ENGINE_SPIDERMONKEY_TRUNK = 1; 15 | var ENGINE_JAVASCRIPTCORE = 4; 16 | 17 | var engine = ENGINE_UNKNOWN; 18 | var jsshell = (typeof window === "undefined"); /* eslint-disable-line no-undef */ 19 | var xpcshell = jsshell && (typeof Components === "object"); /* eslint-disable-line no-undef */ 20 | var dump; 21 | var dumpln; 22 | var printImportant; 23 | if (jsshell) { 24 | dumpln = print; 25 | printImportant = function (s) { dumpln("***"); dumpln(s); }; 26 | if (typeof verifyprebarriers === "function") { 27 | // Run a diff between the help() outputs of different js shells. 28 | // Make sure the function to look out for is not located only in some 29 | // particular #ifdef, e.g. JS_GC_ZEAL, or controlled by --fuzzing-safe. 30 | if (typeof wasmIsSupported === "function") { 31 | engine = ENGINE_SPIDERMONKEY_TRUNK; 32 | } 33 | 34 | // Avoid accidentally waiting for user input that will never come. 35 | readline = function () {}; 36 | } else if (typeof XPCNativeWrapper === "function") { /* eslint-disable-line no-undef */ 37 | // e.g. xpcshell or firefox 38 | engine = ENGINE_SPIDERMONKEY_TRUNK; 39 | } else if (typeof debug === "function") { 40 | engine = ENGINE_JAVASCRIPTCORE; 41 | } 42 | } else { 43 | if (navigator.userAgent.indexOf("WebKit") !== -1) { /* eslint-disable-line no-undef */ 44 | // XXX detect Google Chrome for V8 45 | engine = ENGINE_JAVASCRIPTCORE; 46 | // This worked in Safari 3.0, but it might not work in Safari 3.1. 47 | dump = function (s) { console.log(s); }; 48 | } else if (navigator.userAgent.indexOf("Gecko") !== -1) { /* eslint-disable-line no-undef */ 49 | engine = ENGINE_SPIDERMONKEY_TRUNK; 50 | } else if (typeof dump !== "function") { 51 | // In other browsers, jsfunfuzz does not know how to log anything. 52 | dump = function () { }; 53 | } 54 | dumpln = function (s) { dump(s + "\n"); }; 55 | 56 | printImportant = function (s) { 57 | dumpln(s); 58 | var p = document.createElement("pre"); /* eslint-disable-line no-undef */ 59 | p.appendChild(document.createTextNode(s)); /* eslint-disable-line no-undef */ 60 | document.body.appendChild(p); /* eslint-disable-line no-undef */ 61 | }; 62 | } 63 | 64 | // If WebAssembly object doesn't exist, make it an empty function, else runtime flags like --wasm-compiler=ion throw 65 | if (typeof WebAssembly === "undefined") { this.WebAssembly = function () {}; } 66 | 67 | if (typeof gc === "undefined") { this.gc = function () {}; } 68 | var gcIsQuiet = !(gc()); // see bug 706433 69 | 70 | // If the JavaScript engine being tested has heuristics like 71 | // "recompile any loop that is run more than X times" 72 | // this should be set to the highest such X. 73 | var HOTLOOP = 60; 74 | function loopCount () { return rnd(rnd(HOTLOOP * 3)); } /* eslint-disable-line require-jsdoc */ 75 | function loopModulo () { return (rnd(2) ? rnd(rnd(HOTLOOP * 2)) : rnd(5)) + 2; } /* eslint-disable-line require-jsdoc */ 76 | 77 | function simpleSource (st) { /* eslint-disable-line require-jsdoc */ 78 | function hexify (c) { /* eslint-disable-line require-jsdoc */ 79 | var code = c.charCodeAt(0); 80 | var hex = code.toString(16); 81 | while (hex.length < 4) { hex = `0${hex}`; } 82 | return `\\u${hex}`; 83 | } 84 | 85 | if (typeof st === "string") { 86 | return ("\"" + 87 | st.replace(/\\/g, "\\\\") 88 | .replace(/"/g, "\\\"") 89 | .replace(/\0/g, "\\0") 90 | .replace(/\n/g, "\\n") 91 | .replace(/[^ -~]/g, hexify) + // not space (32) through tilde (126) 92 | "\""); 93 | } else { return `${st}`; } // hope this is right ;) should work for numbers. 94 | } 95 | 96 | var haveRealUneval = (typeof uneval === "function"); 97 | if (!haveRealUneval) { uneval = simpleSource; } 98 | 99 | if (engine === ENGINE_UNKNOWN) { printImportant("Targeting an unknown JavaScript engine!"); } else if (engine === ENGINE_SPIDERMONKEY_TRUNK) { printImportant("Targeting SpiderMonkey / Gecko (trunk)."); } else if (engine === ENGINE_JAVASCRIPTCORE) { printImportant("Targeting JavaScriptCore / WebKit."); } 100 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/driver.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported failsToCompileInTry, start */ 7 | /* global count:writable, dumpln, engine, ENGINE_SPIDERMONKEY_TRUNK, jsshell */ 8 | /* global makeScript, mathInitFCM, print, printImportant, Random, rnd, tryItOut, uneval */ 9 | 10 | function start (glob) { /* eslint-disable-line require-jsdoc */ 11 | var fuzzSeed = Math.floor(Math.random() * Math.pow(2, 28)); 12 | dumpln(`fuzzSeed: ${fuzzSeed}`); 13 | Random.init(fuzzSeed); 14 | 15 | // Split this string across two source strings to ensure that if a 16 | // generated function manages to output the entire jsfunfuzz source, 17 | // that output won't match the grep command. 18 | var cookie = "/*F"; 19 | cookie += `RC-fuzzSeed-${fuzzSeed}*/`; 20 | 21 | // Can be set to true if makeStatement has side effects, such as crashing, so you have to reduce "the hard way". 22 | var dumpEachSeed = false; 23 | 24 | if (dumpEachSeed) { 25 | dumpln(`${cookie}Random.init(0);`); 26 | } 27 | 28 | mathInitFCM(); 29 | 30 | count = 0; 31 | 32 | if (jsshell) { 33 | // If another script specified a "maxRunTime" argument, use it; otherwise, run forever 34 | var MAX_TOTAL_TIME = (glob.maxRunTime) || (Infinity); 35 | var startTime = new Date(); 36 | var lastTime; 37 | 38 | do { 39 | testOne(); 40 | var elapsed1 = new Date() - lastTime; 41 | if (elapsed1 > 1000) { 42 | print(`That took ${elapsed1}ms!`); 43 | } 44 | lastTime = new Date(); 45 | } while (lastTime - startTime < MAX_TOTAL_TIME); 46 | } else { 47 | setTimeout(testStuffForAWhile, 200); /* eslint-disable-line no-undef */ 48 | } 49 | 50 | function testStuffForAWhile () { /* eslint-disable-line require-jsdoc */ 51 | for (var j = 0; j < 100; ++j) { testOne(); } 52 | 53 | if (count % 10000 < 100) { printImportant(`Iterations: ${count}`); } 54 | 55 | setTimeout(testStuffForAWhile, 30); /* eslint-disable-line no-undef */ 56 | } 57 | 58 | function testOne () { /* eslint-disable-line require-jsdoc */ 59 | ++count; 60 | 61 | // Sometimes it makes sense to start with simpler functions: 62 | // var depth = ((count / 1000) | 0) & 16; 63 | var depth = 14; 64 | 65 | if (dumpEachSeed) { 66 | // More complicated, but results in a much shorter script, making SpiderMonkey happier. 67 | var MTA = uneval(Random.twister.export_mta()); 68 | var MTI = Random.twister.export_mti(); 69 | if (MTA !== Random.lastDumpedMTA) { 70 | dumpln(`${cookie}Random.twister.import_mta(${MTA});`); 71 | Random.lastDumpedMTA = MTA; 72 | } 73 | dumpln(`${cookie}Random.twister.import_mti(${MTI}); void (makeScript(${depth}));`); 74 | } 75 | 76 | var code = makeScript(depth); 77 | 78 | if (count === 1 && engine === ENGINE_SPIDERMONKEY_TRUNK && rnd(5)) { 79 | code = `tryRunning = useSpidermonkeyShellSandbox(${rnd(4)});`; 80 | // print("Sane mode!") 81 | } 82 | 83 | // if (rnd(10) === 1) { 84 | // var dp = "/*infloop-deParen*/" + Random.index(deParen(code)); 85 | // if (dp) 86 | // code = dp; 87 | // } 88 | dumpln(`${cookie}count=${count}; tryItOut(${uneval(code)});`); 89 | 90 | tryItOut(code); 91 | } 92 | } 93 | 94 | function failsToCompileInTry (code) { /* eslint-disable-line require-jsdoc */ 95 | // Why would this happen? One way is "let x, x" 96 | try { 97 | var codeInTry = `try { ${code} } catch(e) { }`; 98 | void new Function(codeInTry); /* eslint-disable-line no-new-func */ 99 | return false; 100 | } catch (e) { 101 | return true; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/error-reporting.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported confused, errorstack, errorToString, foundABug */ 7 | /* global dumpln, jsshell, print, printImportant, quit */ 8 | 9 | function confused (s) { /* eslint-disable-line require-jsdoc */ 10 | if (jsshell) { 11 | // Magic string that js_interesting looks for 12 | // Currently disabled until its use can be figured out 13 | // print("jsfunfuzz broke" + " its own scripting environment: " + s); 14 | // Replaced with the following: 15 | print(`jsfunfuzz got confused: ${s}`); 16 | quit(); 17 | } 18 | } 19 | 20 | function foundABug (summary, details) { /* eslint-disable-line require-jsdoc */ 21 | // Magic pair of strings that js_interesting looks for 22 | // Break up the following string so internal js functions do not print it deliberately 23 | let foundMsg = `Found`; 24 | foundMsg += ` a bug: ${summary}`; 25 | printImportant(foundMsg); 26 | if (details) { 27 | printImportant(details); 28 | } 29 | if (jsshell) { 30 | dumpln("jsfunfuzz stopping due to finding a bug."); 31 | quit(); 32 | } 33 | } 34 | 35 | function errorToString (e) { /* eslint-disable-line require-jsdoc */ 36 | try { 37 | return (`${e}`); 38 | } catch (e2) { 39 | return "Can't toString the error!!"; 40 | } 41 | } 42 | 43 | function errorstack () { /* eslint-disable-line require-jsdoc */ 44 | print("EEE"); 45 | try { 46 | void ([].qwerty.qwerty); 47 | } catch (e) { print(e.stack); } 48 | } 49 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/gen-math.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported makeMathFunction, NUM_MATH_FUNCTIONS */ 7 | /* global directivePrologue, makeExpr, numericVals, Random, rnd, TOTALLY_RANDOM, totallyRandom */ 8 | 9 | const NUM_MATH_FUNCTIONS = 6; 10 | 11 | var binaryMathOps = [ 12 | " * ", " / ", " % ", 13 | " + ", " - ", 14 | " ** ", 15 | " << ", " >> ", " >>> ", 16 | " < ", " > ", " <= ", " >= ", 17 | " == ", " != ", 18 | " === ", " !== ", 19 | " & ", " | ", " ^ ", " && ", " || ", 20 | " , " 21 | ]; 22 | 23 | var leftUnaryMathOps = [ 24 | " ! ", " + ", " - ", " ~ " 25 | ]; 26 | 27 | // unaryMathFunctions and binaryMathFunctions updated on 2017-01-21 and added from: 28 | // https://dxr.mozilla.org/mozilla-central/rev/3cedab21a7e65e6a1c4c2294ecfb5502575a46e3/js/src/jsmath.cpp#1330 29 | // Update to the latest revision as needed. 30 | var unaryMathFunctions = [ 31 | "abs", 32 | "acos", 33 | "acosh", 34 | "asin", 35 | "asinh", 36 | "atan", 37 | "atanh", 38 | "cbrt", 39 | "ceil", 40 | "clz32", 41 | "cos", 42 | "cosh", 43 | "exp", 44 | "expm1", 45 | // "floor", // avoid breaking rnd. 46 | "fround", 47 | "log", 48 | "log2", 49 | "log10", 50 | "log1p", 51 | // "random", // avoid breaking rnd. avoid non-determinism. 52 | "round", 53 | "sign", 54 | "sin", 55 | "sinh", 56 | "sqrt", 57 | "tan", 58 | "tanh", 59 | "trunc" 60 | ]; 61 | 62 | // n-ary functions will also be tested with varying numbers of parameters by makeFunction 63 | var binaryMathFunctions = [ 64 | "atan2", 65 | "hypot", // n-ary 66 | "imul", 67 | "max", // n-ary 68 | "min", // n-ary 69 | "pow" 70 | ]; 71 | 72 | function makeMathFunction (d, b, i) { /* eslint-disable-line require-jsdoc */ 73 | if (rnd(TOTALLY_RANDOM) === 2) return totallyRandom(d, b); 74 | 75 | var ivars = ["x", "y"]; 76 | if (rnd(10) === 0) { 77 | // Also use variables from the enclosing scope 78 | ivars = ivars.concat(b); 79 | } 80 | return `(function(x, y) { ${directivePrologue()}return ${makeMathExpr(d, ivars, i)}; })`; 81 | } 82 | 83 | function makeMathExpr (d, b, i) { /* eslint-disable-line require-jsdoc */ 84 | if (rnd(TOTALLY_RANDOM) === 2) return totallyRandom(d, b); 85 | 86 | // As depth decreases, make it more likely to bottom out 87 | if (d < rnd(5)) { 88 | if (rnd(4)) { 89 | return Random.index(b); 90 | } 91 | return Random.index(numericVals); 92 | } 93 | 94 | if (rnd(500) === 0 && d > 0) { return makeExpr(d - 1, b); } 95 | 96 | function r () { return makeMathExpr(d - 1, b, i); } /* eslint-disable-line require-jsdoc */ 97 | 98 | // Frequently, coerce both the inputs and outputs to the same "numeric sub-type" 99 | // (asm.js formalizes this concept, but JITs may have their own variants) 100 | var commonCoercion = rnd(10); 101 | function mc (expr) { /* eslint-disable-line require-jsdoc */ 102 | switch (rnd(3) ? commonCoercion : rnd(10)) { 103 | /* eslint-disable no-multi-spaces */ 104 | case 0: return `( + ${expr})`; // f64 (asm.js) 105 | case 1: return `Math.fround(${expr})`; // f32 106 | case 2: return `(${expr} | 0)`; // i32 (asm.js) 107 | case 3: return `(${expr} >>> 0)`; // u32 108 | default: return expr; 109 | /* eslint-enable no-multi-spaces */ 110 | } 111 | } 112 | 113 | if (i > 0 && rnd(10) === 0) { 114 | // Call a *lower-numbered* mathy function. (This avoids infinite recursion.) 115 | return mc(`mathy${rnd(i)}(${mc(r())}, ${mc(r())})`); 116 | } 117 | 118 | if (rnd(20) === 0) { 119 | return mc(`(${mc(r())} ? ${mc(r())} : ${mc(r())})`); 120 | } 121 | 122 | switch (rnd(4)) { 123 | /* eslint-disable no-multi-spaces */ 124 | case 0: return mc(`(${mc(r())}${Random.index(binaryMathOps)}${mc(r())})`); 125 | case 1: return mc(`(${Random.index(leftUnaryMathOps)}${mc(r())})`); 126 | case 2: return mc(`Math.${Random.index(unaryMathFunctions)}(${mc(r())})`); 127 | default: return mc(`Math.${Random.index(binaryMathFunctions)}(${mc(r())}, ${mc(r())})`); 128 | /* eslint-enable no-multi-spaces */ 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/gen-proxy.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported makeProxyHandler */ 7 | /* global bp:writable, makeExpr, makeFunction, Random, rnd, TOTALLY_RANDOM, totallyRandom */ 8 | 9 | // In addition, can always use "undefined" or makeFunction 10 | // Forwarding proxy code based on http://wiki.ecmascript.org/doku.php?id=harmony:proxies "Example: a no-op forwarding proxy" 11 | // The letter 'x' is special. 12 | var proxyHandlerProperties = { 13 | getOwnPropertyDescriptor: { 14 | empty: "function(target, name) {}", 15 | forward: "function(target, name) { var desc = Reflect.getOwnPropertyDescriptor(x); desc.configurable = true; return desc; }", 16 | throwing: "function(target, name) { return {get: function() { throw 4; }, set: function() { throw 5; }}; }" 17 | }, 18 | defineProperty: { 19 | empty: "function(target, name, desc) {}", 20 | forward: "function(target, name, desc) { return Reflect.defineProperty(x, name, desc); }" 21 | }, 22 | ownKeys: { 23 | empty: "function(target) { return []; }", 24 | forward: "function(target) { return Reflect.ownKeys(x); }" 25 | }, 26 | deleteProperty: { 27 | empty: "function(target, name) { return true; }", 28 | yes: "function(target, name) { return true; }", 29 | no: "function(target, name) { return false; }", 30 | forward: "function(target, name) { return Reflect.deleteProperty(x, name); }" 31 | }, 32 | has: { 33 | empty: "function(target, name) { return false; }", 34 | yes: "function(target, name) { return true; }", 35 | no: "function(target, name) { return false; }", 36 | forward: "function(target, name) { return name in x; }" 37 | }, 38 | get: { 39 | empty: "function(target, name, receiver) { return undefined }", 40 | forward: "function(target, name, receiver) { return Reflect.get(x, name, receiver); }", 41 | bind: "function(target, name, receiver) { var prop = Reflect.get(x, name, receiver); return (typeof prop) === 'function' ? prop.bind(x) : prop; }" 42 | }, 43 | set: { 44 | empty: "function(target, name, val, receiver) { return true; }", 45 | yes: "function(target, name, val, receiver) { return true; }", 46 | no: "function(target, name, val, receiver) { return false; }", 47 | forward: "function(target, name, val, receiver) { return Reflect.set(x, name, val, receiver); }" 48 | }, 49 | getPrototypeOf: { 50 | empty: "function(target) { return null; }", 51 | forward: "function(target) { return Reflect.getPrototypeOf(x); }" 52 | }, 53 | setPrototypeOf: { 54 | yes: "function(target, proto) { return true; }", 55 | no: "function(target, proto) { return false; }", 56 | forward: "function(target, proto) { return Reflect.setPrototypeOf(x, proto); }" 57 | }, 58 | isExtensible: { 59 | yes: "function(target) { return true; }", 60 | no: "function(target) { return false; }", 61 | forward: "function(target) { return Reflect.isExtensible(x); }" 62 | }, 63 | preventExtensions: { 64 | yes: "function(target) { return true; }", 65 | no: "function(target) { return false; }", 66 | forward: "function(target) { return Reflect.preventExtensions(x); }" 67 | }, 68 | apply: { 69 | empty: "function(target, thisArgument, argumentsList) {}", 70 | forward: "function(target, thisArgument, argumentsList) { return Reflect.apply(x, thisArgument, argumentsList); }" 71 | }, 72 | construct: { 73 | empty: "function(target, argumentsList, newTarget) { return []; }", 74 | invalid: "function(target, argumentsList, newTarget) { return 3; }", 75 | forward: "function(target, argumentsList, newTarget) { return Reflect.construct(x, argumentsList, newTarget); }" 76 | } 77 | }; 78 | 79 | function makeProxyHandlerFactory (d, b) { /* eslint-disable-line require-jsdoc */ 80 | if (rnd(TOTALLY_RANDOM) === 2) return totallyRandom(d, b); 81 | 82 | if (d < 1) { return "({/*TOODEEP*/})"; } 83 | 84 | try { // in case we screwed Object.prototype, breaking proxyHandlerProperties 85 | var preferred = Random.index(["empty", "forward", "yes", "no", "bind", "throwing"]); 86 | var fallback = Random.index(["empty", "forward"]); 87 | var fidelity = rnd(10); 88 | 89 | var handlerFactoryText = "(function handlerFactory(x) {"; 90 | handlerFactoryText += "return {"; 91 | 92 | if (rnd(2)) { 93 | // handlerFactory has an argument 'x' 94 | bp = b.concat(["x"]); 95 | } else { 96 | // handlerFactory has no argument 97 | handlerFactoryText = handlerFactoryText.replace(/x/, ""); 98 | bp = b; 99 | } 100 | 101 | for (var p in proxyHandlerProperties) { 102 | var funText; 103 | if (proxyHandlerProperties[p][preferred] && rnd(10) <= fidelity) { 104 | funText = proxyMunge(proxyHandlerProperties[p][preferred], p); 105 | } else { 106 | switch (rnd(7)) { 107 | /* eslint-disable no-multi-spaces */ 108 | case 0: funText = makeFunction(d - 3, bp); break; 109 | case 1: funText = "undefined"; break; 110 | case 2: funText = "function() { throw 3; }"; break; 111 | default: funText = proxyMunge(proxyHandlerProperties[p][fallback], p); 112 | /* eslint-enable no-multi-spaces */ 113 | } 114 | } 115 | handlerFactoryText += `${p}: ${funText}, `; 116 | } 117 | 118 | handlerFactoryText += "}; })"; 119 | 120 | return handlerFactoryText; 121 | } catch (e) { 122 | return "({/* :( */})"; 123 | } 124 | } 125 | 126 | function proxyMunge (funText, p) { /* eslint-disable-line require-jsdoc */ 127 | // funText = funText.replace(/\{/, `{ var yum = 'PCAL'; dumpln(yum + 'LED: ${p}');`); 128 | return funText; 129 | } 130 | 131 | function makeProxyHandler (d, b) { /* eslint-disable-line require-jsdoc */ 132 | if (rnd(TOTALLY_RANDOM) === 2) return totallyRandom(d, b); 133 | 134 | return `${makeProxyHandlerFactory(d, b)}(${makeExpr(d - 3, b)})`; 135 | } 136 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/gen-recursion.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported makeImmediateRecursiveCall */ 7 | /* global makeExpr, makeMixedTypeArray, makeStatement, Random, rnd, uniqueVarName */ 8 | 9 | /* 10 | David Anderson suggested creating the following recursive structures: 11 | - recurse down an array of mixed types, car cdr kinda thing 12 | - multiple recursive calls in a function, like binary search left/right, sometimes calls neither and sometimes calls both 13 | 14 | the recursion support in spidermonkey only works with self-recursion. 15 | that is, two functions that call each other recursively will not be traced. 16 | 17 | two trees are formed, going down and going up. 18 | type instability matters on both sides. 19 | so the values returned from the function calls matter. 20 | 21 | so far, what i've thought of means recursing from the top of a function and if..else. 22 | but i'd probably also want to recurse from other points, e.g. loops. 23 | 24 | special code for tail recursion likely coming soon, but possibly as a separate patch, because it requires changes to the interpreter. 25 | */ 26 | 27 | // "@" indicates a point at which a statement can be inserted. XXX allow use of variables, as consts 28 | // variable names will be replaced, and should be uppercase to reduce the chance of matching things they shouldn't. 29 | // take care to ensure infinite recursion never occurs unexpectedly, especially with doubly-recursive functions. 30 | var recursiveFunctions = [ 31 | { 32 | // Unless the recursive call is in the tail position, this will throw. 33 | text: "(function too_much_recursion(depth) { @; if (depth > 0) { @; too_much_recursion(depth - 1); @ } else { @ } @ })", 34 | vars: ["depth"], 35 | args: function (d, b) { return singleRecursionDepth(d, b); }, 36 | test: function (f) { try { f(5000); } catch (e) { } return true; } 37 | }, 38 | { 39 | text: "(function factorial(N) { @; if (N == 0) { @; return 1; } @; return N * factorial(N - 1); @ })", 40 | vars: ["N"], 41 | args: function (d, b) { return singleRecursionDepth(d, b); }, 42 | test: function (f) { return f(10) === 3628800; } 43 | }, 44 | { 45 | text: "(function factorial_tail(N, Acc) { @; if (N == 0) { @; return Acc; } @; return factorial_tail(N - 1, Acc * N); @ })", 46 | vars: ["N", "Acc"], 47 | args: function (d, b) { return `${singleRecursionDepth(d, b)}, 1`; }, 48 | test: function (f) { return f(10, 1) === 3628800; } 49 | }, 50 | { 51 | // two recursive calls 52 | text: "(function fibonacci(N) { @; if (N <= 1) { @; return 1; } @; return fibonacci(N - 1) + fibonacci(N - 2); @ })", 53 | vars: ["N"], 54 | args: function (d, b) { return `${rnd(8)}`; }, 55 | test: function (f) { return f(6) === 13; } 56 | }, 57 | { 58 | // do *anything* while indexing over mixed-type arrays 59 | text: "(function a_indexing(array, start) { @; if (array.length == start) { @; return EXPR1; } var thisitem = array[start]; var recval = a_indexing(array, start + 1); STATEMENT1 })", 60 | vars: ["array", "start", "thisitem", "recval"], 61 | args: function (d, b) { return `${makeMixedTypeArray(d - 1, b)}, 0`; }, 62 | testSub: function (text) { return text.replace(/EXPR1/, "0").replace(/STATEMENT1/, "return thisitem + recval;"); }, 63 | randSub: function (text, varMap, d, b) { 64 | /* eslint-disable indent, no-multi-spaces */ 65 | var expr1 = makeExpr(d, b.concat([varMap["array"], varMap["start"]])); 66 | var statement1 = rnd(2) ? 67 | makeStatement(d, b.concat([varMap["thisitem"], varMap["recval"]])) : 68 | `return ${makeExpr(d, b.concat([varMap["thisitem"], varMap["recval"]]))};`; 69 | 70 | return (text.replace(/EXPR1/, expr1) 71 | .replace(/STATEMENT1/, statement1) 72 | /* eslint-enable indent, no-multi-spaces */ 73 | ); 74 | }, 75 | test: function (f) { return f([1, 2, 3, "4", 5, 6, 7], 0) === "123418"; } 76 | }, 77 | { 78 | // this lets us play a little with mixed-type arrays 79 | text: "(function sum_indexing(array, start) { @; return array.length == start ? 0 : array[start] + sum_indexing(array, start + 1); })", 80 | vars: ["array", "start"], 81 | args: function (d, b) { return `${makeMixedTypeArray(d - 1, b)}, 0`; }, 82 | test: function (f) { return f([1, 2, 3, "4", 5, 6, 7], 0) === "123418"; } 83 | }, 84 | { 85 | text: "(function sum_slicing(array) { @; return array.length == 0 ? 0 : array[0] + sum_slicing(array.slice(1)); })", 86 | vars: ["array"], 87 | args: function (d, b) { return makeMixedTypeArray(d - 1, b); }, 88 | test: function (f) { return f([1, 2, 3, "4", 5, 6, 7]) === "123418"; } 89 | } 90 | ]; 91 | 92 | function singleRecursionDepth (d, b) { /* eslint-disable-line require-jsdoc */ 93 | if (rnd(2) === 0) { 94 | return `${rnd(4)}`; 95 | } 96 | if (rnd(10) === 0) { 97 | return makeExpr(d - 2, b); 98 | } 99 | return `${rnd(100000)}`; 100 | } 101 | 102 | (function testAllRecursiveFunctions () { 103 | for (var i = 0; i < recursiveFunctions.length; ++i) { 104 | var a = recursiveFunctions[i]; 105 | var text = a.text; 106 | if (a.testSub) text = a.testSub(text); 107 | var f = eval(text.replace(/@/g, "")); /* eslint-disable-line no-eval */ 108 | if (!a.test(f)) { throw new Error(`Failed test of: ${a.text}`); } 109 | } 110 | })(); 111 | 112 | function makeImmediateRecursiveCall (d, b, cheat1, cheat2) { /* eslint-disable-line require-jsdoc */ 113 | if (rnd(10) !== 0) { return "(4277)"; } 114 | 115 | var a = (cheat1 == null) ? Random.index(recursiveFunctions) : recursiveFunctions[cheat1]; 116 | var s = a.text; 117 | var varMap = {}; 118 | for (var i = 0; i < a.vars.length; ++i) { 119 | var prettyName = a.vars[i]; 120 | varMap[prettyName] = uniqueVarName(); 121 | s = s.replace(new RegExp(prettyName, "g"), varMap[prettyName]); 122 | } 123 | var actualArgs = cheat2 == null ? a.args(d, b) : cheat2; 124 | s = `${s}(${actualArgs})`; 125 | s = s.replace(/@/g, function () { if (rnd(4) === 0) return makeStatement(d - 2, b); return ""; }); 126 | if (a.randSub) s = a.randSub(s, varMap, d, b); 127 | s = `(${s})`; 128 | return s; 129 | } 130 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/gen-stomp-on-registers.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported makeRegisterStompFunction */ 7 | /* global Random, rnd */ 8 | 9 | // Using up all the registers can find bugs where a caller does not store its 10 | // registers properly, or a callee violates an ABI. 11 | 12 | function makeRegisterStompFunction (d, b, pure) { /* eslint-disable-line require-jsdoc */ 13 | var args = []; 14 | var nArgs = (rnd(10) ? rnd(20) : rnd(100)) + 1; 15 | for (var i = 0; i < nArgs; ++i) { 16 | args.push(`a${i}`); 17 | } 18 | 19 | var bv = b.concat(args); 20 | 21 | return ( 22 | `(function(${args.join(", ")}) { ` + 23 | makeRegisterStompBody(d, bv, pure) + 24 | `return ${Random.index(bv)}; ` + 25 | "})" 26 | ); 27 | } 28 | 29 | function makeRegisterStompBody (d, b, pure) { /* eslint-disable-line require-jsdoc */ 30 | var bv = b.slice(0); 31 | var lastRVar = 0; 32 | var s = ""; 33 | 34 | function value () { /* eslint-disable-line require-jsdoc */ 35 | return rnd(3) && bv.length ? Random.index(bv) : `${rnd(10)}`; 36 | } 37 | 38 | function expr () { /* eslint-disable-line require-jsdoc */ 39 | return value() + Random.index([" + ", " - ", " / ", " * ", " % ", " | ", " & ", " ^ "]) + value(); 40 | } 41 | 42 | while (rnd(100)) { 43 | if (bv.length === 0 || rnd(4)) { 44 | var newVar = `r${lastRVar}`; 45 | ++lastRVar; 46 | s += `var ${newVar} = ${expr()}; `; 47 | bv.push(newVar); 48 | } else if (rnd(5) === 0 && !pure) { 49 | s += `print(${Random.index(bv)}); `; 50 | } else { 51 | s += `${Random.index(bv)} = ${expr()}; `; 52 | } 53 | } 54 | 55 | return s; 56 | } 57 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/mess-grammar.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported TOTALLY_RANDOM, totallyRandom */ 7 | /* global print, Random, rnd */ 8 | 9 | // Randomly ignore the grammar 1 in TOTALLY_RANDOM times we generate any grammar node. 10 | var TOTALLY_RANDOM = 1000; 11 | 12 | var allMakers = getListOfMakers(this); 13 | 14 | function totallyRandom (d, b) { /* eslint-disable-line require-jsdoc */ 15 | d = d + (rnd(5) - 2); // can increase!! 16 | 17 | var maker = Random.index(allMakers); 18 | var val = maker(d, b); 19 | if (typeof val !== "string") { 20 | print(maker.name); 21 | print(maker); 22 | throw new Error("We generated something that isn't a string!"); 23 | } 24 | return val; 25 | } 26 | 27 | function getListOfMakers (glob) { /* eslint-disable-line require-jsdoc */ 28 | var r = []; 29 | for (var f in glob) { 30 | if (f.indexOf("make") === 0 && typeof glob[f] === "function" && f !== "makeFinalizeObserver" && f !== "makeFakePromise") { 31 | r.push(glob[f]); 32 | } 33 | } 34 | return r; 35 | } 36 | 37 | // To run testEachMaker(), replace `start(this)` with `Random.init(0);` and `testEachMaker();` 38 | /* 39 | function testEachMaker() 40 | { 41 | for (var f of allMakers) { 42 | dumpln(""); 43 | dumpln(f.name); 44 | dumpln("=========="); 45 | dumpln(""); 46 | for (var i = 0; i < 100; ++i) { 47 | try { 48 | var r = f(8, ["A", "B"]); 49 | if (typeof r != "string") 50 | throw (`Got a ${typeof r}`); 51 | dumpln(r); 52 | } catch(e) { 53 | dumpln(""); 54 | dumpln(uneval(e)); 55 | dumpln(e.stack); 56 | dumpln(""); 57 | throw "testEachMaker found a bug in jsfunfuzz"; 58 | } 59 | } 60 | dumpln(""); 61 | } 62 | } 63 | */ 64 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/mess-tokens.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported cat, stripSemicolon */ 7 | /* global dumpln, Random, rnd, totallyRandom */ 8 | 9 | // Each input to |cat| should be a token or so, OR a bigger logical piece (such as a call to makeExpr). Smaller than a token is ok too ;) 10 | 11 | // When "torture" is true, it may do any of the following: 12 | // * skip a token 13 | // * skip all the tokens to the left 14 | // * skip all the tokens to the right 15 | // * insert unterminated comments 16 | // * insert line breaks 17 | // * insert entire expressions 18 | // * insert any token 19 | 20 | // Even when not in "torture" mode, it may sneak in extra line breaks. 21 | 22 | // Why did I decide to toString at every step, instead of making larger and larger arrays (or more and more deeply nested arrays?). no particular reason. 23 | 24 | function cat (toks) { /* eslint-disable-line require-jsdoc */ 25 | if (rnd(1700) === 0) { return totallyRandom(2, ["x"]); } 26 | 27 | var torture = (rnd(1700) === 57); 28 | if (torture) { dumpln("Torture!!!"); } 29 | 30 | var s = maybeLineBreak(); 31 | for (var i = 0; i < toks.length; ++i) { 32 | // Catch bugs in the fuzzer. An easy mistake is 33 | // return /*foo*/ + ... 34 | // instead of 35 | // return "/*foo*/" + ... 36 | // Unary plus in the first one coerces the string that follows to number! 37 | if (typeof (toks[i]) !== "string") { 38 | dumpln(`Strange item in the array passed to cat: typeof toks[${i}] == ${typeof (toks[i])}`); 39 | dumpln(cat.caller); 40 | dumpln(cat.caller.caller); 41 | } 42 | 43 | if (!(torture && rnd(12) === 0)) { s += toks[i]; } 44 | 45 | s += maybeLineBreak(); 46 | 47 | if (torture) { 48 | switch (rnd(120)) { 49 | case 0: 50 | case 1: 51 | case 2: 52 | case 3: 53 | case 4: 54 | s += maybeSpace() + totallyRandom(2, ["x"]) + maybeSpace(); 55 | break; 56 | case 5: 57 | s = `(${s})`; // randomly parenthesize some *prefix* of it. 58 | break; 59 | case 6: 60 | s = ""; // throw away everything before this point 61 | break; 62 | case 7: 63 | return s; // throw away everything after this point 64 | case 8: 65 | s += UNTERMINATED_COMMENT; 66 | break; 67 | case 9: 68 | s += UNTERMINATED_STRING_LITERAL; 69 | break; 70 | case 10: 71 | if (rnd(2)) { s += "("; } 72 | s += UNTERMINATED_REGEXP_LITERAL; 73 | break; 74 | default: 75 | } 76 | } 77 | } 78 | 79 | return s; 80 | } 81 | 82 | // For reference and debugging. 83 | /* 84 | function catNice(toks) 85 | { 86 | var s = "" 87 | var i; 88 | for (i=0; i") === -1 && code.indexOf("//") === -1) { 131 | // FCM cookie, lines with this cookie are used for compare_jit 132 | var cookie1 = "/*F"; 133 | var cookie2 = "CM*/"; 134 | var nCode = code; 135 | // Avoid compile-time errors because those are no fun. 136 | // But leave some things out of function(){} because some bugs are only detectable at top-level, and 137 | // pure jsfunfuzz doesn't test top-level at all. 138 | // (This is a good reason to use compare_jit even if I'm not interested in finding JIT bugs!) 139 | if (nCode.indexOf("return") !== -1 || nCode.indexOf("yield") !== -1 || nCode.indexOf("const") !== -1 || failsToCompileInTry(nCode)) { nCode = `(function(){${nCode}})()`; } 140 | dumpln(`${cookie1 + cookie2} try { ${nCode} } catch(e) { }`); 141 | } 142 | } 143 | 144 | if (tryRunning !== tryRunningDirectly) { 145 | optionalTests(f, code, wtt); 146 | } 147 | 148 | if (wtt.allowExec && f) { 149 | tryRunning(f, code, wtt); 150 | } 151 | 152 | if (verbose) { dumpln("Done trying out that function!"); } 153 | 154 | dumpln(""); 155 | } 156 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/tail.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported count, verbose */ 7 | /* global jsshell, print, start */ 8 | 9 | var count = 0; 10 | var verbose = false; 11 | 12 | /* ********************************** * 13 | * To reproduce a crash or assertion: * 14 | * ********************************** */ 15 | 16 | // 1. grep tryIt LOGFILE | grep -v "function tryIt" | pbcopy 17 | // 2. Paste the result between "ddbegin" and "ddend", replacing "start(this);" 18 | // 3. Run Lithium to remove unnecessary lines between "ddbegin" and "ddend". 19 | // SPLICE DDBEGIN 20 | start(this); 21 | // SPLICE DDEND 22 | 23 | if (jsshell) { print("It's looking good!"); } // Magic string that js_interesting looks for 24 | 25 | // 3. Run it. 26 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/test-asm.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported startAsmDifferential */ 7 | /* global ArrayBuffer, asmJSInterior, binaryMathFunctions, dumpln, foundABug, gc, Int32Array, isAsmJSModule */ 8 | /* global makeRegisterStompFunction, print, Random, rnd, unaryMathFunctions, uneval */ 9 | 10 | /* *********************** * 11 | * TEST ASM.JS CORRECTNESS * 12 | * *********************** */ 13 | 14 | // asm.js functions should always have the same semantics as JavaScript functions. 15 | // 16 | // We just need to watch out for: 17 | // * Invalid asm.js 18 | // * Foreign imports of impure functions 19 | // * Mixing int and double heap views (NaN bits) 20 | // * Allowing mutable state to diverge (mutable module globals, heap) 21 | 22 | // In those cases, we can test that the asm-compiled version matches the normal js version. 23 | // * isAsmJSFunction(f) 24 | // * !isAsmJSFunction(g) 25 | 26 | // Because we pass the 'sanePlease' flag to asmJSInterior, 27 | // * We don't expect any parse errors. (testOneAsmJSInterior currently relies on this.) 28 | // * We expect only the following asm.js type errors: 29 | // * "numeric literal out of representable integer range" (https://github.com/dherman/asm.js/issues/67 makes composition hard) 30 | // * "no duplicate case labels" (because asmSwitchStatement doesn't avoid this) 31 | // * And the following, infrequently, due to out-of-range integer literals: 32 | // * "one arg to int multiply must be a small (-2^20, 2^20) int literal" 33 | // * "arguments to / or % must both be double, signed, or unsigned, unsigned and signed are given" 34 | // * "unsigned is not a subtype of signed or doublish" [Math.abs] 35 | 36 | var compareAsm = (function () { 37 | function isSameNumber (a, b) { /* eslint-disable-line require-jsdoc */ 38 | if (!(typeof a === "number" && typeof b === "number")) { return false; } 39 | 40 | // Differentiate between 0 and -0 41 | if (a === 0 && b === 0) { return 1 / a === 1 / b; } 42 | 43 | // Don't differentiate between NaNs 44 | return a === b || (a !== a && b !== b); /* eslint-disable-line no-self-compare */ 45 | } 46 | 47 | var asmvals = [ 48 | 1, Math.PI, 42, 49 | // Special float values 50 | 0, -0, 0 / 0, 1 / 0, -1 / 0, 51 | // Boundaries of int, signed, unsigned (near +/- 2^31, +/- 2^32) 52 | 0x07fffffff, 0x080000000, 0x080000001, 53 | -0x07fffffff, -0x080000000, -0x080000001, 54 | 0x0ffffffff, 0x100000000, 0x100000001, 55 | -0x0ffffffff, -0x100000000, 0x100000001, 56 | // Boundaries of double 57 | Number.MIN_VALUE, -Number.MIN_VALUE, 58 | Number.MAX_VALUE, -Number.MAX_VALUE 59 | ]; 60 | var asmvalsLen = asmvals.length; 61 | 62 | function compareUnaryFunctions (f, g) { /* eslint-disable-line require-jsdoc */ 63 | for (var i = 0; i < asmvalsLen; ++i) { 64 | var x = asmvals[i]; 65 | var fr = f(x); 66 | var gr = g(x); 67 | if (!isSameNumber(fr, gr)) { 68 | foundABug("asm mismatch", `(${uneval(x)}) -> ${uneval(fr)} vs ${uneval(gr)}`); 69 | } 70 | } 71 | } 72 | 73 | function compareBinaryFunctions (f, g) { /* eslint-disable-line require-jsdoc */ 74 | for (var i = 0; i < asmvalsLen; ++i) { 75 | var x = asmvals[i]; 76 | for (var j = 0; j < asmvalsLen; ++j) { 77 | var y = asmvals[j]; 78 | var fr = f(x, y); 79 | var gr = g(x, y); 80 | if (!isSameNumber(fr, gr)) { 81 | foundABug("asm mismatch", `(${uneval(x)}, ${uneval(y)}) -> ${uneval(fr)} vs ${uneval(gr)}`); 82 | } 83 | } 84 | } 85 | } 86 | 87 | return { compareUnaryFunctions: compareUnaryFunctions, compareBinaryFunctions: compareBinaryFunctions }; 88 | })(); 89 | 90 | function nanBitsMayBeVisible (s) { /* eslint-disable-line require-jsdoc */ 91 | // Does the code use more than one of {*int*, float32, or float64} views on the same array buffer? 92 | return (s.indexOf("Uint") !== -1 || s.indexOf("Int") !== -1) + (s.indexOf("Float32Array") !== -1) + (s.indexOf("Float64Array") !== -1) > 1; 93 | } 94 | 95 | var pureForeign = { 96 | identity: function (x) { return x; }, 97 | quadruple: function (x) { return x * 4; }, 98 | half: function (x) { return x / 2; }, 99 | // Test coercion coming back from FFI. 100 | asString: function (x) { return uneval(x); }, 101 | asValueOf: function (x) { return { valueOf: function () { return x; } }; }, 102 | // Test register arguments vs stack arguments. 103 | sum: function () { var s = 0; for (var i = 0; i < arguments.length; ++i) s += arguments[i]; return s; }, 104 | // Will be replaced by calling makeRegisterStompFunction 105 | stomp: function () { } 106 | }; 107 | 108 | for (let f in unaryMathFunctions) { 109 | pureForeign[`Math_${unaryMathFunctions[f]}`] = Math[unaryMathFunctions[f]]; 110 | } 111 | 112 | for (let f in binaryMathFunctions) { 113 | pureForeign[`Math_${binaryMathFunctions[f]}`] = Math[binaryMathFunctions[f]]; 114 | } 115 | 116 | var pureMathNames = Object.keys(pureForeign); 117 | 118 | function generateAsmDifferential () { /* eslint-disable-line require-jsdoc */ 119 | var foreignFunctions = rnd(10) ? [] : pureMathNames; 120 | return asmJSInterior(foreignFunctions, true); 121 | } 122 | 123 | function testAsmDifferential (stdlib, interior) { /* eslint-disable-line require-jsdoc */ 124 | if (nanBitsMayBeVisible(interior)) { 125 | dumpln("Skipping correctness test for asm module that could expose low bits of NaN"); 126 | return; 127 | } 128 | 129 | var asmJs = `(function(stdlib, foreign, heap) { 'use asm'; ${interior} })`; 130 | var asmModule = eval(asmJs); /* eslint-disable-line no-eval */ 131 | 132 | if (isAsmJSModule(asmModule)) { 133 | var asmHeap = new ArrayBuffer(4096); 134 | (new Int32Array(asmHeap))[0] = 0x12345678; 135 | var asmFun = asmModule(stdlib, pureForeign, asmHeap); 136 | 137 | var normalHeap = new ArrayBuffer(4096); 138 | (new Int32Array(normalHeap))[0] = 0x12345678; 139 | var normalJs = `(function(stdlib, foreign, heap) { ${interior} })`; 140 | var normalModule = eval(normalJs); /* eslint-disable-line no-eval */ 141 | var normalFun = normalModule(stdlib, pureForeign, normalHeap); 142 | 143 | compareAsm.compareBinaryFunctions(asmFun, normalFun); 144 | } 145 | } 146 | 147 | // Call this instead of start() to run asm-differential tests 148 | function startAsmDifferential () { /* eslint-disable-line require-jsdoc */ 149 | var asmFuzzSeed = Math.floor(Math.random() * Math.pow(2, 28)); 150 | dumpln(`asmFuzzSeed: ${asmFuzzSeed}`); 151 | Random.init(asmFuzzSeed); 152 | 153 | while (true) { 154 | var stompStr = makeRegisterStompFunction(8, [], true); 155 | print(stompStr); 156 | pureForeign.stomp = eval(stompStr); /* eslint-disable-line no-eval */ 157 | 158 | for (var i = 0; i < 100; ++i) { 159 | var interior = generateAsmDifferential(); 160 | print(interior); 161 | testAsmDifferential(this, interior); 162 | } 163 | gc(); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/test-consistency.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported nestingConsistencyTest */ 7 | /* global count, evalcx, errorToString, foundABug, newGlobal */ 8 | 9 | /* *************************** * 10 | * EXECUTION CONSISTENCY TESTS * 11 | * *************************** */ 12 | 13 | function sandboxResult (code, zone) { /* eslint-disable-line require-jsdoc */ 14 | // Use sandbox to isolate side-effects. 15 | var result; 16 | var resultStr = ""; 17 | try { 18 | // Using newGlobal(), rather than evalcx(''), to get 19 | // shell functions. (see bug 647412 comment 2) 20 | var sandbox = newGlobal({ sameZoneAs: zone }); 21 | 22 | result = evalcx(code, sandbox); 23 | if (typeof result !== "object") { 24 | // Avoid cross-compartment excitement if it has a toString 25 | resultStr = `${result}`; 26 | } 27 | } catch (e) { 28 | result = `Error: ${errorToString(e)}`; 29 | } 30 | // print(`resultStr: ${resultStr}`); 31 | return resultStr; 32 | } 33 | 34 | function nestingConsistencyTest (code) { /* eslint-disable-line require-jsdoc */ 35 | // Inspired by bug 676343 36 | // This only makes sense if |code| is an expression (or an expression followed by a semicolon). Oh well. 37 | function nestExpr (e) { return `(function() { return ${code}; })()`; } /* eslint-disable-line require-jsdoc */ 38 | var codeNestedOnce = nestExpr(code); 39 | var codeNestedDeep = code; 40 | var depth = (count % 7) + 14; // 16 might be special 41 | for (var i = 0; i < depth; ++i) { 42 | codeNestedDeep = nestExpr(codeNestedDeep); 43 | } 44 | 45 | // These are on the same line so that line numbers in stack traces will match. 46 | var resultO = sandboxResult(codeNestedOnce, null); var resultD = sandboxResult(codeNestedDeep, null); 47 | 48 | // if (resultO != "" && resultO != "undefined" && resultO != "use strict") 49 | // print(`NestTest: ${resultO}`); 50 | 51 | if (resultO !== resultD) { 52 | foundABug("NestTest mismatch", 53 | `resultO: ${resultO}\n` + 54 | `resultD: ${resultD}`); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/test-math.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported makeMathyFunAndTest, makeMathyFunRef, mathInitFCM */ 7 | /* global errorToString, makeAsmJSFunction, makeMathFunction, makeMixedTypeArray, NUM_MATH_FUNCTIONS, print, Random */ 8 | /* global rnd, TOTALLY_RANDOM, totallyRandom, uneval */ 9 | 10 | var numericVals = [ 11 | "1", "Math.PI", "42", 12 | // Special float values 13 | "0", "-0", "0/0", "1/0", "-1/0", 14 | // Boundaries of int, signed, unsigned (near +/- 2^31, +/- 2^32) 15 | "0x07fffffff", "0x080000000", "0x080000001", 16 | "-0x07fffffff", "-0x080000000", "-0x080000001", 17 | "0x0ffffffff", "0x100000000", "0x100000001", 18 | "-0x0ffffffff", "-0x100000000", "-0x100000001", 19 | // Boundaries of double 20 | "Number.MIN_VALUE", "-Number.MIN_VALUE", 21 | "Number.MAX_VALUE", "-Number.MAX_VALUE", 22 | // Boundaries of maximum safe integer 23 | "Number.MIN_SAFE_INTEGER", "-Number.MIN_SAFE_INTEGER", 24 | "-(2**53-2)", "-(2**53)", "-(2**53+2)", 25 | "Number.MAX_SAFE_INTEGER", "-Number.MAX_SAFE_INTEGER", 26 | "(2**53)-2", "(2**53)", "(2**53)+2", 27 | // See bug 1350097 - 1.79...e308 is the largest (by module) finite number 28 | "0.000000000000001", "1.7976931348623157e308" 29 | ]; 30 | 31 | var confusableVals = [ 32 | "0", 33 | "0.1", 34 | "-0", 35 | "''", 36 | "'0'", 37 | "'\\0'", 38 | "[]", 39 | "[0]", 40 | "/0/", 41 | "'/0/'", 42 | "1", 43 | "({toString:function(){return '0';}})", 44 | "({valueOf:function(){return 0;}})", 45 | "({valueOf:function(){return '0';}})", 46 | "false", 47 | "true", 48 | "undefined", 49 | "null", 50 | "(function(){return 0;})", 51 | "NaN", 52 | "(new Boolean(false))", 53 | "(new Boolean(true))", 54 | "(new String(''))", 55 | "(new Number(0))", 56 | "(new Number(-0))", 57 | "createIsHTMLDDA()" 58 | ]; 59 | 60 | function hashStr (s) { /* eslint-disable-line require-jsdoc */ 61 | var hash = 0; 62 | var L = s.length; 63 | for (var i = 0; i < L; i++) { 64 | var c = s.charCodeAt(i); 65 | hash = (Math.imul(hash, 31) + c) | 0; 66 | } 67 | return hash; 68 | } 69 | 70 | function testMathyFunction (f, inputs) { /* eslint-disable-line require-jsdoc */ 71 | var results = []; 72 | if (f) { 73 | for (var j = 0; j < inputs.length; ++j) { 74 | for (var k = 0; k < inputs.length; ++k) { 75 | try { 76 | results.push(f(inputs[j], inputs[k])); 77 | } catch (e) { 78 | results.push(errorToString(e)); 79 | } 80 | } 81 | } 82 | } 83 | /* Use uneval to distinguish -0, 0, "0", etc. */ 84 | /* Use hashStr to shorten the output and keep compare_jit files small. */ 85 | print(hashStr(uneval(results))); 86 | } 87 | 88 | function mathInitFCM () { /* eslint-disable-line require-jsdoc */ 89 | // FCM cookie, lines with this cookie are used for compare_jit 90 | var cookie = "/*F" + "CM*/"; 91 | 92 | // Replace carriage returns (Windows) with line breaks, if present 93 | print(cookie + hashStr.toString().replace(/\r/g, "\n").replace(/\n/g, " ")); 94 | print(cookie + testMathyFunction.toString().replace(/\r/g, "\n").replace(/\n/g, " ")); 95 | } 96 | 97 | function makeMathyFunAndTest (d, b) { /* eslint-disable-line require-jsdoc */ 98 | if (rnd(TOTALLY_RANDOM) === 2) return totallyRandom(d, b); 99 | 100 | var i = rnd(NUM_MATH_FUNCTIONS); 101 | var s = ""; 102 | 103 | if (rnd(5)) { 104 | if (rnd(8)) { 105 | s += `mathy${i} = ${makeMathFunction(6, b, i)}; `; 106 | } else { 107 | s += `mathy${i} = ${makeAsmJSFunction(6, b)}; `; 108 | } 109 | } 110 | 111 | if (rnd(5)) { 112 | var inputsStr; 113 | switch (rnd(8)) { 114 | /* eslint-disable no-multi-spaces */ 115 | case 0: inputsStr = makeMixedTypeArray(d - 1, b); break; 116 | case 1: inputsStr = `[${Random.shuffled(confusableVals).join(", ")}]`; break; 117 | default: inputsStr = `[${Random.shuffled(numericVals).join(", ")}]`; break; 118 | /* eslint-enable no-multi-spaces */ 119 | } 120 | 121 | s += `testMathyFunction(mathy${i}, ${inputsStr}); `; 122 | } 123 | 124 | return s; 125 | } 126 | 127 | function makeMathyFunRef (d, b) { /* eslint-disable-line require-jsdoc */ 128 | if (rnd(TOTALLY_RANDOM) === 2) return totallyRandom(d, b); 129 | 130 | return `mathy${rnd(NUM_MATH_FUNCTIONS)}`; 131 | } 132 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/test-misc.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported optionalTests */ 7 | /* global count, disassemble, dumpln, engine, ENGINE_SPIDERMONKEY_TRUNK, foundABug, getBuildConfiguration */ 8 | /* global nestingConsistencyTest, Reflect, tryEnsureSanity, verbose */ 9 | 10 | function optionalTests (f, code, wtt) { /* eslint-disable-line require-jsdoc */ 11 | if (count % 100 === 1) { 12 | tryHalves(code); 13 | } 14 | 15 | if (count % 100 === 2 && engine === ENGINE_SPIDERMONKEY_TRUNK) { 16 | try { 17 | Reflect.parse(code); 18 | } catch (e) { 19 | } 20 | } 21 | 22 | if (count % 100 === 3 && f && typeof disassemble === "function") { 23 | // It's hard to use the recursive disassembly in the comparator, 24 | // but let's at least make sure the disassembler itself doesn't crash. 25 | disassemble("-r", f); 26 | } 27 | 28 | if (0 && f && wtt.allowExec && engine === ENGINE_SPIDERMONKEY_TRUNK) { 29 | testExpressionDecompiler(code); 30 | tryEnsureSanity(); 31 | } 32 | 33 | if (count % 100 === 6 && f && wtt.allowExec && wtt.expectConsistentOutput && wtt.expectConsistentOutputAcrossIter 34 | && engine === ENGINE_SPIDERMONKEY_TRUNK && getBuildConfiguration()["more-deterministic"]) { 35 | nestingConsistencyTest(code); 36 | } 37 | } 38 | 39 | function testExpressionDecompiler (code) { /* eslint-disable-line require-jsdoc */ 40 | var fullCode = `(function() { try { \n${code}\n; throw 1; } catch(exx) { this.nnn.nnn } })()`; 41 | 42 | try { 43 | eval(fullCode); /* eslint-disable-line no-eval */ 44 | } catch (e) { 45 | if (e.message !== "this.nnn is undefined" && e.message.indexOf("redeclaration of") === -1) { 46 | // Break up the following string intentionally, to prevent matching when contents of jsfunfuzz is printed. 47 | foundABug("Wrong error " + "message", e); 48 | } 49 | } 50 | } 51 | 52 | function tryHalves (code) { /* eslint-disable-line require-jsdoc */ 53 | // See if there are any especially horrible bugs that appear when the parser has to start/stop in the middle of something. this is kinda evil. 54 | 55 | // Stray "}"s are likely in secondHalf, so use new Function rather than eval. "}" can't escape from new Function :) 56 | 57 | var f; 58 | var firstHalf; 59 | var secondHalf; 60 | 61 | try { 62 | firstHalf = code.substr(0, code.length / 2); 63 | if (verbose) { dumpln(`First half: ${firstHalf}`); } 64 | f = new Function(firstHalf); /* eslint-disable-line no-new-func */ 65 | void (`${f}`); 66 | } catch (e) { 67 | if (verbose) { dumpln(`First half compilation error: ${e}`); } 68 | } 69 | 70 | try { 71 | secondHalf = code.substr(code.length / 2, code.length); 72 | if (verbose) { dumpln(`Second half: ${secondHalf}`); } 73 | f = new Function(secondHalf); /* eslint-disable-line no-new-func */ 74 | void (`${f}`); 75 | } catch (e) { 76 | if (verbose) { dumpln(`Second half compilation error: ${e}`); } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/funfuzz/js/jsfunfuzz/test-regex.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* exported makeRegex, makeRegexUseBlock, makeRegexUseExpr */ 7 | /* global makeExpr, makeFunction, POTENTIAL_MATCHES, Random, regexPattern, rnd, simpleSource */ 8 | 9 | /* ************* * 10 | * USING REGEXPS * 11 | * ************* */ 12 | 13 | function randomRegexFlags () { /* eslint-disable-line require-jsdoc */ 14 | var s = ""; 15 | if (rnd(2)) { s += "g"; } 16 | if (rnd(2)) { s += "y"; } 17 | if (rnd(2)) { s += "i"; } 18 | if (rnd(2)) { s += "m"; } 19 | return s; 20 | } 21 | 22 | function toRegexSource (rexpat) { /* eslint-disable-line require-jsdoc */ 23 | return (rnd(2) === 0 && rexpat.charAt(0) !== "*") ? 24 | `/${rexpat}/${randomRegexFlags()}` : 25 | `new RegExp(${simpleSource(rexpat)}, ${simpleSource(randomRegexFlags())})`; 26 | } 27 | 28 | function makeRegexUseBlock (d, b, rexExpr, strExpr) { /* eslint-disable-line require-jsdoc */ 29 | var rexpair = regexPattern(10, false); 30 | var rexpat = rexpair[0]; 31 | var str = rexpair[1][rnd(POTENTIAL_MATCHES)]; 32 | 33 | if (!rexExpr) rexExpr = rnd(10) === 0 ? makeExpr(d - 1, b) : toRegexSource(rexpat); 34 | if (!strExpr) strExpr = rnd(10) === 0 ? makeExpr(d - 1, b) : simpleSource(str); 35 | 36 | var bv = b.concat(["s", "r"]); 37 | 38 | return (`/*RXUB*/var r = ${rexExpr}; ` + 39 | `var s = ${strExpr}; ` + 40 | "print(" + 41 | Random.index([ 42 | "r.exec(s)", 43 | "uneval(r.exec(s))", 44 | "r.test(s)", 45 | "s.match(r)", 46 | "uneval(s.match(r))", 47 | "s.search(r)", 48 | `s.replace(r, ${makeReplacement(d, bv)}${rnd(3) ? "" : `, ${simpleSource(randomRegexFlags())}`})`, 49 | "s.split(r)" 50 | ]) + 51 | "); " + 52 | (rnd(3) ? "" : "print(r.lastIndex); ") 53 | ); 54 | } 55 | 56 | function makeRegexUseExpr (d, b) { /* eslint-disable-line require-jsdoc */ 57 | var rexpair = regexPattern(8, false); 58 | var rexpat = rexpair[0]; 59 | var str = rexpair[1][rnd(POTENTIAL_MATCHES)]; 60 | 61 | var rexExpr = rnd(10) === 0 ? makeExpr(d - 1, b) : toRegexSource(rexpat); 62 | var strExpr = rnd(10) === 0 ? makeExpr(d - 1, b) : simpleSource(str); 63 | 64 | return `/*RXUE*/${rexExpr}.exec(${strExpr})`; 65 | } 66 | 67 | function makeRegex (d, b) { /* eslint-disable-line require-jsdoc */ 68 | var rexpair = regexPattern(8, false); 69 | var rexpat = rexpair[0]; 70 | var rexExpr = toRegexSource(rexpat); 71 | return rexExpr; 72 | } 73 | 74 | function makeReplacement (d, b) { /* eslint-disable-line require-jsdoc */ 75 | switch (rnd(3)) { 76 | /* eslint-disable no-multi-spaces */ 77 | case 0: return Random.index(["''", "'x'", "'\\u0341'"]); 78 | case 1: return makeExpr(d, b); 79 | default: return makeFunction(d, b); 80 | /* eslint-enable no-multi-spaces */ 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/funfuzz/js/link_fuzzer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Concatenate js files to create jsfunfuzz. 8 | """ 9 | 10 | import io 11 | from pathlib import Path 12 | 13 | 14 | def link_fuzzer(target_path, prologue=""): 15 | """Links files together to create the full jsfunfuzz file. 16 | 17 | Args: 18 | target_path (Path): Target file with full path, to be created 19 | prologue (str): Contents to be prepended to the target file 20 | """ 21 | base_dir = Path(__file__).parent 22 | 23 | with io.open(str(target_path), "w", encoding="utf-8", errors="replace") as f: # Create the full jsfunfuzz file 24 | if prologue: 25 | f.write(prologue) 26 | 27 | for entry in (base_dir / "files_to_link.txt").read_text().split(): 28 | entry = entry.rstrip() 29 | if entry and not entry.startswith("#"): 30 | file_path = base_dir / Path(entry) 31 | file_name = f'\n\n// {str(file_path).split("funfuzz", 1)[1][1:]}\n\n' 32 | f.write(file_name) 33 | f.write(file_path.read_text()) 34 | -------------------------------------------------------------------------------- /src/funfuzz/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsfunfuzz", 3 | "version": "0.7.0a1", 4 | "description": "A fuzzer for testing the SpiderMonkey JavaScript engine", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/MozillaSecurity/funfuzz.git" 8 | }, 9 | "author": "Gary Kwong", 10 | "license": "MPL-2.0", 11 | "bugs": { 12 | "url": "https://github.com/MozillaSecurity/funfuzz/issues" 13 | }, 14 | "homepage": "https://github.com/MozillaSecurity/funfuzz#readme", 15 | "devDependencies": { 16 | "eslint": "^5.13.0", 17 | "eslint-plugin-import": "^2.16.0", 18 | "eslint-plugin-jsdoc": "^4.1.0", 19 | "eslint-plugin-node": "^8.0.1", 20 | "eslint-plugin-promise": "^4.0.1", 21 | "eslint-plugin-standard": "^4.0.0" 22 | }, 23 | "scripts": { 24 | "lint": "eslint ./", 25 | "lint:fix": "eslint --fix ./" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/funfuzz/js/shared/mersenne-twister.js: -------------------------------------------------------------------------------- 1 | // this program is a JavaScript version of Mersenne Twister, with concealment and encapsulation in class, 2 | // an almost straight conversion from the original program, mt19937ar.c, 3 | // translated by y. okada on July 17, 2006. 4 | 5 | // Changes by Jesse Ruderman: 6 | // * Use intish/int32 rather than uint32 for intermediate calculations 7 | // (see https://bugzilla.mozilla.org/show_bug.cgi?id=883748#c1) 8 | // * Added functions for exporting/importing the entire PRNG state 9 | // * Removed parts not needed for fuzzing 10 | 11 | // in this program, procedure descriptions and comments of original source code were not removed. 12 | // lines commented with //c// were originally descriptions of c procedure. and a few following lines are appropriate JavaScript descriptions. 13 | // lines commented with /* and */ are original comments. 14 | // lines commented with // are additional comments in this JavaScript version. 15 | // before using this version, create at least one instance of MersenneTwister19937 class, and initialize the each state, given below in c comments, of all the instances. 16 | 17 | /* exported MersenneTwister19937 */ 18 | /* global Int32Array */ 19 | 20 | /* 21 | A C-program for MT19937, with initialization improved 2002/1/26. 22 | Coded by Takuji Nishimura and Makoto Matsumoto. 23 | 24 | Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, 25 | All rights reserved. 26 | 27 | Redistribution and use in source and binary forms, with or without 28 | modification, are permitted provided that the following conditions 29 | are met: 30 | 31 | 1. Redistributions of source code must retain the above copyright 32 | notice, this list of conditions and the following disclaimer. 33 | 34 | 2. Redistributions in binary form must reproduce the above copyright 35 | notice, this list of conditions and the following disclaimer in the 36 | documentation and/or other materials provided with the distribution. 37 | 38 | 3. The names of its contributors may not be used to endorse or promote 39 | products derived from this software without specific prior written 40 | permission. 41 | 42 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 43 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 44 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 45 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 46 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 47 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 48 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 49 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 50 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 51 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 52 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 53 | 54 | Any feedback is very welcome. 55 | http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html 56 | email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) 57 | */ 58 | 59 | function MersenneTwister19937 () { /* eslint-disable-line require-jsdoc */ 60 | const N = 624; 61 | const M = 397; 62 | const MAG01 = new Int32Array([0, 0x9908b0df]); 63 | 64 | var mt = new Int32Array(N); /* the array for the state vector */ 65 | var mti = 625; 66 | 67 | this.seed = function (s) { 68 | mt[0] = s | 0; 69 | for (mti = 1; mti < N; mti++) { 70 | mt[mti] = Math.imul(1812433253, mt[mti - 1] ^ (mt[mti - 1] >>> 30)) + mti; 71 | } 72 | }; 73 | 74 | /* eslint-disable camelcase */ 75 | this.export_state = function () { return [mt, mti]; }; 76 | this.import_state = function (s) { mt = s[0]; mti = s[1]; }; 77 | this.export_mta = function () { return mt; }; 78 | this.import_mta = function (_mta) { mt = _mta; }; 79 | this.export_mti = function () { return mti; }; 80 | this.import_mti = function (_mti) { mti = _mti; }; 81 | /* eslint-enable camelcase */ 82 | 83 | function mag01 (y) { /* eslint-disable-line require-jsdoc */ 84 | return MAG01[y & 0x1]; 85 | } 86 | 87 | this.int32 = function () { 88 | var y; 89 | var kk; 90 | 91 | if (mti >= N) { /* generate N words at one time */ 92 | for (kk = 0; kk < N - M; kk++) { 93 | y = ((mt[kk] & 0x80000000) | (mt[kk + 1] & 0x7fffffff)); 94 | mt[kk] = (mt[kk + M] ^ (y >>> 1) ^ mag01(y)); 95 | } 96 | for (;kk < N - 1; kk++) { 97 | y = ((mt[kk] & 0x80000000) | (mt[kk + 1] & 0x7fffffff)); 98 | mt[kk] = (mt[kk + (M - N)] ^ (y >>> 1) ^ mag01(y)); 99 | } 100 | y = ((mt[N - 1] & 0x80000000) | (mt[0] & 0x7fffffff)); 101 | mt[N - 1] = (mt[M - 1] ^ (y >>> 1) ^ mag01(y)); 102 | mti = 0; 103 | } 104 | 105 | y = mt[mti++]; 106 | 107 | /* Tempering */ 108 | y = y ^ (y >>> 11); 109 | y = y ^ ((y << 7) & 0x9d2c5680); 110 | y = y ^ ((y << 15) & 0xefc60000); 111 | y = y ^ (y >>> 18); 112 | 113 | return y; 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /src/funfuzz/js/shared/random.js: -------------------------------------------------------------------------------- 1 | 2 | // This Source Code Form is subject to the terms of the Mozilla Public 3 | // License, v. 2.0. If a copy of the MPL was not distributed with this 4 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | 6 | /* global MersenneTwister19937, Utils */ 7 | 8 | var Random = { 9 | twister: null, 10 | 11 | init: function (seed) { 12 | if (seed == null || seed === undefined) { 13 | seed = new Date().getTime(); 14 | } 15 | this.twister = new MersenneTwister19937(); 16 | this.twister.seed(seed); 17 | }, 18 | number: function (limit) { 19 | // Returns an integer in [0, limit). Uniform distribution. 20 | if (limit === 0) { 21 | return limit; 22 | } 23 | if (limit == null || limit === undefined) { 24 | limit = 0xffffffff; 25 | } 26 | return (Random.twister.int32() >>> 0) % limit; 27 | }, 28 | float: function () { 29 | // Returns a float in [0, 1]. Uniform distribution. 30 | return (Random.twister.int32() >>> 0) * (1.0 / 4294967295.0); 31 | }, 32 | range: function (start, limit) { 33 | // Returns an integer in [start, limit]. Uniform distribution. 34 | if (isNaN(start) || isNaN(limit)) { 35 | Utils.traceback(); 36 | throw new TypeError(`Random.range() received a non number type: '${start}', '${limit}')`); 37 | } 38 | return Random.number(limit - start + 1) + start; 39 | }, 40 | ludOneTo: function (limit) { 41 | // Returns a float in [1, limit]. The logarithm has uniform distribution. 42 | return Math.exp(Random.float() * Math.log(limit)); 43 | }, 44 | index: function (list, emptyr) { 45 | if (!(list instanceof Array || (typeof list !== "string" && "length" in list))) { 46 | Utils.traceback(); 47 | throw new TypeError(`Random.index() received a non array type: '${list}'`); 48 | } 49 | if (!list.length) { return emptyr; } 50 | return list[this.number(list.length)]; 51 | }, 52 | key: function (obj) { 53 | var list = []; 54 | for (var i in obj) { 55 | list.push(i); 56 | } 57 | return this.index(list); 58 | }, 59 | bool: function () { 60 | return this.index([true, false]); 61 | }, 62 | pick: function (obj) { 63 | if (typeof obj === "function") { 64 | return obj(); 65 | } 66 | if (obj instanceof Array) { 67 | return this.pick(this.index(obj)); 68 | } 69 | return obj; 70 | }, 71 | chance: function (limit) { 72 | if (limit == null || limit === undefined) { 73 | limit = 2; 74 | } 75 | if (isNaN(limit)) { 76 | Utils.traceback(); 77 | throw new TypeError(`Random.chance() received a non number type: '${limit}'`); 78 | } 79 | return this.number(limit) === 1; 80 | }, 81 | choose: function (list, flat) { 82 | if (!(list instanceof Array)) { 83 | Utils.traceback(); 84 | throw new TypeError(`Random.choose() received a non-array type: '${list}'`); 85 | } 86 | var total = 0; 87 | for (var i = 0; i < list.length; i++) { 88 | total += list[i][0]; 89 | } 90 | var n = this.number(total); 91 | for (let j = 0; j < list.length; j++) { 92 | if (n < list[j][0]) { 93 | if (flat === true) { 94 | return list[j][1]; 95 | } else { 96 | return this.pick([list[j][1]]); 97 | } 98 | } 99 | n = n - list[j][0]; 100 | } 101 | if (flat === true) { 102 | return list[0][1]; 103 | } 104 | return this.pick([list[0][1]]); 105 | }, 106 | weighted: function (wa) { 107 | // More memory-hungry but hopefully faster than Random.choose$flat 108 | var a = []; 109 | for (var i = 0; i < wa.length; ++i) { 110 | for (var j = 0; j < wa[i].w; ++j) { 111 | a.push(wa[i].v); 112 | } 113 | } 114 | return a; 115 | }, 116 | use: function (obj) { 117 | return Random.bool() ? obj : ""; 118 | }, 119 | shuffle: function (arr) { 120 | var len = arr.length; 121 | var i = len; 122 | while (i--) { 123 | var p = Random.number(i + 1); 124 | var t = arr[i]; 125 | arr[i] = arr[p]; 126 | arr[p] = t; 127 | } 128 | }, 129 | shuffled: function (arr) { 130 | var newArray = arr.slice(); 131 | Random.shuffle(newArray); 132 | return newArray; 133 | }, 134 | subset: function (a) { 135 | // TODO: shuffle, repeat, include bogus things [see also https://github.com/mozilla/rust/blob/d0ddc69298c41df04b0488d91d521eb531d79177/src/fuzzer/ivec_fuzz.rs] 136 | // Consider adding a weight argument, or swarming on inclusion/exclusion to make 'all' and 'none' more likely 137 | var subset = []; 138 | for (var i = 0; i < a.length; ++i) { 139 | if (rnd(2)) { 140 | subset.push(a[i]); 141 | } 142 | } 143 | return subset; 144 | } 145 | 146 | }; 147 | 148 | function rnd (n) { return Random.number(n); } /* eslint-disable-line require-jsdoc */ 149 | -------------------------------------------------------------------------------- /src/funfuzz/js/with_binaryen.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Run seeds with binaryen to get a wasm file, 8 | then run the shell with the translated wasm binary using a js file wrapper 9 | """ 10 | 11 | import io 12 | from pathlib import Path 13 | import platform 14 | import subprocess 15 | import tarfile 16 | import threading 17 | from time import sleep 18 | 19 | import fasteners 20 | import requests 21 | 22 | from ..util import sm_compile_helpers 23 | 24 | BINARYEN_OS = platform.system().lower() 25 | BINARYEN_ARCH = platform.machine() 26 | 27 | if platform.system() == "Darwin": 28 | BINARYEN_OS = "apple-" + BINARYEN_OS 29 | elif platform.system() == "Windows": 30 | BINARYEN_ARCH = "x86_64" 31 | 32 | BINARYEN_VERSION = 84 33 | BINARYEN_URL = (f"https://github.com/WebAssembly/binaryen/releases/download/version_{BINARYEN_VERSION}/" 34 | f"binaryen-version_{BINARYEN_VERSION}-{BINARYEN_ARCH}-{BINARYEN_OS}.tar.gz") 35 | 36 | 37 | def ensure_binaryen(url, version): 38 | """Download and use a compiled binaryen to generate WebAssembly files if it does not exist. 39 | 40 | Args: 41 | url (str): URL of the compressed binaryen binary package 42 | version (int): Version of the compressed binaryen binary package 43 | 44 | Returns: 45 | Path: Path of the extracted wasm-opt binary 46 | """ 47 | shell_cache = sm_compile_helpers.ensure_cache_dir(Path.home()) 48 | wasmopt_path = Path(shell_cache / f"binaryen-version_{version}" / 49 | ("wasm-opt" + (".exe" if platform.system() == "Windows" else ""))).resolve() 50 | 51 | sleep_time = 2 52 | t_lock = threading.Lock() 53 | with fasteners.try_lock(t_lock) as gotten: 54 | while not wasmopt_path.is_file(): 55 | if gotten: 56 | with requests.get(url, allow_redirects=True, stream=True) as binaryen_gzip_request: 57 | try: 58 | with tarfile.open(fileobj=io.BytesIO(binaryen_gzip_request.content), mode="r:gz") as f: 59 | f.extractall(str(shell_cache.resolve())) 60 | except OSError: 61 | print("binaryen tarfile threw an OSError") 62 | break 63 | sleep(sleep_time) 64 | sleep_time *= 2 65 | return wasmopt_path 66 | 67 | 68 | def wasmopt_run(seed): 69 | """Runs binaryen with the generated seed. 70 | 71 | Args: 72 | seed (Path): Generated jsfunfuzz file (acts as the seed for binaryen) 73 | 74 | Returns: 75 | bool: Returns True on successful wasm-opt execution, False otherwise 76 | """ 77 | assert platform.system() == "Linux" 78 | 79 | assert seed.is_file() 80 | seed_wrapper_output = seed.resolve().with_suffix(".wrapper") 81 | seed_wasm_output = seed.resolve().with_suffix(".wasm") 82 | 83 | sleep_time = 2 84 | t_lock = threading.Lock() 85 | with fasteners.try_lock(t_lock) as gotten: 86 | while True: 87 | if gotten: 88 | try: 89 | subprocess.run([ensure_binaryen(BINARYEN_URL, BINARYEN_VERSION), 90 | seed, 91 | "--translate-to-fuzz", 92 | "--disable-simd", 93 | "--output", seed_wasm_output, 94 | f"--emit-js-wrapper={seed_wrapper_output}"], check=True) 95 | except subprocess.CalledProcessError: 96 | print("wasm-opt aborted with a CalledProcessError") 97 | break 98 | sleep(sleep_time) 99 | sleep_time *= 2 100 | assert seed_wrapper_output.is_file() 101 | assert seed_wasm_output.is_file() 102 | 103 | return (seed_wrapper_output, seed_wasm_output) 104 | -------------------------------------------------------------------------------- /src/funfuzz/loop_bot.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Loop of { update repos, call bot } to allow things to run unattended 8 | All command-line options are passed through to bot 9 | 10 | This script used to update funfuzz itself (when run as scripts, no longer supported) 11 | so it uses subprocess.run() rather than import 12 | 13 | Config-ish bits should move to bot, OR move into a config file, 14 | OR this file should subprocess-run ITSELF rather than using a while loop. 15 | """ 16 | 17 | import subprocess 18 | import sys 19 | import time 20 | 21 | 22 | def loop_seq(cmd_seq, wait_time): # pylint: disable=missing-param-doc,missing-type-doc 23 | """Call a sequence of commands in a loop. 24 | If any fails, sleep(wait_time) and go back to the beginning of the sequence.""" 25 | i = 0 26 | while True: 27 | i += 1 28 | print(f"localLoop #{i}!") 29 | for cmd in cmd_seq: 30 | try: 31 | subprocess.run(cmd, check=True) 32 | except subprocess.CalledProcessError as ex: 33 | print(f"Something went wrong when calling: {cmd!r}") 34 | print(f"{ex!r}") 35 | import traceback 36 | print(traceback.format_exc()) 37 | print(f"Waiting {wait_time} seconds...") 38 | time.sleep(wait_time) 39 | break 40 | 41 | 42 | def main(): # pylint: disable=missing-docstring 43 | loop_seq([ 44 | [sys.executable, "-u", "-m", "funfuzz.util.repos_update"], 45 | [sys.executable, "-u", "-m", "funfuzz.bot"] + [str(x) for x in sys.argv[1:]], 46 | ], 60) 47 | 48 | 49 | if __name__ == "__main__": 50 | main() 51 | -------------------------------------------------------------------------------- /src/funfuzz/run_ccoverage.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Runs coverage builds for a set amount of time and reports it to CovManager 8 | 9 | """ 10 | 11 | import argparse 12 | import logging 13 | from pathlib import Path 14 | import platform 15 | import sys 16 | import tempfile 17 | 18 | from .ccoverage import gatherer 19 | from .ccoverage import get_build 20 | from .ccoverage import reporter 21 | 22 | RUN_COV_LOG = logging.getLogger("funfuzz") 23 | 24 | 25 | def parse_args(args=None): 26 | """Parses arguments from the command line. 27 | 28 | Args: 29 | args (None): Argument parameters, defaults to None. 30 | 31 | Returns: 32 | class: Namespace of argparse parameters. 33 | """ 34 | arg_parser = argparse.ArgumentParser() 35 | arg_parser.add_argument("--report", action="store_true", help="Report results to FuzzManager") 36 | arg_parser.add_argument("--grcov_ver", 37 | default="0.5.1", 38 | help='Set the version of grcov to use. Defaults to "%(default)s".') 39 | arg_parser.add_argument("--url", 40 | required=True, 41 | help="URL to the downloadable js binary with coverage support") 42 | arg_parser.add_argument("-v", "--verbose", action="store_true", help="Show more information for debugging") 43 | return arg_parser.parse_args(args) 44 | 45 | 46 | def main(argparse_args=None): 47 | """Gets a coverage build, run it for a set amount of time then report it. 48 | 49 | Args: 50 | argparse_args (None): Argument parameters, defaults to None. 51 | """ 52 | if platform.system() != "Linux": 53 | sys.exit("Coverage mode must be run on Linux.") 54 | args = parse_args(argparse_args) 55 | logging.basicConfig(datefmt="%Y-%m-%d %H:%M:%S", 56 | format="%(asctime)s %(levelname)-8s %(message)s", 57 | level=logging.DEBUG if args.verbose else logging.INFO) 58 | logging.getLogger("flake8").setLevel(logging.ERROR) 59 | 60 | with tempfile.TemporaryDirectory(suffix="funfuzzcov") as dirpath: 61 | dirpath = Path(dirpath) 62 | 63 | get_build.get_coverage_build(dirpath, args) 64 | get_build.get_grcov(dirpath, args) 65 | cov_result_file = gatherer.gather_coverage(dirpath) 66 | if args.report: 67 | reporter.report_coverage(cov_result_file) 68 | reporter.disable_pool() 69 | 70 | 71 | if __name__ == "__main__": 72 | main() 73 | -------------------------------------------------------------------------------- /src/funfuzz/util/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # flake8: noqa 3 | # pylint: disable=missing-docstring 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | -------------------------------------------------------------------------------- /src/funfuzz/util/cdb_cmds.txt: -------------------------------------------------------------------------------- 1 | .echo Toggle for 32-bit/64-bit modes 2 | .echo See http://people.mozilla.org/~aklotz/windbgcheatsheet.html 3 | !wow64exts.sw 4 | .echo Display lines in stack trace 5 | .lines 6 | .echo .ecxr switches to the exception context frame 7 | .ecxr 8 | .echo Inspect program counter, equivalent of gdb's "x/i $pc" 9 | u 10 | .echo Inspect eip (32-bit) register, equivalent of gdb's "x/b $eax" 11 | db @@c++(@eip) L4 12 | .echo Inspect rip (64-bit) register, equivalent of gdb's "x/b $rax" 13 | db @@c++(@rip) L8 14 | .echo To switch frames: .frame /r /c 15 | .echo Then inspect locals using: dv 16 | .echo Running !analyze 17 | !analyze -v 18 | .echo Backtrace of faulting thread, limited to 50 frames 19 | ~#kn 50 20 | .echo Backtrace, limited to 50 frames (should execute after .ecxr) 21 | kb 50 22 | q 23 | -------------------------------------------------------------------------------- /src/funfuzz/util/crashesat.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Lithium's "crashesat" interestingness test to assess whether a binary crashes with a possibly-desired signature on 8 | the stack. 9 | 10 | Not merged into Lithium as it still relies on grab_crash_log. 11 | """ 12 | 13 | import argparse 14 | import logging 15 | from pathlib import Path 16 | 17 | import lithium.interestingness.timed_run as timedrun 18 | from lithium.interestingness.utils import file_contains 19 | 20 | from . import os_ops 21 | 22 | 23 | def interesting(cli_args, temp_prefix): 24 | """Interesting if the binary crashes with a possibly-desired signature on the stack. 25 | 26 | Args: 27 | cli_args (list): List of input arguments. 28 | temp_prefix (str): Temporary directory prefix, e.g. tmp1/1 or tmp4/1 29 | 30 | Returns: 31 | bool: True if the intended signature shows up on the stack, False otherwise. 32 | """ 33 | parser = argparse.ArgumentParser(prog="crashesat", 34 | usage="python3 -m lithium %(prog)s [options] binary [flags] testcase.ext") 35 | parser.add_argument("-r", "--regex", action="store_true", default=False, 36 | help="Allow search for regular expressions instead of strings.") 37 | parser.add_argument("-s", "--sig", default="", type=str, 38 | help="Match this crash signature. Defaults to '%default'.") 39 | parser.add_argument("-t", "--timeout", default=120, type=int, 40 | help="Optionally set the timeout. Defaults to '%default' seconds.") 41 | parser.add_argument("cmd_with_flags", nargs=argparse.REMAINDER) 42 | args = parser.parse_args(cli_args) 43 | 44 | log = logging.getLogger(__name__) 45 | 46 | # Examine stack for crash signature, this is needed if args.sig is specified. 47 | runinfo = timedrun.timed_run(args.cmd_with_flags, args.timeout, temp_prefix) 48 | if runinfo.sta == timedrun.CRASHED: 49 | os_ops.grab_crash_log(Path(args.cmd_with_flags[0]), runinfo.pid, Path(temp_prefix), True) 50 | 51 | crash_log = Path(f"{temp_prefix}-crash.txt") 52 | time_str = f" ({runinfo.elapsedtime:.3f} seconds)" 53 | 54 | if runinfo.sta == timedrun.CRASHED: 55 | if crash_log.resolve().is_file(): 56 | # When using this script, remember to escape characters, e.g. "\(" instead of "(" ! 57 | if file_contains(str(crash_log), args.sig.encode("utf-8"), args.regex)[0]: 58 | log.info("Exit status: %s%s", runinfo.msg, time_str) 59 | return True 60 | log.info("[Uninteresting] It crashed somewhere else!%s", time_str) 61 | return False 62 | log.info("[Uninteresting] It appeared to crash, but no crash log was found?%s", time_str) 63 | return False 64 | log.info("[Uninteresting] It didn't crash.%s", time_str) 65 | return False 66 | -------------------------------------------------------------------------------- /src/funfuzz/util/create_collector.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Functions here make use of a Collector created from FuzzManager. 8 | """ 9 | 10 | from pathlib import Path 11 | from time import sleep 12 | 13 | from Collector.Collector import Collector 14 | 15 | 16 | def make_collector(): 17 | """Creates a jsfunfuzz collector specifying ~/sigcache as the signature cache dir 18 | 19 | Returns: 20 | Collector: jsfunfuzz collector object 21 | """ 22 | sigcache_path = Path.home() / "sigcache" 23 | sigcache_path.mkdir(exist_ok=True) 24 | return Collector(sigCacheDir=str(sigcache_path), tool="jsfunfuzz") 25 | 26 | 27 | def printCrashInfo(crashInfo): # pylint: disable=invalid-name,missing-docstring 28 | if crashInfo.createShortSignature() != "No crash detected": 29 | print() 30 | print("crashInfo:") 31 | print(f" Short Signature: {crashInfo.createShortSignature()}") 32 | print(f" Class name: {crashInfo.__class__.__name__}") # "NoCrashInfo", etc 33 | print(f" Stack trace: {crashInfo.backtrace!r}") 34 | print() 35 | 36 | 37 | def printMatchingSignature(match): # pylint: disable=invalid-name,missing-docstring 38 | print("Matches signature in FuzzManager:") 39 | print(f' Signature description: {match[1].get("shortDescription")}') 40 | print(f" Signature file: {match[0]}") 41 | print() 42 | 43 | 44 | def submit_collector(collector, crash_info, testcase, quality, meta_data=None): 45 | """Use exponential backoff for FuzzManager submission. Adapted from https://stackoverflow.com/a/23961254/445241 46 | 47 | Args: 48 | collector (class): Collector for FuzzManager 49 | crash_info (object): Crash info object 50 | testcase (Path): Path to the testcase to be submitted 51 | quality (int): Quality specified as a number when submitting to FuzzManager 52 | meta_data (dict): Metadata when submitting testcase, if any 53 | """ 54 | if meta_data is None: 55 | meta_data = {} 56 | sleep_time = 2 57 | for _ in range(0, 99): 58 | try: 59 | collector.submit(crash_info, str(testcase), quality, metaData=meta_data) 60 | break 61 | except RuntimeError: 62 | # Sample error via Reporter: 63 | # RuntimeError: Server unexpectedly responded with status code 500:

Server Error (500)

64 | sleep(sleep_time) 65 | sleep_time *= 2 # exponential backoff 66 | -------------------------------------------------------------------------------- /src/funfuzz/util/file_manipulation.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Functions dealing with files and their contents. 8 | """ 9 | 10 | import io 11 | 12 | 13 | def amiss(log_prefix): # pylint: disable=missing-param-doc,missing-return-doc,missing-return-type-doc,missing-type-doc 14 | """Look for "szone_error" (Tiger), "malloc_error_break" (Leopard), "MallocHelp" (?) 15 | which are signs of malloc being unhappy (double free, out-of-memory, etc). 16 | """ 17 | found_something = False 18 | err_log = (log_prefix.parent / f"{log_prefix.stem}-err").with_suffix(".txt") 19 | with io.open(str(err_log), "r", encoding="utf-8", errors="replace") as f: 20 | for line in f: 21 | line = line.strip("\x07").rstrip("\n") 22 | if (line.find("szone_error") != -1 or 23 | line.find("malloc_error_break") != -1 or 24 | line.find("MallocHelp") != -1): 25 | print() 26 | print(line) 27 | found_something = True 28 | break # Don't flood the log with repeated malloc failures 29 | 30 | return found_something 31 | 32 | 33 | def fuzzSplice(filename): # pylint: disable=invalid-name,missing-param-doc,missing-return-doc,missing-return-type-doc 34 | # pylint: disable=missing-type-doc 35 | """Return the lines of a file, minus the ones between the two lines containing SPLICE.""" 36 | before = [] 37 | after = [] 38 | with io.open(str(filename), "r", encoding="utf-8", errors="replace") as f: 39 | for line in f: 40 | before.append(line) 41 | if line.find("SPLICE") != -1: 42 | break 43 | for line in f: 44 | if line.find("SPLICE") != -1: 45 | after.append(line) 46 | break 47 | for line in f: 48 | after.append(line) 49 | return [before, after] 50 | 51 | 52 | def linesWith(lines, search_for): # pylint: disable=invalid-name,missing-param-doc,missing-return-doc 53 | # pylint: disable=missing-return-type-doc,missing-type-doc 54 | """Return the lines from an array that contain a given string.""" 55 | matched = [] 56 | for line in lines: 57 | if line.find(search_for) != -1: 58 | matched.append(line) 59 | return matched 60 | 61 | 62 | def linesStartingWith(lines, search_for): # pylint: disable=invalid-name,missing-param-doc,missing-return-doc 63 | # pylint: disable=missing-return-type-doc,missing-type-doc 64 | """Return the lines from an array that start with a given string.""" 65 | matched = [] 66 | for line in lines: 67 | if line.startswith(search_for): 68 | matched.append(line) 69 | return matched 70 | 71 | 72 | def truncateMid(a, limit_each_side, insert_if_truncated): # pylint: disable=invalid-name,missing-param-doc 73 | # pylint: disable=missing-return-doc,missing-return-type-doc,missing-type-doc 74 | """Return a list with the middle portion removed, if it has more than limit_each_side*2 items.""" 75 | if len(a) <= limit_each_side + limit_each_side: 76 | return a 77 | return a[0:limit_each_side] + insert_if_truncated + a[-limit_each_side:] 78 | -------------------------------------------------------------------------------- /src/funfuzz/util/file_system_helpers.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Helper functions dealing with the files on the file system. 8 | """ 9 | 10 | import errno 11 | from pathlib import Path 12 | import platform 13 | import shutil 14 | import stat 15 | 16 | 17 | def delete_logs(log_prefix): # pylint: disable=too-complex 18 | """Whoever might call baseLevel should eventually call this function (unless a bug was found). 19 | 20 | If this turns up a WindowsError on Windows, remember to have excluded fuzzing locations in 21 | the search indexer, anti-virus realtime protection and backup applications. 22 | 23 | Args: 24 | log_prefix (Path): Prefix of the log name 25 | """ 26 | out_log = (log_prefix.parent / f"{log_prefix.stem}-out").with_suffix(".txt") 27 | if out_log.is_file(): 28 | out_log.unlink() 29 | if out_log.with_suffix(".binaryen-seed").is_file(): 30 | out_log.with_suffix(".binaryen-seed").unlink() 31 | if out_log.with_suffix(".wasm").is_file(): 32 | out_log.with_suffix(".wasm").unlink() 33 | if out_log.with_suffix(".wrapper").is_file(): 34 | out_log.with_suffix(".wrapper").unlink() 35 | err_log = (log_prefix.parent / f"{log_prefix.stem}-err").with_suffix(".txt") 36 | if err_log.is_file(): 37 | err_log.unlink() 38 | wasm_err_log = (log_prefix.parent / f"{log_prefix.stem}-wasm-err").with_suffix(".txt") 39 | if wasm_err_log.is_file(): 40 | wasm_err_log.unlink() 41 | wasm_out_log = (log_prefix.parent / f"{log_prefix.stem}-wasm-out").with_suffix(".txt") 42 | if wasm_out_log.is_file(): 43 | wasm_out_log.unlink() 44 | wasm_summary_log = (log_prefix.parent / f"{log_prefix.stem}-wasm-summary").with_suffix(".txt") 45 | if wasm_summary_log.is_file(): 46 | wasm_summary_log.unlink() 47 | crash_log = (log_prefix.parent / f"{log_prefix.stem}-crash").with_suffix(".txt") 48 | if crash_log.is_file(): 49 | crash_log.unlink() 50 | valgrind_xml = (log_prefix.parent / f"{log_prefix.stem}-vg").with_suffix(".xml") 51 | if valgrind_xml.is_file(): 52 | valgrind_xml.unlink() 53 | core_gzip = (log_prefix.parent / f"{log_prefix.stem}-core").with_suffix(".gz") 54 | if core_gzip.is_file(): 55 | core_gzip.unlink() 56 | 57 | 58 | def handle_rm_readonly_files(_func, path, exc): 59 | """Handle read-only files on Windows. Adapted from https://stackoverflow.com/a/21263493. 60 | 61 | Args: 62 | _func (function): Function which raised the exception 63 | path (str): Path name passed to function 64 | exc (exception): Exception information returned by sys.exc_info() 65 | 66 | Raises: 67 | OSError: Raised if the read-only files are unable to be handled 68 | """ 69 | assert platform.system() == "Windows" 70 | path = Path(path) 71 | if exc[1].errno == errno.EACCES: 72 | Path.chmod(path, stat.S_IWRITE) 73 | assert path.is_file() 74 | path.unlink() 75 | else: 76 | raise OSError("Unable to handle read-only files.") 77 | 78 | 79 | def rm_tree_incl_readonly_files(dir_tree): 80 | """Remove a directory tree including all read-only files. Directories should not be read-only. 81 | 82 | Args: 83 | dir_tree (Path): Directory tree of files to be removed 84 | """ 85 | shutil.rmtree(str(dir_tree), onerror=handle_rm_readonly_files if platform.system() == "Windows" else None) 86 | -------------------------------------------------------------------------------- /src/funfuzz/util/fork_join.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | """Functions dealing with multiple processes. 8 | """ 9 | 10 | import io 11 | import multiprocessing 12 | from pathlib import Path 13 | import sys 14 | 15 | 16 | # Call |fun| in a bunch of separate processes, then wait for them all to finish. 17 | # fun is called with someArgs, plus an additional argument with a numeric ID. 18 | # |fun| must be a top-level function (not a closure) so it can be pickled on Windows. 19 | def forkJoin(logDir, numProcesses, fun, *someArgs): # pylint: disable=invalid-name,missing-docstring 20 | def showFile(fn): # pylint: disable=invalid-name 21 | print(f"==== {fn} ====") 22 | print() 23 | with io.open(str(fn), "r", encoding="utf-8", errors="replace") as f: 24 | for line in f: 25 | print(line.rstrip()) 26 | print() 27 | 28 | # Fork a bunch of processes 29 | print(f"Forking {numProcesses} children...") 30 | ps = [] # pylint: disable=invalid-name 31 | for i in range(numProcesses): 32 | p = multiprocessing.Process( # pylint: disable=invalid-name 33 | target=redirectOutputAndCallFun, args=[logDir, i, fun, someArgs], name=f"Parallel process {i}") 34 | p.start() 35 | ps.append(p) 36 | 37 | # Wait for them all to finish, and splat their outputs 38 | for i in range(numProcesses): 39 | p = ps[i] # pylint: disable=invalid-name 40 | print(f"=== Waiting for child #{i} ({p.pid}) to finish... ===") 41 | p.join() 42 | print(f"=== Child process #{i} exited with code {p.exitcode} ===") 43 | print() 44 | showFile(log_name(logDir, i, "out")) 45 | showFile(log_name(logDir, i, "err")) 46 | print() 47 | 48 | 49 | # Functions used by forkJoin are top-level so they can be "pickled" (required on Windows) 50 | def log_name(log_dir, i, log_type): 51 | """Returns the path of the forkjoin log file as a string. 52 | 53 | Args: 54 | log_dir (str): Directory of the log file 55 | i (int): Log number 56 | log_type (str): Log type 57 | 58 | Returns: 59 | str: The forkjoin log file path 60 | """ 61 | return str(Path(log_dir) / f"forkjoin-{i}-{log_type}.txt") 62 | 63 | 64 | def redirectOutputAndCallFun(logDir, i, fun, someArgs): # pylint: disable=invalid-name,missing-docstring 65 | sys.stdout = io.open(log_name(logDir, i, "out"), "w", buffering=1) 66 | sys.stderr = io.open(log_name(logDir, i, "err"), "w", buffering=1) 67 | fun(*(someArgs + (i,))) 68 | 69 | 70 | # You should see: 71 | # * "Green Chairs" from the first few processes 72 | # * A pause and error (with stack trace) from process 5 73 | # * "Green Chairs" again from the rest. 74 | # def test_forkJoin(): 75 | # forkJoin(".", 8, test_forkJoin_inner, "Green", "Chairs") 76 | 77 | 78 | # def test_forkJoin_inner(adj, noun, forkjoin_id): 79 | # import time 80 | # print(f"{adj} {noun}") 81 | # print(forkjoin_id) 82 | # if forkjoin_id == 5: 83 | # time.sleep(1) 84 | # raise NameError() 85 | 86 | 87 | # if __name__ == "__main__": 88 | # print("test_forkJoin():") 89 | # test_forkJoin() 90 | -------------------------------------------------------------------------------- /src/funfuzz/util/gdb_cmds.txt: -------------------------------------------------------------------------------- 1 | set pagination 0 2 | set backtrace limit 50 3 | 4 | # SpiderMonkey unwinder is disabled until issues with gdb are fixed 5 | # https://hg.mozilla.org/integration/mozilla-inbound/rev/9861363aaea9 6 | # https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Hacking_Tips#Printing_the_JS_stack_%28from_gdb%29 7 | # echo \n\nenable unwinder .* SpiderMonkey\n\n 8 | # enable unwinder .* SpiderMonkey 9 | 10 | # nbp mentioned over IRC to first run `backtrace 0` in order to work around a "gdb issue" 11 | echo \n\nbacktrace 0\n\n 12 | backtrace 0 13 | 14 | # CrashSignature trips up with "thread apply all backtrace" for now. 15 | echo \n\nbacktrace\n\n 16 | backtrace 17 | 18 | # Could use "info all-registers" but that spews lots of junk 19 | echo \n\ninfo reg\n\n 20 | info registers 21 | 22 | echo \n\print $_siginfo\n\n 23 | print $_siginfo 24 | 25 | # Dump the disassembly of the crashing instruction and several following instructions. 26 | # ($pc is an alias for $eip or $rip as needed) 27 | echo \n\nx/8i $pc\n\n 28 | x/8i $pc 29 | 30 | echo \n\nthread apply all backtrace\n\n 31 | thread apply all backtrace 32 | -------------------------------------------------------------------------------- /src/funfuzz/util/get_hg_repo.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -ex 2 | 3 | # This script downloads a bundle from Mozilla's Mercurial repositories and extracts them. 4 | # 5 | # Arguments: