From 31ec959e9ba06c4fae70dc5dddfeb2257a4d0e48 Mon Sep 17 00:00:00 2001 From: Sathya Gunasekaran Date: Thu, 6 Jul 2023 11:44:14 +0530 Subject: [PATCH] [fuzzer] Initial import of v8's fuzzer Copied from https://chromium.googlesource.com/v8/v8/+/master/tools/clusterfuzz/js_fuzzer/ --- .../forget/packages/js-fuzzer/.eslintrc.js | 22 + compiler/forget/packages/js-fuzzer/.gitignore | 6 + .../forget/packages/js-fuzzer/DIR_METADATA | 11 + compiler/forget/packages/js-fuzzer/OWNERS | 7 + compiler/forget/packages/js-fuzzer/README.md | 122 +++++ .../forget/packages/js-fuzzer/build_db.js | 65 +++ compiler/forget/packages/js-fuzzer/corpus.js | 141 +++++ compiler/forget/packages/js-fuzzer/db.js | 485 ++++++++++++++++++ .../js-fuzzer/differential_script_mutator.js | 168 ++++++ .../forget/packages/js-fuzzer/exceptions.js | 256 +++++++++ .../packages/js-fuzzer/foozzie_launcher.py | 49 ++ .../packages/js-fuzzer/gen_exceptions.js | 196 +++++++ .../packages/js-fuzzer/gen_exceptions.sh | 12 + .../js-fuzzer/mutators/array_mutator.js | 115 +++++ .../packages/js-fuzzer/mutators/common.js | 376 ++++++++++++++ .../js-fuzzer/mutators/crossover_mutator.js | 85 +++ .../mutators/differential_fuzz_mutator.js | 225 ++++++++ .../js-fuzzer/mutators/expression_mutator.js | 63 +++ .../mutators/function_call_mutator.js | 149 ++++++ .../packages/js-fuzzer/mutators/mutator.js | 98 ++++ .../packages/js-fuzzer/mutators/normalizer.js | 89 ++++ .../js-fuzzer/mutators/number_mutator.js | 105 ++++ .../js-fuzzer/mutators/object_mutator.js | 135 +++++ .../packages/js-fuzzer/mutators/try_catch.js | 175 +++++++ .../js-fuzzer/mutators/variable_mutator.js | 73 +++ .../mutators/variable_or_object_mutation.js | 154 ++++++ .../forget/packages/js-fuzzer/package.json | 34 ++ compiler/forget/packages/js-fuzzer/package.sh | 32 ++ compiler/forget/packages/js-fuzzer/random.js | 113 ++++ .../resources/differential_fuzz_chakra.js | 17 + .../resources/differential_fuzz_jstest.js | 11 + .../resources/differential_fuzz_library.js | 122 +++++ .../resources/differential_fuzz_mjsunit.js | 8 + .../differential_fuzz_suppressions.js | 12 + .../resources/differential_fuzz_v8.js | 29 ++ .../js-fuzzer/resources/fuzz_library.js | 116 +++++ .../js-fuzzer/resources/jstest_stubs.js | 41 ++ .../packages/js-fuzzer/resources/stubs.js | 36 ++ compiler/forget/packages/js-fuzzer/run.js | 241 +++++++++ .../packages/js-fuzzer/script_mutator.js | 253 +++++++++ .../packages/js-fuzzer/source_helpers.js | 466 +++++++++++++++++ .../forget/packages/js-fuzzer/test/helpers.js | 75 +++ .../test/test_available_variables.js | 34 ++ .../packages/js-fuzzer/test/test_corpus.js | 113 ++++ .../forget/packages/js-fuzzer/test/test_db.js | 33 ++ .../js-fuzzer/test/test_differential_fuzz.js | 141 +++++ .../test/test_differential_fuzz_library.js | 113 ++++ .../packages/js-fuzzer/test/test_load.js | 69 +++ .../js-fuzzer/test/test_mutate_arrays.js | 47 ++ .../js-fuzzer/test/test_mutate_expressions.js | 79 +++ .../test/test_mutate_function_calls.js | 84 +++ .../js-fuzzer/test/test_mutate_numbers.js | 54 ++ .../js-fuzzer/test/test_mutate_objects.js | 47 ++ .../test/test_mutate_variable_or_object.js | 72 +++ .../js-fuzzer/test/test_mutate_variables.js | 47 ++ .../js-fuzzer/test/test_mutation_order.js | 56 ++ .../packages/js-fuzzer/test/test_normalize.js | 42 ++ .../packages/js-fuzzer/test/test_random.js | 51 ++ .../js-fuzzer/test/test_regressions.js | 113 ++++ .../packages/js-fuzzer/test/test_try_catch.js | 85 +++ .../js-fuzzer/test_data/JSTests/fake_stub.js | 5 + .../js-fuzzer/test_data/JSTests/load.js | 5 + .../test_data/JSTests/load_expected.js | 9 + .../test_data/available_variables.js | 33 ++ .../test_data/available_variables_expected.js | 270 ++++++++++ .../js-fuzzer/test_data/chakra/dir/load3.js | 6 + .../js-fuzzer/test_data/chakra/load.js | 9 + .../js-fuzzer/test_data/chakra/load1.js | 8 + .../js-fuzzer/test_data/chakra/load2.js | 5 + .../test_data/chakra/load_expected.js | 17 + .../cross_over_mutator_class_input.js | 11 + .../js-fuzzer/test_data/db/this/file.js | 9 + .../differential_fuzz/combined_expected.js | 59 +++ .../test_data/differential_fuzz/exceptions.js | 8 + .../differential_fuzz/exceptions_expected.js | 16 + .../differential_fuzz/fake_resource.js | 7 + .../test_data/differential_fuzz/input1.js | 9 + .../test_data/differential_fuzz/input2.js | 7 + .../test_data/differential_fuzz/mutations.js | 26 + .../differential_fuzz/mutations_expected.js | 44 ++ .../differential_fuzz/suppressions.js | 15 + .../suppressions_expected.js | 21 + .../v8_fuzz_experiments.json | 3 + .../differential_fuzz/v8_fuzz_flags.json | 3 + .../js-fuzzer/test_data/fake_db/index.json | 1 + .../js-fuzzer/test_data/mjsunit/mjsunit.js | 5 + .../js-fuzzer/test_data/mjsunit/test_load.js | 7 + .../test_data/mjsunit/test_load_0.js | 8 + .../test_data/mjsunit/test_load_1.js | 6 + .../test_data/mjsunit/test_load_2.js | 5 + .../test_data/mjsunit/test_load_3.js | 5 + .../test_data/mjsunit/test_load_expected.js | 21 + .../test_data/mjsunit/test_load_self.js | 5 + .../mjsunit/test_load_self_expected.js | 6 + .../mjsunit_softskipped/object-literal.js | 5 + .../mjsunit_softskipped/permitted.js | 5 + .../regress/binaryen-123.js | 5 + .../js-fuzzer/test_data/mutate_arrays.js | 34 ++ .../test_data/mutate_arrays_expected.js | 109 ++++ .../js-fuzzer/test_data/mutate_expressions.js | 8 + .../mutate_expressions_current_expected.js | 15 + .../mutate_expressions_previous_expected.js | 12 + .../test_data/mutate_function_call.js | 7 + .../mutate_function_call_baseline_expected.js | 16 + .../mutate_function_call_deopt_expected.js | 19 + .../mutate_function_call_expected.js | 23 + .../mutate_function_call_maglev_expected.js | 23 + .../js-fuzzer/test_data/mutate_numbers.js | 22 + .../test_data/mutate_numbers_expected.js | 46 ++ .../js-fuzzer/test_data/mutate_objects.js | 41 ++ .../test_data/mutate_objects_expected.js | 182 +++++++ .../js-fuzzer/test_data/mutate_var_or_obj.js | 10 + .../test_data/mutate_var_or_obj_expected.js | 37 ++ .../js-fuzzer/test_data/mutate_variables.js | 30 ++ .../test_data/mutate_variables_expected.js | 54 ++ .../test_data/mutation_order/input.js | 23 + .../mutation_order/output_expected.js | 119 +++++ .../packages/js-fuzzer/test_data/normalize.js | 23 + .../test_data/normalize_expected_0.js | 28 + .../test_data/normalize_expected_1.js | 28 + .../test_data/regress/numbers/db/index.json | 1 + .../regress/numbers/input_indices.js | 11 + .../regress/numbers/input_negative.js | 8 + .../regress/spidermonkey/db/index.json | 1 + .../test_data/regress/spidermonkey/input.js | 5 + .../test_data/regress/spidermonkey/shell.js | 5 + .../test_data/regress/strict/db/index.json | 1 + .../test_data/regress/strict/input_delete.js | 6 + .../test_data/regress/strict/input_strict.js | 7 + .../test_data/regress/strict/input_with.js | 7 + .../js-fuzzer/test_data/simple_test.js | 87 ++++ .../test_data/simple_test_expected.js | 177 +++++++ .../js-fuzzer/test_data/spidermonkey/load1.js | 5 + .../js-fuzzer/test_data/spidermonkey/shell.js | 7 + .../test_data/spidermonkey/test/load.js | 15 + .../test_data/spidermonkey/test/load2.js | 5 + .../spidermonkey/test/load_expected.js | 22 + .../test_data/spidermonkey/test/shell.js | 5 + .../packages/js-fuzzer/test_data/try_catch.js | 41 ++ .../test_data/try_catch_alternate_expected.js | 51 ++ .../js-fuzzer/test_data/try_catch_expected.js | 104 ++++ .../test_data/try_catch_nothing_expected.js | 49 ++ .../test_data/try_catch_toplevel_expected.js | 74 +++ compiler/forget/packages/js-fuzzer/test_db.js | 66 +++ .../packages/js-fuzzer/tools/fuzz_one.py | 43 ++ .../packages/js-fuzzer/tools/minimize.py | 44 ++ .../packages/js-fuzzer/tools/run_one.py | 106 ++++ .../packages/js-fuzzer/tools/workbench.py | 127 +++++ 148 files changed, 8946 insertions(+) create mode 100644 compiler/forget/packages/js-fuzzer/.eslintrc.js create mode 100644 compiler/forget/packages/js-fuzzer/.gitignore create mode 100644 compiler/forget/packages/js-fuzzer/DIR_METADATA create mode 100644 compiler/forget/packages/js-fuzzer/OWNERS create mode 100644 compiler/forget/packages/js-fuzzer/README.md create mode 100644 compiler/forget/packages/js-fuzzer/build_db.js create mode 100644 compiler/forget/packages/js-fuzzer/corpus.js create mode 100644 compiler/forget/packages/js-fuzzer/db.js create mode 100644 compiler/forget/packages/js-fuzzer/differential_script_mutator.js create mode 100644 compiler/forget/packages/js-fuzzer/exceptions.js create mode 100644 compiler/forget/packages/js-fuzzer/foozzie_launcher.py create mode 100644 compiler/forget/packages/js-fuzzer/gen_exceptions.js create mode 100755 compiler/forget/packages/js-fuzzer/gen_exceptions.sh create mode 100644 compiler/forget/packages/js-fuzzer/mutators/array_mutator.js create mode 100644 compiler/forget/packages/js-fuzzer/mutators/common.js create mode 100644 compiler/forget/packages/js-fuzzer/mutators/crossover_mutator.js create mode 100644 compiler/forget/packages/js-fuzzer/mutators/differential_fuzz_mutator.js create mode 100644 compiler/forget/packages/js-fuzzer/mutators/expression_mutator.js create mode 100644 compiler/forget/packages/js-fuzzer/mutators/function_call_mutator.js create mode 100644 compiler/forget/packages/js-fuzzer/mutators/mutator.js create mode 100644 compiler/forget/packages/js-fuzzer/mutators/normalizer.js create mode 100644 compiler/forget/packages/js-fuzzer/mutators/number_mutator.js create mode 100644 compiler/forget/packages/js-fuzzer/mutators/object_mutator.js create mode 100644 compiler/forget/packages/js-fuzzer/mutators/try_catch.js create mode 100644 compiler/forget/packages/js-fuzzer/mutators/variable_mutator.js create mode 100644 compiler/forget/packages/js-fuzzer/mutators/variable_or_object_mutation.js create mode 100644 compiler/forget/packages/js-fuzzer/package.json create mode 100755 compiler/forget/packages/js-fuzzer/package.sh create mode 100644 compiler/forget/packages/js-fuzzer/random.js create mode 100644 compiler/forget/packages/js-fuzzer/resources/differential_fuzz_chakra.js create mode 100644 compiler/forget/packages/js-fuzzer/resources/differential_fuzz_jstest.js create mode 100644 compiler/forget/packages/js-fuzzer/resources/differential_fuzz_library.js create mode 100644 compiler/forget/packages/js-fuzzer/resources/differential_fuzz_mjsunit.js create mode 100644 compiler/forget/packages/js-fuzzer/resources/differential_fuzz_suppressions.js create mode 100644 compiler/forget/packages/js-fuzzer/resources/differential_fuzz_v8.js create mode 100644 compiler/forget/packages/js-fuzzer/resources/fuzz_library.js create mode 100644 compiler/forget/packages/js-fuzzer/resources/jstest_stubs.js create mode 100644 compiler/forget/packages/js-fuzzer/resources/stubs.js create mode 100644 compiler/forget/packages/js-fuzzer/run.js create mode 100644 compiler/forget/packages/js-fuzzer/script_mutator.js create mode 100644 compiler/forget/packages/js-fuzzer/source_helpers.js create mode 100644 compiler/forget/packages/js-fuzzer/test/helpers.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_available_variables.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_corpus.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_db.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_differential_fuzz.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_differential_fuzz_library.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_load.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_mutate_arrays.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_mutate_expressions.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_mutate_function_calls.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_mutate_numbers.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_mutate_objects.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_mutate_variable_or_object.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_mutate_variables.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_mutation_order.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_normalize.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_random.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_regressions.js create mode 100644 compiler/forget/packages/js-fuzzer/test/test_try_catch.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/JSTests/fake_stub.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/JSTests/load.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/JSTests/load_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/available_variables.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/available_variables_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/chakra/dir/load3.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/chakra/load.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/chakra/load1.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/chakra/load2.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/chakra/load_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/cross_over_mutator_class_input.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/db/this/file.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/combined_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/exceptions.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/exceptions_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/fake_resource.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/input1.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/input2.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/mutations.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/mutations_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/suppressions.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/suppressions_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/v8_fuzz_experiments.json create mode 100644 compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/v8_fuzz_flags.json create mode 100644 compiler/forget/packages/js-fuzzer/test_data/fake_db/index.json create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mjsunit/mjsunit.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_0.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_1.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_2.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_3.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_self.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_self_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mjsunit_softskipped/object-literal.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mjsunit_softskipped/permitted.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mjsunit_softskipped/regress/binaryen-123.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_arrays.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_arrays_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_expressions.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_expressions_current_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_expressions_previous_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_function_call.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_baseline_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_deopt_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_maglev_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_numbers.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_numbers_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_objects.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_objects_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_var_or_obj.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_var_or_obj_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_variables.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutate_variables_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutation_order/input.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/mutation_order/output_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/normalize.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/normalize_expected_0.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/normalize_expected_1.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/regress/numbers/db/index.json create mode 100644 compiler/forget/packages/js-fuzzer/test_data/regress/numbers/input_indices.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/regress/numbers/input_negative.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/regress/spidermonkey/db/index.json create mode 100644 compiler/forget/packages/js-fuzzer/test_data/regress/spidermonkey/input.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/regress/spidermonkey/shell.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/regress/strict/db/index.json create mode 100644 compiler/forget/packages/js-fuzzer/test_data/regress/strict/input_delete.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/regress/strict/input_strict.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/regress/strict/input_with.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/simple_test.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/simple_test_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/spidermonkey/load1.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/spidermonkey/shell.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/load.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/load2.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/load_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/shell.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/try_catch.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/try_catch_alternate_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/try_catch_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/try_catch_nothing_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_data/try_catch_toplevel_expected.js create mode 100644 compiler/forget/packages/js-fuzzer/test_db.js create mode 100644 compiler/forget/packages/js-fuzzer/tools/fuzz_one.py create mode 100644 compiler/forget/packages/js-fuzzer/tools/minimize.py create mode 100644 compiler/forget/packages/js-fuzzer/tools/run_one.py create mode 100644 compiler/forget/packages/js-fuzzer/tools/workbench.py diff --git a/compiler/forget/packages/js-fuzzer/.eslintrc.js b/compiler/forget/packages/js-fuzzer/.eslintrc.js new file mode 100644 index 0000000000..f3ba3c962b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/.eslintrc.js @@ -0,0 +1,22 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module.exports = { + "env": { + "node": true, + "commonjs": true, + "es6": true, + "mocha": true + }, + "extends": "eslint:recommended", + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parserOptions": { + "ecmaVersion": 2018 + }, + "rules": { + } +}; diff --git a/compiler/forget/packages/js-fuzzer/.gitignore b/compiler/forget/packages/js-fuzzer/.gitignore new file mode 100644 index 0000000000..870328c522 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/ochang_js_fuzzer* +/db/ +/output.zip +/output/ +/workdir/ diff --git a/compiler/forget/packages/js-fuzzer/DIR_METADATA b/compiler/forget/packages/js-fuzzer/DIR_METADATA new file mode 100644 index 0000000000..9fc13203d7 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/DIR_METADATA @@ -0,0 +1,11 @@ +# Metadata information for this directory. +# +# For more information on DIR_METADATA files, see: +# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md +# +# For the schema of this file, see Metadata message: +# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto + +monorail { + component: "Infra>Client>V8" +} \ No newline at end of file diff --git a/compiler/forget/packages/js-fuzzer/OWNERS b/compiler/forget/packages/js-fuzzer/OWNERS new file mode 100644 index 0000000000..bc18f69af5 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/OWNERS @@ -0,0 +1,7 @@ +set noparent + +file:../../../INFRA_OWNERS + +msarms@chromium.org +mslekova@chromium.org +ochang@chromium.org diff --git a/compiler/forget/packages/js-fuzzer/README.md b/compiler/forget/packages/js-fuzzer/README.md new file mode 100644 index 0000000000..71a1140fe1 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/README.md @@ -0,0 +1,122 @@ +# JS-Fuzzer + +Javascript fuzzer for stand-alone shells like D8, Chakra, JSC or Spidermonkey. + +Original author: Oliver Chang + +# Building + +This fuzzer may require versions of node that are newer than available on +ClusterFuzz, so we use [pkg](https://github.com/zeit/pkg) to create a self +contained binary) out of this. + +## Prereqs +You need to intall nodejs and npm. Run `npm install` in this directory. + +## Fuzzing DB +This fuzzer requires a fuzzing DB. To build one, get the latest `web_tests.zip` +from [gs://clusterfuzz-data/web_tests.zip]( +https://storage.cloud.google.com/clusterfuzz-data/web_tests.zip) and unzip it +(note https://crbug.com/v8/10891 for making this data publicly available). +Then run: + +```bash +$ mkdir db +$ node build_db.js -i /path/to/web_tests -o db chakra v8 spidermonkey WebKit/JSTests +``` + +This may take a while. Optionally test the fuzzing DB with: + +```bash +$ node test_db.js -i db +``` + +## Building fuzzer +Then, to build the fuzzer, +```bash +$ ./node_modules/.bin/pkg -t node10-linux-x64 . +``` + +Replace "linux" with either "win" or "macos" for those platforms. + +This builds a binary named `ochang_js_fuzzer` for Linux / macOS OR +`ochang_js_fuzzer.exe` for Windows. + +## Packaging +Use `./package.sh`, `./package.sh win` or `./package.sh macos` to build and +create the `output.zip` archive or use these raw commands: +```bash +$ mkdir output +$ cd output +$ ln -s ../db db +$ ln -s ../ochang_js_fuzzer run +$ zip -r /path/output.zip * +``` + +**NOTE**: Add `.exe` to `ochang_js_fuzzer` and `run` filename above if archiving +for Windows platform. + +# Development + +Run the tests with: + +```bash +$ npm test +``` + +When test expectations change, generate them with: + +```bash +$ GENERATE=1 npm test +``` + +# Generating exceptional configurations + +Tests that fail to parse or show very bad performance can be automatically +skipped or soft-skipped with the following script (takes >1h): + +```bash +$ WEB_TESTS=/path/to/web_tests OUTPUT=/path/to/output/folder ./gen_exceptions.sh +``` + +# Experimenting (limited to differential fuzzing) + +To locally evaluate the fuzzer, setup a work directory as follows: + +```bash +$ workdir/ +$ workdir/app_dir +$ workdir/fuzzer +$ workdir/input +$ workdir/output +``` + +The `app_dir` folder can be a symlink or should contain the bundled +version of `d8` with all files required for execution. +Copy the packaged `ochang_js_fuzzer` executable and the `db` folder +to the `fuzzer` directory or use a symlink. +The `input` directory is the root folder of the corpus, i.e. pointing +to the unzipped data of `gs://clusterfuzz-data/web_tests.zip`. +The `output` directory is expected to be empty. It'll contain all +output of the fuzzing session. Start the experiments with: + +```bash +$ # Around ~40000 corresponds to 24h of fuzzing on a workstation. +$ NUM_RUNS = 40000 +$ python tools/workbench.py $NUM_RUNS +``` + +You can check current stats with: + +```bash +$ cat workdir/output/stats.json | python -m json.tool +``` + +When failures are found, you can forge minimization command lines with: + +```bash +$ MINIMIZER_PATH = path/to/minimizer +$ python tools/minimize.py $MINIMIZER_PATH +``` + +The path should point to a local checkout of the [minimizer](https://chrome-internal.googlesource.com/chrome/tools/clusterfuzz/+/refs/heads/master/src/python/bot/minimizer/). diff --git a/compiler/forget/packages/js-fuzzer/build_db.js b/compiler/forget/packages/js-fuzzer/build_db.js new file mode 100644 index 0000000000..c00d286eb1 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/build_db.js @@ -0,0 +1,65 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Collect JS nodes. + */ + +const program = require('commander'); + +const corpus = require('./corpus.js'); +const db = require('./db.js'); +const path = require('path'); + +const sourceHelpers = require('./source_helpers.js'); + +function main() { + Error.stackTraceLimit = Infinity; + + program + .version('0.0.1') + .option('-i, --input_dir ', 'Input directory.') + .option('-o, --output_dir ', 'Output directory.') + .parse(process.argv); + + if (!program.args.length) { + console.log('Need to specify corpora.'); + return; + } + + if (!program.output_dir) { + console.log('Need to specify output dir.'); + return; + } + + const mutateDb = new db.MutateDbWriter(program.output_dir); + + const inputDir = path.resolve(program.input_dir); + for (const corpusName of program.args) { + const curCorpus = new corpus.Corpus(inputDir, corpusName); + for (const relPath of curCorpus.relFiles()) { + let source; + try { + source = sourceHelpers.loadSource(inputDir, relPath); + } catch (e) { + console.log(e); + continue; + } + + if (!source) { + continue; + } + + try{ + mutateDb.process(source); + } catch (e) { + console.log(e); + } + } + } + + mutateDb.writeIndex(); +} + +main(); diff --git a/compiler/forget/packages/js-fuzzer/corpus.js b/compiler/forget/packages/js-fuzzer/corpus.js new file mode 100644 index 0000000000..d186ce8556 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/corpus.js @@ -0,0 +1,141 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Corpus + */ + +const program = require('commander'); +const fs = require('fs'); +const path = require('path'); + +const exceptions = require('./exceptions.js'); +const random = require('./random.js'); +const sourceHelpers = require('./source_helpers.js'); + +function* walkDirectory(directory, filter) { + // Generator for recursively walk a directory. + for (const filePath of fs.readdirSync(directory)) { + const currentPath = path.join(directory, filePath); + const stat = fs.lstatSync(currentPath); + if (stat.isFile()) { + if (!filter || filter(currentPath)) { + yield currentPath; + } + continue; + } + + if (stat.isDirectory()) { + for (let childFilePath of walkDirectory(currentPath, filter)) { + yield childFilePath; + } + } + } +} + +class Corpus { + // Input corpus. + constructor(inputDir, corpusName, extraStrict=false) { + this.inputDir = inputDir; + this.extraStrict = extraStrict; + + // Filter for permitted JS files. + function isPermittedJSFile(absPath) { + return (absPath.endsWith('.js') && + !exceptions.isTestSkippedAbs(absPath)); + } + + // Cache relative paths of all files in corpus. + this.skippedFiles = []; + this.softSkippedFiles = []; + this.permittedFiles = []; + const directory = path.join(inputDir, corpusName); + for (const absPath of walkDirectory(directory, isPermittedJSFile)) { + const relPath = path.relative(this.inputDir, absPath); + if (exceptions.isTestSkippedRel(relPath)) { + this.skippedFiles.push(relPath); + } else if (exceptions.isTestSoftSkippedAbs(absPath) || + exceptions.isTestSoftSkippedRel(relPath)) { + this.softSkippedFiles.push(relPath); + } else { + this.permittedFiles.push(relPath); + } + } + random.shuffle(this.softSkippedFiles); + random.shuffle(this.permittedFiles); + } + + // Relative paths of all files in corpus. + *relFiles() { + for (const relPath of this.permittedFiles) { + yield relPath; + } + for (const relPath of this.softSkippedFiles) { + yield relPath; + } + } + + // Relative paths of all files in corpus including generated skipped. + *relFilesForGenSkipped() { + for (const relPath of this.relFiles()) { + yield relPath; + } + for (const relPath of this.skippedFiles) { + yield relPath; + } + } + + /** + * Returns "count" relative test paths, randomly selected from soft-skipped + * and permitted files. Permitted files have a 4 times higher chance to + * be chosen. + */ + getRandomTestcasePaths(count) { + return random.twoBucketSample( + this.softSkippedFiles, this.permittedFiles, 4, count); + } + + loadTestcase(relPath, strict, label) { + const start = Date.now(); + try { + const source = sourceHelpers.loadSource(this.inputDir, relPath, strict); + if (program.verbose) { + const duration = Date.now() - start; + console.log(`Parsing ${relPath} ${label} took ${duration} ms.`); + } + return source; + } catch (e) { + console.log(`WARNING: failed to ${label} parse ${relPath}`); + console.log(e); + } + return undefined; + } + + *loadTestcases(relPaths) { + for (const relPath of relPaths) { + if (this.extraStrict) { + // When re-generating the files marked sloppy, we additionally test if + // the file parses in strict mode. + this.loadTestcase(relPath, true, 'strict'); + } + const source = this.loadTestcase(relPath, false, 'sloppy'); + if (source) { + yield source; + } + } + } + + getRandomTestcases(count) { + return Array.from(this.loadTestcases(this.getRandomTestcasePaths(count))); + } + + getAllTestcases() { + return this.loadTestcases(this.relFilesForGenSkipped()); + } +} + +module.exports = { + Corpus: Corpus, + walkDirectory: walkDirectory, +} diff --git a/compiler/forget/packages/js-fuzzer/db.js b/compiler/forget/packages/js-fuzzer/db.js new file mode 100644 index 0000000000..3fbe438023 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/db.js @@ -0,0 +1,485 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Mutation Db. + */ + +const crypto = require('crypto'); +const fs = require('fs'); +const fsPath = require('path'); + +const babelGenerator = require('@babel/generator').default; +const babelTemplate = require('@babel/template').default; +const babelTraverse = require('@babel/traverse').default; +const babelTypes = require('@babel/types'); +const globals = require('globals'); + +const random = require('./random.js'); +const sourceHelpers = require('./source_helpers.js'); + +const globalIdentifiers = new Set(Object.keys(globals.builtin)); +const propertyNames = new Set([ + // Parsed from https://github.com/tc39/ecma262/blob/master/spec.html + 'add', + 'anchor', + 'apply', + 'big', + 'bind', + 'blink', + 'bold', + 'buffer', + 'byteLength', + 'byteOffset', + 'BYTES_PER_ELEMENT', + 'call', + 'catch', + 'charAt', + 'charCodeAt', + 'clear', + 'codePointAt', + 'compile', + 'concat', + 'constructor', + 'copyWithin', + '__defineGetter__', + '__defineSetter__', + 'delete', + 'endsWith', + 'entries', + 'every', + 'exec', + 'fill', + 'filter', + 'find', + 'findIndex', + 'fixed', + 'flags', + 'fontcolor', + 'fontsize', + 'forEach', + 'get', + 'getDate', + 'getDay', + 'getFloat32', + 'getFloat64', + 'getFullYear', + 'getHours', + 'getInt16', + 'getInt32', + 'getInt8', + 'getMilliseconds', + 'getMinutes', + 'getMonth', + 'getSeconds', + 'getTime', + 'getTimezoneOffset', + 'getUint16', + 'getUint32', + 'getUint8', + 'getUTCDate', + 'getUTCDay', + 'getUTCFullYear', + 'getUTCHours', + 'getUTCMilliseconds', + 'getUTCMinutes', + 'getUTCMonth', + 'getUTCSeconds', + 'getYear', + 'global', + 'has', + 'hasInstance', + 'hasOwnProperty', + 'ignoreCase', + 'includes', + 'indexOf', + 'isConcatSpreadable', + 'isPrototypeOf', + 'italics', + 'iterator', + 'join', + 'keys', + 'lastIndexOf', + 'length', + 'link', + 'localeCompare', + '__lookupGetter__', + '__lookupSetter__', + 'map', + 'match', + 'match', + 'message', + 'multiline', + 'name', + 'next', + 'normalize', + 'padEnd', + 'padStart', + 'pop', + 'propertyIsEnumerable', + '__proto__', + 'prototype', + 'push', + 'reduce', + 'reduceRight', + 'repeat', + 'replace', + 'replace', + 'return', + 'reverse', + 'search', + 'search', + 'set', + 'set', + 'setDate', + 'setFloat32', + 'setFloat64', + 'setFullYear', + 'setHours', + 'setInt16', + 'setInt32', + 'setInt8', + 'setMilliseconds', + 'setMinutes', + 'setMonth', + 'setSeconds', + 'setTime', + 'setUint16', + 'setUint32', + 'setUint8', + 'setUTCDate', + 'setUTCFullYear', + 'setUTCHours', + 'setUTCMilliseconds', + 'setUTCMinutes', + 'setUTCMonth', + 'setUTCSeconds', + 'setYear', + 'shift', + 'size', + 'slice', + 'slice', + 'small', + 'some', + 'sort', + 'source', + 'species', + 'splice', + 'split', + 'split', + 'startsWith', + 'sticky', + 'strike', + 'sub', + 'subarray', + 'substr', + 'substring', + 'sup', + 'test', + 'then', + 'throw', + 'toDateString', + 'toExponential', + 'toFixed', + 'toGMTString', + 'toISOString', + 'toJSON', + 'toLocaleDateString', + 'toLocaleLowerCase', + 'toLocaleString', + 'toLocaleTimeString', + 'toLocaleUpperCase', + 'toLowerCase', + 'toPrecision', + 'toPrimitive', + 'toString', + 'toStringTag', + 'toTimeString', + 'toUpperCase', + 'toUTCString', + 'trim', + 'unicode', + 'unscopables', + 'unshift', + 'valueOf', + 'values', +]); + +const MAX_DEPENDENCIES = 2; + +class Expression { + constructor(type, source, isStatement, originalPath, + dependencies, needsSuper) { + this.type = type; + this.source = source; + this.isStatement = isStatement; + this.originalPath = originalPath; + this.dependencies = dependencies; + this.needsSuper = needsSuper; + } +} + +function dedupKey(expression) { + if (!expression.dependencies) { + return expression.source; + } + + let result = expression.source; + for (let dependency of expression.dependencies) { + result = result.replace(new RegExp(dependency, 'g'), 'ID'); + } + + return result; +} + +function _markSkipped(path) { + while (path) { + path.node.__skipped = true; + path = path.parentPath; + } +} + +/** + * Returns true if an expression can be applied or false otherwise. + */ +function isValid(expression) { + const expressionTemplate = babelTemplate( + expression.source, + sourceHelpers.BABYLON_REPLACE_VAR_OPTIONS); + + const dependencies = {}; + if (expression.dependencies) { + for (const dependency of expression.dependencies) { + dependencies[dependency] = babelTypes.identifier('__v_0'); + } + } + + try { + expressionTemplate(dependencies); + } catch (e) { + return false; + } + return true; +} + +class MutateDbWriter { + constructor(outputDir) { + this.seen = new Set(); + this.outputDir = fsPath.resolve(outputDir); + this.index = { + statements: [], + superStatements: [], + all: [], + }; + } + + process(source) { + let self = this; + + let varIndex = 0; + + // First pass to collect dependency information. + babelTraverse(source.ast, { + Super(path) { + while (path) { + path.node.__needsSuper = true; + path = path.parentPath; + } + }, + + YieldExpression(path) { + // Don't include yield expressions in DB. + _markSkipped(path); + }, + + Identifier(path) { + if (globalIdentifiers.has(path.node.name) && + path.node.name != 'eval') { + // Global name. + return; + } + + if (propertyNames.has(path.node.name) && + path.parentPath.isMemberExpression() && + path.parentKey !== 'object') { + // Builtin property name. + return; + } + + let binding = path.scope.getBinding(path.node.name); + if (!binding) { + // Unknown dependency. Don't handle this. + _markSkipped(path); + return; + } + + let newName; + if (path.node.name.startsWith('VAR_')) { + newName = path.node.name; + } else if (babelTypes.isFunctionDeclaration(binding.path.node) || + babelTypes.isFunctionExpression(binding.path.node) || + babelTypes.isDeclaration(binding.path.node) || + babelTypes.isFunctionExpression(binding.path.node)) { + // Unknown dependency. Don't handle this. + _markSkipped(path); + return; + } else { + newName = 'VAR_' + varIndex++; + path.scope.rename(path.node.name, newName); + } + + // Mark all parents as having a dependency. + while (path) { + path.node.__idDependencies = path.node.__idDependencies || []; + if (path.node.__idDependencies.length <= MAX_DEPENDENCIES) { + path.node.__idDependencies.push(newName); + } + path = path.parentPath; + } + } + }); + + babelTraverse(source.ast, { + Expression(path) { + if (!path.parentPath.isExpressionStatement()) { + return; + } + + if (path.node.__skipped || + (path.node.__idDependencies && + path.node.__idDependencies.length > MAX_DEPENDENCIES)) { + return; + } + + if (path.isIdentifier() || path.isMemberExpression() || + path.isConditionalExpression() || + path.isBinaryExpression() || path.isDoExpression() || + path.isLiteral() || + path.isObjectExpression() || path.isArrayExpression()) { + // Skip: + // - Identifiers. + // - Member expressions (too many and too context dependent). + // - Conditional expressions (too many and too context dependent). + // - Binary expressions (too many). + // - Literals (too many). + // - Object/array expressions (too many). + return; + } + + if (path.isAssignmentExpression()) { + if (!babelTypes.isMemberExpression(path.node.left)) { + // Skip assignments that aren't to properties. + return; + } + + if (babelTypes.isIdentifier(path.node.left.object)) { + if (babelTypes.isNumericLiteral(path.node.left.property)) { + // Skip VAR[\d+] = ...; + // There are too many and they generally aren't very useful. + return; + } + + if (babelTypes.isStringLiteral(path.node.left.property) && + !propertyNames.has(path.node.left.property.value)) { + // Skip custom properties. e.g. + // VAR["abc"] = ...; + // There are too many and they generally aren't very useful. + return; + } + } + } + + if (path.isCallExpression() && + babelTypes.isIdentifier(path.node.callee) && + !globalIdentifiers.has(path.node.callee.name)) { + // Skip VAR(...) calls since there's too much context we're missing. + return; + } + + if (path.isUnaryExpression() && path.node.operator == '-') { + // Skip -... since there are too many. + return; + } + + // Make the template. + let generated = babelGenerator(path.node, { concise: true }).code; + let expression = new Expression( + path.node.type, + generated, + path.parentPath.isExpressionStatement(), + source.relPath, + path.node.__idDependencies, + Boolean(path.node.__needsSuper)); + + // Try to de-dupe similar expressions. + let key = dedupKey(expression); + if (self.seen.has(key)) { + return; + } + + // Test results. + if (!isValid(expression)) { + return; + } + + // Write results. + let dirPath = fsPath.join(self.outputDir, expression.type); + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath); + } + + let sha1sum = crypto.createHash('sha1'); + sha1sum.update(key); + + let filePath = fsPath.join(dirPath, sha1sum.digest('hex') + '.json'); + fs.writeFileSync(filePath, JSON.stringify(expression)); + + let relPath = fsPath.relative(self.outputDir, filePath); + + // Update index. + self.seen.add(key); + self.index.all.push(relPath); + + if (expression.needsSuper) { + self.index.superStatements.push(relPath); + } else { + self.index.statements.push(relPath); + } + } + }); + } + + writeIndex() { + fs.writeFileSync( + fsPath.join(this.outputDir, 'index.json'), + JSON.stringify(this.index)); + } +} + +class MutateDb { + constructor(outputDir) { + this.outputDir = fsPath.resolve(outputDir); + this.index = JSON.parse( + fs.readFileSync(fsPath.join(outputDir, 'index.json'), 'utf-8')); + } + + getRandomStatement({canHaveSuper=false} = {}) { + let choices; + if (canHaveSuper) { + choices = random.randInt(0, 1) ? + this.index.all : this.index.superStatements; + } else { + choices = this.index.statements; + } + + let path = fsPath.join( + this.outputDir, choices[random.randInt(0, choices.length - 1)]); + return JSON.parse(fs.readFileSync(path), 'utf-8'); + } +} + +module.exports = { + MutateDb: MutateDb, + MutateDbWriter: MutateDbWriter, +} diff --git a/compiler/forget/packages/js-fuzzer/differential_script_mutator.js b/compiler/forget/packages/js-fuzzer/differential_script_mutator.js new file mode 100644 index 0000000000..6c43f645af --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/differential_script_mutator.js @@ -0,0 +1,168 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Script mutator for differential fuzzing. + */ + +'use strict'; + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const common = require('./mutators/common.js'); +const random = require('./random.js'); +const sourceHelpers = require('./source_helpers.js'); + +const { filterDifferentialFuzzFlags } = require('./exceptions.js'); +const { DifferentialFuzzMutator, DifferentialFuzzSuppressions } = require( + './mutators/differential_fuzz_mutator.js'); +const { ScriptMutator } = require('./script_mutator.js'); + + +const USE_ORIGINAL_FLAGS_PROB = 0.2; + +/** + * Randomly chooses a configuration from experiments. The configuration + * parameters are expected to be passed from a bundled V8 build. Constraints + * mentioned below are enforced by PRESUBMIT checks on the V8 side. + * + * @param {Object[]} experiments List of tuples (probability, first config name, + * second config name, second d8 name). The probabilities are integers in + * [0,100]. We assume the sum of all probabilities is 100. + * @param {Object[]} additionalFlags List of tuples (probability, flag strings). + * Probability is in [0,1). + * @return {string[]} List of flags for v8_foozzie.py. + */ +function chooseRandomFlags(experiments, additionalFlags) { + // Add additional flags to second config based on experiment percentages. + const extra_flags = []; + for (const [p, flags] of additionalFlags) { + if (random.choose(p)) { + for (const flag of flags.split(' ')) { + extra_flags.push('--second-config-extra-flags=' + flag); + } + } + } + + // Calculate flags determining the experiment. + let acc = 0; + const threshold = random.random() * 100; + for (let [prob, first_config, second_config, second_d8] of experiments) { + acc += prob; + if (acc > threshold) { + return [ + '--first-config=' + first_config, + '--second-config=' + second_config, + '--second-d8=' + second_d8, + ].concat(extra_flags); + } + } + // Unreachable. + assert(false); +} + +function loadJSONFromBuild(name) { + assert(process.env.APP_DIR); + const fullPath = path.join(path.resolve(process.env.APP_DIR), name); + return JSON.parse(fs.readFileSync(fullPath, 'utf-8')); +} + +function hasMjsunit(dependencies) { + return dependencies.some(dep => dep.relPath.endsWith('mjsunit.js')); +} + +function hasJSTests(dependencies) { + return dependencies.some(dep => dep.relPath.endsWith('jstest_stubs.js')); +} + +class DifferentialScriptMutator extends ScriptMutator { + constructor(settings, db_path) { + super(settings, db_path); + + // Mutators for differential fuzzing. + this.differential = [ + new DifferentialFuzzSuppressions(settings), + new DifferentialFuzzMutator(settings), + ]; + + // Flag configurations from the V8 build directory. + this.experiments = loadJSONFromBuild('v8_fuzz_experiments.json'); + this.additionalFlags = loadJSONFromBuild('v8_fuzz_flags.json'); + } + + /** + * Performes the high-level mutation and afterwards adds flags for the + * v8_foozzie.py harness. + */ + mutateMultiple(inputs) { + const result = super.mutateMultiple(inputs); + const originalFlags = []; + + // Keep original JS flags in some cases. Let the harness pass them to + // baseline _and_ comparison run. + if (random.choose(USE_ORIGINAL_FLAGS_PROB)) { + for (const flag of filterDifferentialFuzzFlags(result.flags)) { + originalFlags.push('--first-config-extra-flags=' + flag); + originalFlags.push('--second-config-extra-flags=' + flag); + } + } + + // Add flags for the differnetial-fuzzing settings. + const fuzzFlags = chooseRandomFlags(this.experiments, this.additionalFlags); + result.flags = fuzzFlags.concat(originalFlags); + return result; + } + + /** + * Mutatates a set of inputs. + * + * Additionally we prepare inputs by tagging each with the original source + * path for later printing. The mutated sources are post-processed by the + * differential-fuzz mutators, adding extra printing and other substitutions. + */ + mutateInputs(inputs) { + inputs.forEach(input => common.setOriginalPath(input, input.relPath)); + + const result = super.mutateInputs(inputs); + this.differential.forEach(mutator => mutator.mutate(result)); + return result; + } + + /** + * Adds extra dependencies for differential fuzzing. + */ + resolveDependencies(inputs) { + const dependencies = super.resolveDependencies(inputs); + // The suppression file neuters functions not working with differential + // fuzzing. It can also be used to temporarily silence some functionality + // leading to dupes of an active bug. + dependencies.push( + sourceHelpers.loadResource('differential_fuzz_suppressions.js')); + // Extra printing and tracking functionality. + dependencies.push( + sourceHelpers.loadResource('differential_fuzz_library.js')); + // Make Chakra tests print more. + dependencies.push( + sourceHelpers.loadResource('differential_fuzz_chakra.js')); + + if (hasMjsunit(dependencies)) { + // Make V8 tests print more. We guard this as the functionality + // relies on mjsunit.js. + dependencies.push(sourceHelpers.loadResource('differential_fuzz_v8.js')); + } + + if (hasJSTests(dependencies)) { + dependencies.push( + sourceHelpers.loadResource('differential_fuzz_jstest.js')); + } + + return dependencies; + } +} + +module.exports = { + DifferentialScriptMutator: DifferentialScriptMutator, +}; diff --git a/compiler/forget/packages/js-fuzzer/exceptions.js b/compiler/forget/packages/js-fuzzer/exceptions.js new file mode 100644 index 0000000000..4269a383d1 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/exceptions.js @@ -0,0 +1,256 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Blacklists for fuzzer. + */ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const random = require('./random.js'); + +const {generatedSloppy, generatedSoftSkipped, generatedSkipped} = require( + './generated/exceptions.js'); + +const SKIPPED_FILES = [ + // Disabled for unexpected test behavior, specific to d8 shell. + 'd8-os.js', + 'd8-readbuffer.js', + + // Passes JS flags. + 'd8-arguments.js', + + // Slow tests or tests that are too large to be used as input. + /numops-fuzz-part.*.js/, + 'regexp-pcre.js', + 'unicode-test.js', + 'unicodelctest.js', + 'unicodelctest-no-optimization.js', + + // Unsupported modules. + /^modules.*\.js/, + + // Unsupported property escapes. + /^regexp-property-.*\.js/, + + // Bad testcases that just loads a script that always throws errors. + 'regress-444805.js', + 'regress-crbug-489597.js', + 'regress-crbug-620253.js', + + // Just recursively loads itself. + 'regress-8510.js', +]; + +const SKIPPED_DIRECTORIES = [ + // Slow tests or tests that are too large to be used as input. + 'embenchen', + 'poppler', + 'sqlite', + + // Causes lots of failures. + 'test262', + + // Unavailable debug.Debug. + 'v8/test/debugger', + 'v8/test/inspector', + + // Unsupported modules. + 'v8/test/js-perf-test/Modules', + + // Contains tests expected to error out on parsing. + 'v8/test/message', + + // Needs specific dependencies for load of various tests. + 'v8/test/mjsunit/tools', + + // Unsupported e4x standard. + 'mozilla/data/e4x', + + // Bails out fast without ReadableStream support. + 'spidermonkey/non262/ReadableStream', +]; + +// Files used with a lower probability. +const SOFT_SKIPPED_FILES = [ + // Tests with large binary content. + /^binaryen.*\.js/, + + // Tests slow to parse. + // CrashTests: + /^jquery.*\.js/, + // Spidermonkey: + 'regress-308085.js', + 'regress-74474-002.js', + 'regress-74474-003.js', + // V8: + 'object-literal.js', +]; + +// Flags that lead to false positives or that are already passed by default. +const DISALLOWED_FLAGS = [ + // Disallowed because features prefixed with "experimental" are not + // stabilized yet and would cause too much noise when enabled. + /^--experimental-.*/, + + // Disallowed due to noise. We explicitly add --harmony to job + // definitions, and all of these features are staged before launch. + /^--harmony-.*/, + + // Disallowed because they are passed explicitly on the command line. + '--allow-natives-syntax', + '--debug-code', + '--harmony', + '--wasm-staging', + '--expose-gc', + '--expose_gc', + '--icu-data-file', + '--random-seed', + + // Disallowed due to false positives. + '--check-handle-count', + '--correctness-fuzzer-suppressions', + '--expose-debug-as', + '--expose-natives-as', + '--expose-trigger-failure', + '--mock-arraybuffer-allocator', + 'natives', // Used in conjuction with --expose-natives-as. + /^--trace-path.*/, +]; + +// Flags only used with 25% probability. +const LOW_PROB_FLAGS_PROB = 0.25; +const LOW_PROB_FLAGS = [ + // Flags that lead to slow test performance. + /^--gc-interval.*/, + /^--deopt-every-n-times.*/, +]; + + +// Flags printing data, leading to false positives in differential fuzzing. +const DISALLOWED_DIFFERENTIAL_FUZZ_FLAGS = [ + /^--gc-interval.*/, + /^--perf.*/, + /^--print.*/, + /^--stress-runs.*/, + /^--trace.*/, + '--expose-externalize-string', + '--interpreted-frames-native-stack', + '--validate-asm', +]; + +const MAX_FILE_SIZE_BYTES = 128 * 1024; // 128KB +const MEDIUM_FILE_SIZE_BYTES = 32 * 1024; // 32KB + +function _findMatch(iterable, candidate) { + for (const entry of iterable) { + if (typeof entry === 'string') { + if (entry === candidate) { + return true; + } + } else { + if (entry.test(candidate)) { + return true; + } + } + } + + return false; +} + +function _doesntMatch(iterable, candidate) { + return !_findMatch(iterable, candidate); +} + +// Convert Windows path separators. +function normalize(testPath) { + return path.normalize(testPath).replace(/\\/g, '/'); +} + +function isTestSkippedAbs(absPath) { + const basename = path.basename(absPath); + if (_findMatch(SKIPPED_FILES, basename)) { + return true; + } + + const normalizedTestPath = normalize(absPath); + for (const entry of SKIPPED_DIRECTORIES) { + if (normalizedTestPath.includes(entry)) { + return true; + } + } + + // Avoid OOM/hangs through huge inputs. + const stat = fs.statSync(absPath); + return (stat && stat.size >= MAX_FILE_SIZE_BYTES); +} + +function isTestSkippedRel(relPath) { + return generatedSkipped.has(normalize(relPath)); +} + +// For testing. +function getSoftSkipped() { + return SOFT_SKIPPED_FILES; +} + +// For testing. +function getGeneratedSoftSkipped() { + return generatedSoftSkipped; +} + +// For testing. +function getGeneratedSloppy() { + return generatedSloppy; +} + +function isTestSoftSkippedAbs(absPath) { + const basename = path.basename(absPath); + if (_findMatch(this.getSoftSkipped(), basename)) { + return true; + } + + // Graylist medium size files. + const stat = fs.statSync(absPath); + return (stat && stat.size >= MEDIUM_FILE_SIZE_BYTES); +} + +function isTestSoftSkippedRel(relPath) { + return this.getGeneratedSoftSkipped().has(normalize(relPath)); +} + +function isTestSloppyRel(relPath) { + return this.getGeneratedSloppy().has(normalize(relPath)); +} + +function filterFlags(flags) { + return flags.filter(flag => { + return ( + _doesntMatch(DISALLOWED_FLAGS, flag) && + (_doesntMatch(LOW_PROB_FLAGS, flag) || + random.choose(LOW_PROB_FLAGS_PROB))); + }); +} + +function filterDifferentialFuzzFlags(flags) { + return flags.filter( + flag => _doesntMatch(DISALLOWED_DIFFERENTIAL_FUZZ_FLAGS, flag)); +} + + +module.exports = { + filterDifferentialFuzzFlags: filterDifferentialFuzzFlags, + filterFlags: filterFlags, + getGeneratedSoftSkipped: getGeneratedSoftSkipped, + getGeneratedSloppy: getGeneratedSloppy, + getSoftSkipped: getSoftSkipped, + isTestSkippedAbs: isTestSkippedAbs, + isTestSkippedRel: isTestSkippedRel, + isTestSoftSkippedAbs: isTestSoftSkippedAbs, + isTestSoftSkippedRel: isTestSoftSkippedRel, + isTestSloppyRel: isTestSloppyRel, +} diff --git a/compiler/forget/packages/js-fuzzer/foozzie_launcher.py b/compiler/forget/packages/js-fuzzer/foozzie_launcher.py new file mode 100644 index 0000000000..88d2afd1c1 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/foozzie_launcher.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# Copyright 2020 the V8 project authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +""" +Launcher for the foozzie differential-fuzzing harness. Wraps foozzie +with Python2 for backwards-compatibility when bisecting. + +Obsolete now after switching to Python3 entirely. We keep the launcher +for a transition period. +""" + +import os +import re +import shutil +import subprocess +import sys + +def find_harness_code(args): + for arg in args: + if arg.endswith('v8_foozzie.py'): + with open(arg) as f: + return f.read() + assert False, 'Foozzie harness not found' + +if __name__ == '__main__': + # In some cases or older versions, the python executable is passed as + # first argument. Let's be robust either way, with or without full + # path or version. + if re.match(r'.*python.*', sys.argv[1]): + args = sys.argv[2:] + else: + args = sys.argv[1:] + + python_exe = 'python3' + + # To ease bisection of really old bugs, attempt to use Python2 as long + # as it is supported. This enables bisection before the point where the + # harness switched to Python3. + script = find_harness_code(args) + use_python3 = script.startswith('#!/usr/bin/env python3') + if not use_python3 and shutil.which('python2'): + python_exe = 'python2' + + process = subprocess.Popen([python_exe] + args) + process.communicate() + sys.exit(process.returncode) diff --git a/compiler/forget/packages/js-fuzzer/gen_exceptions.js b/compiler/forget/packages/js-fuzzer/gen_exceptions.js new file mode 100644 index 0000000000..19e40ed970 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/gen_exceptions.js @@ -0,0 +1,196 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Generate exceptions from full corpus test report. + */ + +const program = require('commander'); + +const assert = require('assert'); +const babelGenerator = require('@babel/generator').default; +const babelTemplate = require('@babel/template').default; +const babelTypes = require('@babel/types'); +const fs = require('fs'); +const p = require('path'); +const prettier = require("prettier"); + +const SPLIT_LINES_RE = /^.*([\n\r]+|$)/gm; +const PARSE_RE = /^Parsing (.*) sloppy took (\d+) ms\.\n$/; +const MUTATE_RE = /^Mutating (.*) took (\d+) ms\.\n$/; +const PARSE_FAILED_RE = /^WARNING: failed to sloppy parse (.*)\n$/; +const PARSE_STRICT_FAILED_RE = /^WARNING: failed to strict parse (.*)\n$/; +const MUTATE_FAILED_RE = /^ERROR: Exception during mutate: (.*)\n$/; + +// Add tests matching error regexp to result array. +function matchError(regexp, line, resultArray){ + const match = line.match(regexp); + if (!match) return false; + const relPath = match[1]; + assert(relPath); + resultArray.push(relPath); + return true; +} + +// Sum up total duration of tests matching the duration regexp and +// map test -> duration in result map. +function matchDuration(regexp, line, resultMap){ + const match = line.match(regexp); + if (!match) return false; + const relPath = match[1]; + assert(relPath); + resultMap[relPath] = (resultMap[relPath] || 0) + parseInt(match[2]); + return true; +} + +// Create lists of failed and slow tests from stdout of a fuzzer run. +function processFuzzOutput(outputFile){ + const text = fs.readFileSync(outputFile, 'utf-8'); + const lines = text.match(SPLIT_LINES_RE); + + const failedParse = []; + const failedParseStrict = []; + const failedMutate = []; + const durationsMap = {}; + + for (const line of lines) { + if (matchError(PARSE_FAILED_RE, line, failedParse)) + continue; + if (matchError(PARSE_STRICT_FAILED_RE, line, failedParseStrict)) + continue; + if (matchError(MUTATE_FAILED_RE, line, failedMutate)) + continue; + if (matchDuration(PARSE_RE, line, durationsMap)) + continue; + if (matchDuration(MUTATE_RE, line, durationsMap)) + continue; + } + + // Tuples (absPath, duration). + const total = Object.entries(durationsMap); + // Tuples (absPath, duration) with 2s < duration <= 10s. + const slow = total.filter(t => t[1] > 2000 && t[1] <= 10000); + // Tuples (absPath, duration) with 10s < duration. + const verySlow = total.filter(t => t[1] > 10000); + + // Assert there's nothing horribly wrong with the results. + // We have at least 2500 tests in the output. + assert(total.length > 2500); + // No more than 5% parse/mutation errors. + assert(failedParse.length + failedMutate.length < total.length / 20); + // No more than 10% slow tests + assert(slow.length < total.length / 10); + // No more than 2% very slow tests. + assert(verySlow.length < total.length / 50); + + // Sort everything. + failedParse.sort(); + failedParseStrict.sort(); + failedMutate.sort(); + + function slowestFirst(a, b) { + return b[1] - a[1]; + } + + slow.sort(slowestFirst); + verySlow.sort(slowestFirst); + + return [failedParse, failedParseStrict, failedMutate, slow, verySlow]; +} + +// List of string literals of failed tests. +function getLiteralsForFailed(leadingComment, failedList) { + const result = failedList.map(path => babelTypes.stringLiteral(path)); + if (result.length) { + babelTypes.addComment(result[0], 'leading', leadingComment); + } + return result; +} + +// List of string literals of slow tests with duration comments. +function getLiteralsForSlow(leadingComment, slowList) { + const result = slowList.map(([path, duration]) => { + const literal = babelTypes.stringLiteral(path); + babelTypes.addComment( + literal, 'trailing', ` ${duration / 1000}s`, true); + return literal; + }); + if (result.length) { + babelTypes.addComment(result[0], 'leading', leadingComment); + } + return result; +} + +function main() { + program + .version('0.0.1') + .parse(process.argv); + + if (!program.args.length) { + console.log('Need to specify stdout reports of fuzz runs.'); + return; + } + + let skipped = []; + let softSkipped = []; + let sloppy = []; + for (const outputFile of program.args) { + const [failedParse, failedParseStrict, failedMutate, slow, verySlow] = ( + processFuzzOutput(outputFile)); + const name = p.basename(outputFile, p.extname(outputFile)); + + // Skip tests that fail to parse/mutate or are very slow. + skipped = skipped.concat(getLiteralsForFailed( + ` Tests with parse errors from ${name} `, failedParse)); + skipped = skipped.concat(getLiteralsForFailed( + ` Tests with mutation errors from ${name} `, failedMutate)); + skipped = skipped.concat(getLiteralsForSlow( + ` Very slow tests from ${name} `, verySlow)); + + // Soft-skip slow but not very slow tests. + softSkipped = softSkipped.concat(getLiteralsForSlow( + ` Slow tests from ${name} `, slow)); + + // Mark sloppy tests. + sloppy = sloppy.concat(getLiteralsForFailed( + ` Tests requiring sloppy mode from ${name} `, failedParseStrict)); + } + + const fileTemplate = babelTemplate(` + /** + * @fileoverview Autogenerated exceptions. Created with gen_exceptions.js. + */ + + 'use strict'; + + const skipped = SKIPPED; + + const softSkipped = SOFTSKIPPED; + + const sloppy = SLOPPY; + + module.exports = { + generatedSkipped: new Set(skipped), + generatedSoftSkipped: new Set(softSkipped), + generatedSloppy: new Set(sloppy), + } + `, {preserveComments: true}); + + const skippedArray = babelTypes.arrayExpression(skipped); + const softSkippedArray = babelTypes.arrayExpression(softSkipped); + const sloppyArray = babelTypes.arrayExpression(sloppy); + + const statements = fileTemplate({ + SKIPPED: skippedArray, + SOFTSKIPPED: softSkippedArray, + SLOPPY: sloppyArray, + }); + + const resultProgram = babelTypes.program(statements); + const code = babelGenerator(resultProgram, { comments: true }).code; + const prettyCode = prettier.format(code, { parser: "babel" }); + fs.writeFileSync('generated/exceptions.js', prettyCode); +} + +main(); diff --git a/compiler/forget/packages/js-fuzzer/gen_exceptions.sh b/compiler/forget/packages/js-fuzzer/gen_exceptions.sh new file mode 100755 index 0000000000..b2a90b8bd2 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/gen_exceptions.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Copyright 2020 the V8 project authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +APP_NAME=d8 node run.js -i $WEB_TESTS -o $OUTPUT -z -v -e -c chakra > chakra.log +APP_NAME=d8 node run.js -i $WEB_TESTS -o $OUTPUT -z -v -e -c v8 > v8.log +APP_NAME=d8 node run.js -i $WEB_TESTS -o $OUTPUT -z -v -e -c spidermonkey > spidermonkey.log +APP_NAME=d8 node run.js -i $WEB_TESTS -o $OUTPUT -z -v -e -c WebKit/JSTests > jstests.log +APP_NAME=d8 node run.js -i $WEB_TESTS -o $OUTPUT -z -v -e -c CrashTests > crashtests.log + +node gen_exceptions.js v8.log spidermonkey.log chakra.log jstests.log crashtests.log diff --git a/compiler/forget/packages/js-fuzzer/mutators/array_mutator.js b/compiler/forget/packages/js-fuzzer/mutators/array_mutator.js new file mode 100644 index 0000000000..bcfb601436 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/array_mutator.js @@ -0,0 +1,115 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Mutator for array expressions. + */ + +'use strict'; + +const babelTypes = require('@babel/types'); + +const common = require('./common.js'); +const mutator = require('./mutator.js'); +const random = require('../random.js'); + +// Blueprint for choosing the maximum number of mutations. Bias towards +// performing only one mutation. +const MUTATION_CHOICES = [1, 1, 1, 1, 1, 2, 2, 2, 3]; + +const MAX_ARRAY_LENGTH = 50; + +class ArrayMutator extends mutator.Mutator { + constructor(settings) { + super(); + this.settings = settings; + } + + get visitor() { + const thisMutator = this; + + return { + ArrayExpression(path) { + const elements = path.node.elements; + if (!random.choose(thisMutator.settings.MUTATE_ARRAYS) || + elements.length > MAX_ARRAY_LENGTH) { + return; + } + + // Annotate array expression with the action taken, indicating + // if we also replaced elements. + function annotate(message, replace) { + if (replace) message += ' (replaced)'; + thisMutator.annotate(path.node, message); + } + + // Add or replace elements at a random index. + function randomSplice(replace, ...args) { + // Choose an index that's small enough to replace all desired items. + const index = random.randInt(0, elements.length - replace); + elements.splice(index, replace, ...args); + } + + function duplicateElement(replace) { + const element = random.single(elements); + if (!element || common.isLargeNode(element)) { + return; + } + annotate('Duplicate an element', replace); + randomSplice(replace, babelTypes.cloneDeep(element)); + } + + function insertRandomValue(replace) { + annotate('Insert a random value', replace); + randomSplice(replace, common.randomValue(path)); + } + + function insertHole(replace) { + annotate('Insert a hole', replace); + randomSplice(replace, null); + } + + function removeElements(count) { + annotate('Remove elements'); + randomSplice(random.randInt(1, count)); + } + + function shuffle() { + annotate('Shuffle array'); + random.shuffle(elements); + } + + // Mutation options. Repeated mutations have a higher probability. + const mutations = [ + () => duplicateElement(1), + () => duplicateElement(1), + () => duplicateElement(1), + () => duplicateElement(0), + () => duplicateElement(0), + () => insertRandomValue(1), + () => insertRandomValue(1), + () => insertRandomValue(0), + () => insertHole(1), + () => insertHole(0), + () => removeElements(1), + () => removeElements(elements.length), + shuffle, + ]; + + // Perform several mutations. + const count = random.single(MUTATION_CHOICES); + for (let i = 0; i < count; i++) { + random.single(mutations)(); + } + + // Don't recurse on nested arrays. + path.skip(); + }, + } + } +} + +module.exports = { + ArrayMutator: ArrayMutator, +}; diff --git a/compiler/forget/packages/js-fuzzer/mutators/common.js b/compiler/forget/packages/js-fuzzer/mutators/common.js new file mode 100644 index 0000000000..64a164583e --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/common.js @@ -0,0 +1,376 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Common mutator utilities. + */ + +const babelTemplate = require('@babel/template').default; +const babelTypes = require('@babel/types'); +const babylon = require('@babel/parser'); + +const sourceHelpers = require('../source_helpers.js'); +const random = require('../random.js'); + +const INTERESTING_NUMBER_VALUES = [ + -1, -0.0, 0, 1, + + // Float values. + -0.000000000000001, 0.000000000000001, + + // Special values. + NaN, +Infinity, -Infinity, + + // Boundaries of int, signed, unsigned, SMI (near +/- 2^(30, 31, 32). + 0x03fffffff, 0x040000000, 0x040000001, + -0x03fffffff, -0x040000000, -0x040000001, + 0x07fffffff, 0x080000000, 0x080000001, + -0x07fffffff, -0x080000000, -0x080000001, + 0x0ffffffff, 0x100000000, 0x100000001, + -0x0ffffffff, -0x100000000, -0x100000001, + + // Boundaries of maximum safe integer (near +/- 2^53). + 9007199254740990, 9007199254740991, 9007199254740992, + -9007199254740990, -9007199254740991, -9007199254740992, + + // Boundaries of double. + 5e-324, 1.7976931348623157e+308, + -5e-324,-1.7976931348623157e+308, +] + +const INTERESTING_NON_NUMBER_VALUES = [ + // Simple arrays. + '[]', + 'Array(0x8000).fill("a")', + + // Simple object. + '{}', + '{a: "foo", b: 10, c: {}}', + + // Simple strings. + '"foo"', + '""', + + // Simple regex. + '/0/', + '"/0/"', + + // Simple symbol. + 'Symbol("foo")', + + // Long string. + 'Array(0x8000).join("a")', + + // Math.PI + 'Math.PI', + + // Others. + 'false', + 'true', + 'undefined', + 'null', + 'this', + 'this[0]', + 'this[1]', + + // Empty function. + '(function() {return 0;})', + + // Objects with functions. + '({toString:function(){return "0";}})', + '({valueOf:function(){return 0;}})', + '({valueOf:function(){return "0";}})', + + // Objects for primitive types created using new. + '(new Boolean(false))', + '(new Boolean(true))', + '(new String(""))', + '(new Number(0))', + '(new Number(-0))', +] + +const LARGE_NODE_SIZE = 100; +const MAX_ARGUMENT_COUNT = 10; + +function _identifier(identifier) { + return babelTypes.identifier(identifier); +} + +function _numericLiteral(number) { + return babelTypes.numericLiteral(number); +} + +function _unwrapExpressionStatement(value) { + if (babelTypes.isExpressionStatement(value)) { + return value.expression; + } + + return value; +} + +function isVariableIdentifier(name) { + return /__v_[0-9]+/.test(name); +} + +function isFunctionIdentifier(name) { + return /__f_[0-9]+/.test(name); +} + +function isInForLoopCondition(path) { + // Return whether if we're in the init/test/update parts of a for loop (but + // not the body). Mutating variables in the init/test/update will likely + // modify loop variables and cause infinite loops. + const forStatementChild = path.find( + p => p.parent && babelTypes.isForStatement(p.parent)); + + return (forStatementChild && forStatementChild.parentKey !== 'body'); +} + +function isInWhileLoop(path) { + // Return whether if we're in a while loop. + const whileStatement = path.find(p => babelTypes.isWhileStatement(p)); + return Boolean(whileStatement); +} + +function _availableIdentifiers(path, filter) { + // TODO(ochang): Consider globals that aren't declared with let/var etc. + const available = new Array(); + const allBindings = path.scope.getAllBindings(); + for (const key of Object.keys(allBindings)) { + if (!filter(key)) { + continue; + } + + if (filter === isVariableIdentifier && + path.willIMaybeExecuteBefore(allBindings[key].path)) { + continue; + } + + available.push(_identifier(key)); + } + + return available; +} + +function availableVariables(path) { + return _availableIdentifiers(path, isVariableIdentifier); +} + +function availableFunctions(path) { + return _availableIdentifiers(path, isFunctionIdentifier); +} + +function randomVariable(path) { + return random.single(availableVariables(path)); +} + +function randomFunction(path) { + return random.single(availableFunctions(path)); +} + +function randomSeed() { + return random.randInt(0, 2**20); +} + +function randomObject(seed) { + if (seed === undefined) { + seed = randomSeed(); + } + + const template = babelTemplate('__getRandomObject(SEED)'); + return template({ + SEED: _numericLiteral(seed), + }).expression; +} + +function randomProperty(identifier, seed) { + if (seed === undefined) { + seed = randomSeed(); + } + + const template = babelTemplate('__getRandomProperty(IDENTIFIER, SEED)'); + return template({ + IDENTIFIER: identifier, + SEED: _numericLiteral(seed), + }).expression; +} + +function randomArguments(path) { + const numArgs = random.randInt(0, MAX_ARGUMENT_COUNT); + const args = []; + + for (let i = 0; i < numArgs; i++) { + args.push(randomValue(path)); + } + + return args.map(_unwrapExpressionStatement); +} + +function randomValue(path) { + const probability = random.random(); + + if (probability < 0.01) { + const randomFunc = randomFunction(path); + if (randomFunc) { + return randomFunc; + } + } + + if (probability < 0.25) { + const randomVar = randomVariable(path); + if (randomVar) { + return randomVar; + } + } + + if (probability < 0.5) { + return randomInterestingNumber(); + } + + if (probability < 0.75) { + return randomInterestingNonNumber(); + } + + return randomObject(); +} + +function callRandomFunction(path, identifier, seed) { + if (seed === undefined) { + seed = randomSeed(); + } + + let args = [ + identifier, + _numericLiteral(seed) + ]; + + args = args.map(_unwrapExpressionStatement); + args = args.concat(randomArguments(path)); + + return babelTypes.callExpression( + babelTypes.identifier('__callRandomFunction'), + args); +} + +function nearbyRandomNumber(value) { + const probability = random.random(); + + if (probability < 0.9) { + return _numericLiteral(value + random.randInt(-0x10, 0x10)); + } else if (probability < 0.95) { + return _numericLiteral(value + random.randInt(-0x100, 0x100)); + } else if (probability < 0.99) { + return _numericLiteral(value + random.randInt(-0x1000, 0x1000)); + } + + return _numericLiteral(value + random.randInt(-0x10000, 0x10000)); +} + +function randomInterestingNumber() { + const value = random.single(INTERESTING_NUMBER_VALUES); + if (random.choose(0.05)) { + return nearbyRandomNumber(value); + } + return _numericLiteral(value); +} + +function randomInterestingNonNumber() { + return babylon.parseExpression(random.single(INTERESTING_NON_NUMBER_VALUES)); +} + +function concatFlags(inputs) { + const flags = new Set(); + for (const input of inputs) { + for (const flag of input.flags || []) { + flags.add(flag); + } + } + return Array.from(flags.values()); +} + +function concatPrograms(inputs) { + // Concatentate programs. + const resultProgram = babelTypes.program([]); + const result = babelTypes.file(resultProgram, [], null); + + for (const input of inputs) { + const ast = input.ast.program; + resultProgram.body = resultProgram.body.concat(ast.body); + resultProgram.directives = resultProgram.directives.concat(ast.directives); + } + + // TODO(machenbach): Concat dependencies here as soon as they are cached. + const combined = new sourceHelpers.ParsedSource( + result, '', '', concatFlags(inputs)); + // If any input file is sloppy, the combined result is sloppy. + combined.sloppy = inputs.some(input => input.isSloppy()); + return combined; +} + +function setSourceLoc(source, index, total) { + const noop = babelTypes.noop(); + noop.__loc = index / total; + noop.__self = noop; + source.ast.program.body.unshift(noop); +} + +function getSourceLoc(node) { + // Source location is invalid in cloned nodes. + if (node !== node.__self) { + return undefined; + } + return node.__loc; +} + +function setOriginalPath(source, originalPath) { + const noop = babelTypes.noop(); + noop.__path = originalPath; + noop.__self = noop; + source.ast.program.body.unshift(noop); +} + +function getOriginalPath(node) { + // Original path is invalid in cloned nodes. + if (node !== node.__self) { + return undefined; + } + return node.__path; +} + +// Estimate the size of a node in raw source characters. +function isLargeNode(node) { + // Ignore array holes inserted by us (null) or previously cloned nodes + // (they have no start/end). + if (!node || node.start === undefined || node.end === undefined ) { + return false; + } + return node.end - node.start > LARGE_NODE_SIZE; +} + +module.exports = { + callRandomFunction: callRandomFunction, + concatFlags: concatFlags, + concatPrograms: concatPrograms, + availableVariables: availableVariables, + availableFunctions: availableFunctions, + randomFunction: randomFunction, + randomVariable: randomVariable, + isInForLoopCondition: isInForLoopCondition, + isInWhileLoop: isInWhileLoop, + isLargeNode: isLargeNode, + isVariableIdentifier: isVariableIdentifier, + isFunctionIdentifier: isFunctionIdentifier, + nearbyRandomNumber: nearbyRandomNumber, + randomArguments: randomArguments, + randomInterestingNonNumber: randomInterestingNonNumber, + randomInterestingNumber: randomInterestingNumber, + randomObject: randomObject, + randomProperty: randomProperty, + randomSeed: randomSeed, + randomValue: randomValue, + getOriginalPath: getOriginalPath, + setOriginalPath: setOriginalPath, + getSourceLoc: getSourceLoc, + setSourceLoc: setSourceLoc, +} diff --git a/compiler/forget/packages/js-fuzzer/mutators/crossover_mutator.js b/compiler/forget/packages/js-fuzzer/mutators/crossover_mutator.js new file mode 100644 index 0000000000..491501dc5c --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/crossover_mutator.js @@ -0,0 +1,85 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Expression mutator. + */ + +'use strict'; + +const babelTemplate = require('@babel/template').default; + +const common = require('./common.js'); +const random = require('../random.js'); +const mutator = require('./mutator.js'); +const sourceHelpers = require('../source_helpers.js'); + +class CrossOverMutator extends mutator.Mutator { + constructor(settings, db) { + super(); + this.settings = settings; + this.db = db; + } + + get visitor() { + const thisMutator = this; + + return [{ + ExpressionStatement(path) { + if (!random.choose(thisMutator.settings.MUTATE_CROSSOVER_INSERT)) { + return; + } + + const canHaveSuper = Boolean(path.findParent(x => x.isClassMethod())); + const randomExpression = thisMutator.db.getRandomStatement( + {canHaveSuper: canHaveSuper}); + + // Insert the statement. + let toInsert = babelTemplate( + randomExpression.source, + sourceHelpers.BABYLON_REPLACE_VAR_OPTIONS); + const dependencies = {}; + + if (randomExpression.dependencies) { + const variables = common.availableVariables(path); + if (!variables.length) { + return; + } + for (const dependency of randomExpression.dependencies) { + dependencies[dependency] = random.single(variables); + } + } + + try { + toInsert = toInsert(dependencies); + } catch (e) { + if (thisMutator.settings.testing) { + // Fail early in tests. + throw e; + } + console.log('ERROR: Failed to parse:', randomExpression.source); + console.log(e); + return; + } + + thisMutator.annotate( + toInsert, + 'Crossover from ' + randomExpression.originalPath); + + if (random.choose(0.5)) { + thisMutator.insertBeforeSkip(path, toInsert); + } else { + thisMutator.insertAfterSkip(path, toInsert); + } + + path.skip(); + }, + }, { + }]; + } +} + +module.exports = { + CrossOverMutator: CrossOverMutator, +}; diff --git a/compiler/forget/packages/js-fuzzer/mutators/differential_fuzz_mutator.js b/compiler/forget/packages/js-fuzzer/mutators/differential_fuzz_mutator.js new file mode 100644 index 0000000000..92c8df689c --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/differential_fuzz_mutator.js @@ -0,0 +1,225 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Mutator for differential fuzzing. + */ + +'use strict'; + +const babelTemplate = require('@babel/template').default; +const babelTypes = require('@babel/types'); + +const common = require('./common.js'); +const mutator = require('./mutator.js'); +const random = require('../random.js'); + +// Templates for various statements. +const incCaught = babelTemplate('__caught++;'); +const printValue = babelTemplate('print(VALUE);'); +const printCaught = babelTemplate('print("Caught: " + __caught);'); +const printHash = babelTemplate('print("Hash: " + __hash);'); +const prettyPrint = babelTemplate('__prettyPrint(ID);'); +const prettyPrintExtra = babelTemplate('__prettyPrintExtra(ID);'); + +// This section prefix is expected by v8_foozzie.py. Existing prefixes +// (e.g. from CrashTests) are cleaned up with CLEANED_PREFIX. +const SECTION_PREFIX = 'v8-foozzie source: '; +const CLEANED_PREFIX = 'v***************e: '; + +/** + * Babel statement for calling deep printing from the fuzz library. + */ +function prettyPrintStatement(variable) { + return prettyPrint({ ID: babelTypes.cloneDeep(variable) }); +} + +/** + * As above, but using the "extra" variant, which will reduce printing + * after too many calls to prevent I/O flooding. + */ +function prettyPrintExtraStatement(variable) { + return prettyPrintExtra({ ID: babelTypes.cloneDeep(variable) }); +} + +/** + * Mutator for suppressing known and/or unfixable issues. + */ +class DifferentialFuzzSuppressions extends mutator.Mutator { + get visitor() { + let thisMutator = this; + + return { + // Clean up strings containing the magic section prefix. Those can come + // e.g. from CrashTests and would confuse the deduplication in + // v8_foozzie.py. + StringLiteral(path) { + if (path.node.value.startsWith(SECTION_PREFIX)) { + const postfix = path.node.value.substring(SECTION_PREFIX.length); + path.node.value = CLEANED_PREFIX + postfix; + thisMutator.annotate(path.node, 'Replaced magic string'); + } + }, + // Known precision differences: https://crbug.com/1063568 + BinaryExpression(path) { + if (path.node.operator == '**') { + path.node.operator = '+'; + thisMutator.annotate(path.node, 'Replaced **'); + } + }, + // Unsupported language feature: https://crbug.com/1020573 + MemberExpression(path) { + if (path.node.property.name == "arguments") { + let replacement = common.randomVariable(path); + if (!replacement) { + replacement = babelTypes.thisExpression(); + } + thisMutator.annotate(replacement, 'Replaced .arguments'); + thisMutator.replaceWithSkip(path, replacement); + } + }, + }; + } +} + +/** + * Mutator for tracking original input files and for extra printing. + */ +class DifferentialFuzzMutator extends mutator.Mutator { + constructor(settings) { + super(); + this.settings = settings; + } + + /** + * Looks for the dummy node that marks the beginning of an input file + * from the corpus. + */ + isSectionStart(path) { + return !!common.getOriginalPath(path.node); + } + + /** + * Create print statements for printing the magic section prefix that's + * expected by v8_foozzie.py to differentiate different source files. + */ + getSectionHeader(path) { + const orig = common.getOriginalPath(path.node); + return printValue({ + VALUE: babelTypes.stringLiteral(SECTION_PREFIX + orig), + }); + } + + /** + * Create statements for extra printing at the end of a section. We print + * the number of caught exceptions, a generic hash of all observed values + * and the contents of all variables in scope. + */ + getSectionFooter(path) { + const variables = common.availableVariables(path); + const statements = variables.map(prettyPrintStatement); + statements.unshift(printCaught()); + statements.unshift(printHash()); + const statement = babelTypes.tryStatement( + babelTypes.blockStatement(statements), + babelTypes.catchClause( + babelTypes.identifier('e'), + babelTypes.blockStatement([]))); + this.annotate(statement, 'Print variables and exceptions from section'); + return statement; + } + + /** + * Helper for printing the contents of several variables. + */ + printVariables(path, nodes) { + const statements = []; + for (const node of nodes) { + if (!babelTypes.isIdentifier(node) || + !common.isVariableIdentifier(node.name)) + continue; + statements.push(prettyPrintExtraStatement(node)); + } + if (statements.length) { + this.annotate(statements[0], 'Extra variable printing'); + this.insertAfterSkip(path, statements); + } + } + + get visitor() { + const thisMutator = this; + const settings = this.settings; + + return { + // Replace existing normal print statements with deep printing. + CallExpression(path) { + if (babelTypes.isIdentifier(path.node.callee) && + path.node.callee.name == 'print') { + path.node.callee = babelTypes.identifier('__prettyPrintExtra'); + thisMutator.annotate(path.node, 'Pretty printing'); + } + }, + // Either print or track caught exceptions, guarded by a probability. + CatchClause(path) { + const probability = random.random(); + if (probability < settings.DIFF_FUZZ_EXTRA_PRINT && + path.node.param && + babelTypes.isIdentifier(path.node.param)) { + const statement = prettyPrintExtraStatement(path.node.param); + path.node.body.body.unshift(statement); + } else if (probability < settings.DIFF_FUZZ_TRACK_CAUGHT) { + path.node.body.body.unshift(incCaught()); + } + }, + // Insert section headers and footers between the contents of two + // original source files. We detect the dummy no-op nodes that were + // previously tagged with the original path of the file. + Noop(path) { + if (!thisMutator.isSectionStart(path)) { + return; + } + const header = thisMutator.getSectionHeader(path); + const footer = thisMutator.getSectionFooter(path); + thisMutator.insertBeforeSkip(path, footer); + thisMutator.insertBeforeSkip(path, header); + }, + // Additionally we print one footer in the end. + Program: { + exit(path) { + const footer = thisMutator.getSectionFooter(path); + path.node.body.push(footer); + }, + }, + // Print contents of variables after assignments, guarded by a + // probability. + ExpressionStatement(path) { + if (!babelTypes.isAssignmentExpression(path.node.expression) || + !random.choose(settings.DIFF_FUZZ_EXTRA_PRINT)) { + return; + } + const left = path.node.expression.left; + if (babelTypes.isMemberExpression(left)) { + thisMutator.printVariables(path, [left.object]); + } else { + thisMutator.printVariables(path, [left]); + } + }, + // Print contents of variables after declaration, guarded by a + // probability. + VariableDeclaration(path) { + if (babelTypes.isLoop(path.parent) || + !random.choose(settings.DIFF_FUZZ_EXTRA_PRINT)) { + return; + } + const identifiers = path.node.declarations.map(decl => decl.id); + thisMutator.printVariables(path, identifiers); + }, + }; + } +} + +module.exports = { + DifferentialFuzzMutator: DifferentialFuzzMutator, + DifferentialFuzzSuppressions: DifferentialFuzzSuppressions, +}; diff --git a/compiler/forget/packages/js-fuzzer/mutators/expression_mutator.js b/compiler/forget/packages/js-fuzzer/mutators/expression_mutator.js new file mode 100644 index 0000000000..516d65ae3b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/expression_mutator.js @@ -0,0 +1,63 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Expression mutator. + */ + +'use strict'; + +const babelTypes = require('@babel/types'); + +const random = require('../random.js'); +const mutator = require('./mutator.js'); + +class ExpressionMutator extends mutator.Mutator { + constructor(settings) { + super(); + this.settings = settings; + } + + get visitor() { + const thisMutator = this; + + return { + ExpressionStatement(path) { + if (!random.choose(thisMutator.settings.MUTATE_EXPRESSIONS)) { + return; + } + + const probability = random.random(); + + if (probability < 0.7) { + const repeated = babelTypes.cloneDeep(path.node); + thisMutator.annotate(repeated, 'Repeated'); + thisMutator.insertBeforeSkip(path, repeated); + } else if (path.key > 0) { + // Get a random previous sibling. + const prev = path.getSibling(random.randInt(0, path.key - 1)); + if (!prev || !prev.node) { + return; + } + // Either select a previous or the current node to clone. + const [selected, destination] = random.shuffle([prev, path]); + if (selected.isDeclaration()) { + return; + } + const cloned = babelTypes.cloneDeep(selected.node); + thisMutator.annotate(cloned, 'Cloned sibling'); + if (random.choose(0.5)) { + thisMutator.insertBeforeSkip(destination, cloned); + } else { + thisMutator.insertAfterSkip(destination, cloned); + } + } + }, + }; + } +} + +module.exports = { + ExpressionMutator: ExpressionMutator, +}; diff --git a/compiler/forget/packages/js-fuzzer/mutators/function_call_mutator.js b/compiler/forget/packages/js-fuzzer/mutators/function_call_mutator.js new file mode 100644 index 0000000000..665550f92b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/function_call_mutator.js @@ -0,0 +1,149 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Function calls mutator. + */ + +'use strict'; + +const babelTemplate = require('@babel/template').default; +const babelTypes = require('@babel/types'); + +const common = require('./common.js'); +const random = require('../random.js'); +const mutator = require('./mutator.js'); + +function _liftExpressionsToStatements(path, nodes) { + // If the node we're replacing is an expression in an expression statement, + // lift the replacement nodes into statements too. + if (!babelTypes.isExpressionStatement(path.parent)) { + return nodes; + } + + return nodes.map(n => babelTypes.expressionStatement(n)); +} + +class FunctionCallMutator extends mutator.Mutator { + constructor(settings) { + super(); + this.settings = settings; + } + + get visitor() { + const thisMutator = this; + + return { + CallExpression(path) { + if (!babelTypes.isIdentifier(path.node.callee)) { + return; + } + + if (!common.isFunctionIdentifier(path.node.callee.name)) { + return; + } + + if (!random.choose(thisMutator.settings.MUTATE_FUNCTION_CALLS)) { + return; + } + + const probability = random.random(); + if (probability < 0.3) { + const randFunc = common.randomFunction(path); + if (randFunc) { + thisMutator.annotate( + path.node, + `Replaced ${path.node.callee.name} with ${randFunc.name}`); + + path.node.callee = randFunc; + } + } else if (probability < 0.7 && thisMutator.settings.engine == 'V8') { + const prepareTemplate = babelTemplate( + '__V8BuiltinPrepareFunctionForOptimization(ID)'); + const optimizationMode = random.choose(0.7) ? 'Function' : 'Maglev'; + const optimizeTemplate = babelTemplate( + `__V8BuiltinOptimize${optimizationMode}OnNextCall(ID)`); + + const nodes = [ + prepareTemplate({ + ID: babelTypes.cloneDeep(path.node.callee), + }).expression, + babelTypes.cloneDeep(path.node), + babelTypes.cloneDeep(path.node), + optimizeTemplate({ + ID: babelTypes.cloneDeep(path.node.callee), + }).expression, + ]; + + thisMutator.annotate( + path.node, + `Optimizing ${path.node.callee.name}`); + if (!babelTypes.isExpressionStatement(path.parent)) { + nodes.push(path.node); + thisMutator.replaceWithSkip( + path, babelTypes.sequenceExpression(nodes)); + } else { + thisMutator.insertBeforeSkip( + path, _liftExpressionsToStatements(path, nodes)); + } + } else if (probability < 0.8 && thisMutator.settings.engine == 'V8') { + const template = babelTemplate( + '__V8BuiltinCompileBaseline(ID)'); + + const nodes = [ + template({ + ID: babelTypes.cloneDeep(path.node.callee), + }).expression, + ]; + + thisMutator.annotate( + nodes[0], + `Compiling baseline ${path.node.callee.name}`); + + if (!babelTypes.isExpressionStatement(path.parent)) { + nodes.push(path.node); + thisMutator.replaceWithSkip( + path, babelTypes.sequenceExpression(nodes)); + } else { + thisMutator.insertBeforeSkip( + path, _liftExpressionsToStatements(path, nodes)); + } + } else if (probability < 0.9 && + thisMutator.settings.engine == 'V8') { + const template = babelTemplate( + '__V8BuiltinDeoptimizeFunction(ID)'); + const insert = _liftExpressionsToStatements(path, [ + template({ + ID: babelTypes.cloneDeep(path.node.callee), + }).expression, + ]); + + thisMutator.annotate( + path.node, + `Deoptimizing ${path.node.callee.name}`); + + thisMutator.insertAfterSkip(path, insert); + } else { + const template = babelTemplate( + 'runNearStackLimit(() => { return CALL });'); + thisMutator.annotate( + path.node, + `Run to stack limit ${path.node.callee.name}`); + + thisMutator.replaceWithSkip( + path, + template({ + CALL: path.node, + }).expression); + } + + path.skip(); + }, + } + } +} + +module.exports = { + FunctionCallMutator: FunctionCallMutator, +}; diff --git a/compiler/forget/packages/js-fuzzer/mutators/mutator.js b/compiler/forget/packages/js-fuzzer/mutators/mutator.js new file mode 100644 index 0000000000..9f27d16906 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/mutator.js @@ -0,0 +1,98 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Mutator + */ +'use strict'; + +const babelTraverse = require('@babel/traverse').default; +const babelTypes = require('@babel/types'); + +class Mutator { + get visitor() { + return null; + } + + _traverse(ast, visitor) { + let oldEnter = null; + if (Object.prototype.hasOwnProperty.call(visitor, 'enter')) { + oldEnter = visitor['enter']; + } + + // Transparently skip nodes that are marked. + visitor['enter'] = (path) => { + if (this.shouldSkip(path.node)) { + path.skip(); + return; + } + + if (oldEnter) { + oldEnter(path); + } + } + + babelTraverse(ast, visitor); + } + + mutate(source) { + if (Array.isArray(this.visitor)) { + for (const visitor of this.visitor) { + this._traverse(source.ast, visitor); + } + } else { + this._traverse(source.ast, this.visitor); + } + } + + get _skipPropertyName() { + return '__skip' + this.constructor.name; + } + + shouldSkip(node) { + return Boolean(node[this._skipPropertyName]); + } + + skipMutations(node) { + // Mark a node to skip further mutations of the same kind. + if (Array.isArray(node)) { + for (const item of node) { + item[this._skipPropertyName] = true; + } + } else { + node[this._skipPropertyName] = true; + } + + return node; + } + + insertBeforeSkip(path, node) { + this.skipMutations(node); + path.insertBefore(node); + } + + insertAfterSkip(path, node) { + this.skipMutations(node); + path.insertAfter(node); + } + + replaceWithSkip(path, node) { + this.skipMutations(node); + path.replaceWith(node); + } + + replaceWithMultipleSkip(path, node) { + this.skipMutations(node); + path.replaceWithMultiple(node); + } + + annotate(node, message) { + babelTypes.addComment( + node, 'leading', ` ${this.constructor.name}: ${message} `); + } +} + +module.exports = { + Mutator: Mutator, +} diff --git a/compiler/forget/packages/js-fuzzer/mutators/normalizer.js b/compiler/forget/packages/js-fuzzer/mutators/normalizer.js new file mode 100644 index 0000000000..7b5a35a7b3 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/normalizer.js @@ -0,0 +1,89 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Normalizer. + * This renames variables so that we don't have collisions when combining + * different files. It also simplifies other logic when e.g. determining the + * type of an identifier. + */ +'use strict'; + +const babelTypes = require('@babel/types'); + +const mutator = require('./mutator.js'); + +class NormalizerContext { + constructor() { + this.funcIndex = 0; + this.varIndex = 0; + this.classIndex = 0; + } +} + +class IdentifierNormalizer extends mutator.Mutator { + constructor() { + super(); + this.context = new NormalizerContext(); + } + + get visitor() { + const context = this.context; + const renamed = new WeakSet(); + const globalMappings = new Map(); + + return [{ + Scope(path) { + for (const [name, binding] of Object.entries(path.scope.bindings)) { + if (renamed.has(binding.identifier)) { + continue; + } + + renamed.add(binding.identifier); + + if (babelTypes.isClassDeclaration(binding.path.node) || + babelTypes.isClassExpression(binding.path.node)) { + path.scope.rename(name, '__c_' + context.classIndex++); + } else if (babelTypes.isFunctionDeclaration(binding.path.node) || + babelTypes.isFunctionExpression(binding.path.node)) { + path.scope.rename(name, '__f_' + context.funcIndex++); + } else { + path.scope.rename(name, '__v_' + context.varIndex++); + } + } + }, + + AssignmentExpression(path) { + // Find assignments for which we have no binding in the scope. We assume + // that these are globals which are local to our script (which weren't + // declared with var/let/const etc). + const ids = path.getBindingIdentifiers(); + for (const name in ids) { + if (!path.scope.getBinding(name)) { + globalMappings.set(name, '__v_' + context.varIndex++); + } + } + } + }, { + // Second pass to rename globals that weren't declared with + // var/let/const etc. + Identifier(path) { + if (!globalMappings.has(path.node.name)) { + return; + } + + if (path.scope.getBinding(path.node.name)) { + // Don't rename if there is a binding that hides the global. + return; + } + + path.node.name = globalMappings.get(path.node.name); + } + }]; + } +} + +module.exports = { + IdentifierNormalizer: IdentifierNormalizer, +}; diff --git a/compiler/forget/packages/js-fuzzer/mutators/number_mutator.js b/compiler/forget/packages/js-fuzzer/mutators/number_mutator.js new file mode 100644 index 0000000000..c0f3ea8d93 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/number_mutator.js @@ -0,0 +1,105 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Numbers mutator. + */ + +'use strict'; + +const babelTypes = require('@babel/types'); + +const common = require('./common.js'); +const random = require('../random.js'); +const mutator = require('./mutator.js'); + +const MIN_SAFE_INTEGER = -9007199254740991; +const MAX_SAFE_INTEGER = 9007199254740991; + + +function isObjectKey(path) { + return (path.parent && + babelTypes.isObjectMember(path.parent) && + path.parent.key === path.node); +} + +function createRandomNumber(value) { + // TODO(ochang): Maybe replace with variable. + const probability = random.random(); + if (probability < 0.01) { + return babelTypes.numericLiteral( + random.randInt(MIN_SAFE_INTEGER, MAX_SAFE_INTEGER)); + } else if (probability < 0.06) { + return common.randomInterestingNumber(); + } else { + return common.nearbyRandomNumber(value); + } +} + +class NumberMutator extends mutator.Mutator { + constructor(settings) { + super(); + this.settings = settings; + } + + ignore(path) { + return !random.choose(this.settings.MUTATE_NUMBERS) || + common.isInForLoopCondition(path) || + common.isInWhileLoop(path); + } + + randomReplace(path, value, forcePositive=false) { + const randomNumber = createRandomNumber(value); + + if (forcePositive) { + randomNumber.value = Math.abs(randomNumber.value); + } + + this.annotate( + path.node, + `Replaced ${value} with ${randomNumber.value}`); + + this.replaceWithSkip(path, randomNumber); + } + + get visitor() { + const thisMutator = this; + + return { + NumericLiteral(path) { + if (thisMutator.ignore(path)) { + return; + } + + // We handle negative unary expressions separately to replace the whole + // expression below. E.g. -5 is UnaryExpression(-, NumericLiteral(5)). + if (path.parent && babelTypes.isUnaryExpression(path.parent) && + path.parent.operator === '-') { + return; + } + + // Enfore positive numbers if the literal is the key of an object + // property or method. Negative keys cause syntax errors. + const forcePositive = isObjectKey(path); + + thisMutator.randomReplace(path, path.node.value, forcePositive); + }, + UnaryExpression(path) { + if (thisMutator.ignore(path)) { + return; + } + + // Handle the case we ignore above. + if (path.node.operator === '-' && + babelTypes.isNumericLiteral(path.node.argument)) { + thisMutator.randomReplace(path, -path.node.argument.value); + } + } + }; + } +} + +module.exports = { + NumberMutator: NumberMutator, +}; diff --git a/compiler/forget/packages/js-fuzzer/mutators/object_mutator.js b/compiler/forget/packages/js-fuzzer/mutators/object_mutator.js new file mode 100644 index 0000000000..7906b4f1a0 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/object_mutator.js @@ -0,0 +1,135 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Mutator for object expressions. + */ + +'use strict'; + +const babelTypes = require('@babel/types'); + +const common = require('./common.js'); +const mutator = require('./mutator.js'); +const random = require('../random.js'); + +const MAX_PROPERTIES = 50; + +/** + * Turn the key of an object property into a string literal. + */ +function keyToString(key) { + if (babelTypes.isNumericLiteral(key)) { + return babelTypes.stringLiteral(key.value.toString()); + } + if (babelTypes.isIdentifier(key)) { + return babelTypes.stringLiteral(key.name); + } + // Already a string literal. + return key; +} + +class ObjectMutator extends mutator.Mutator { + constructor(settings) { + super(); + this.settings = settings; + } + + get visitor() { + const thisMutator = this; + + return { + ObjectExpression(path) { + const properties = path.node.properties; + if (!random.choose(thisMutator.settings.MUTATE_OBJECTS) || + properties.length > MAX_PROPERTIES) { + return; + } + + // Use the indices of object properties for mutations. We ignore + // getters and setters. + const propertyIndicies = []; + for (const [index, property] of properties.entries()) { + if (babelTypes.isObjectProperty(property)) { + propertyIndicies.push(index); + } + } + + // The mutations below require at least one property. + if (!propertyIndicies.length) { + return; + } + + // Annotate object expression with the action taken. + function annotate(message) { + thisMutator.annotate(path.node, message); + } + + function getOneRandomProperty() { + return properties[random.single(propertyIndicies)]; + } + + function getTwoRandomProperties() { + const [a, b] = random.sample(propertyIndicies, 2); + return [properties[a], properties[b]]; + } + + function swapPropertyValues() { + if (propertyIndicies.length > 1) { + annotate('Swap properties'); + const [a, b] = getTwoRandomProperties(); + [a.value, b.value] = [b.value, a.value]; + } + } + + function duplicatePropertyValue() { + if (propertyIndicies.length > 1) { + const [a, b] = random.shuffle(getTwoRandomProperties()); + if (common.isLargeNode(b.value)) { + return; + } + annotate('Duplicate a property value'); + a.value = babelTypes.cloneDeep(b.value); + } + } + + function insertRandomValue() { + annotate('Insert a random value'); + const property = getOneRandomProperty(); + property.value = common.randomValue(path); + } + + function stringifyKey() { + annotate('Stringify a property key'); + const property = getOneRandomProperty(); + property.key = keyToString(property.key); + } + + function removeProperty() { + annotate('Remove a property'); + properties.splice(random.single(propertyIndicies), 1); + } + + // Mutation options. Repeated mutations have a higher probability. + const mutations = [ + swapPropertyValues, + swapPropertyValues, + duplicatePropertyValue, + duplicatePropertyValue, + insertRandomValue, + insertRandomValue, + removeProperty, + stringifyKey, + ]; + + // Perform mutation. + random.single(mutations)(); + }, + } + } +} + +module.exports = { + ObjectMutator: ObjectMutator, +}; diff --git a/compiler/forget/packages/js-fuzzer/mutators/try_catch.js b/compiler/forget/packages/js-fuzzer/mutators/try_catch.js new file mode 100644 index 0000000000..57f81f2a00 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/try_catch.js @@ -0,0 +1,175 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Try catch wrapper. + */ + +const babelTypes = require('@babel/types'); + +const common = require('./common.js'); +const mutator = require('./mutator.js'); +const random = require('../random.js'); + +// Default target probability for skipping try-catch completely. +const DEFAULT_SKIP_PROB = 0.2; + +// Default target probability to wrap only on toplevel, i.e. to not nest +// try-catch. +const DEFAULT_TOPLEVEL_PROB = 0.3; + +// Probability to deviate from defaults and use extreme cases. +const IGNORE_DEFAULT_PROB = 0.05; + +// Member expressions to be wrapped. List of (object, property) identifier +// tuples. +const WRAPPED_MEMBER_EXPRESSIONS = [ + ['WebAssembly', 'Module'], + ['WebAssembly', 'Instantiate'], +]; + +function wrapTryCatch(node) { + return babelTypes.tryStatement( + babelTypes.blockStatement([node]), + babelTypes.catchClause( + babelTypes.identifier('e'), + babelTypes.blockStatement([]))); +} + +function wrapTryCatchInFunction(node) { + const ret = wrapTryCatch(babelTypes.returnStatement(node)); + const anonymousFun = babelTypes.functionExpression( + null, [], babelTypes.blockStatement([ret])); + return babelTypes.callExpression(anonymousFun, []); +} + +// Wrap particular member expressions after `new` that are known to appear +// in initializer lists of `let` and `const`. +function replaceNewExpression(path) { + const callee = path.node.callee; + if (!babelTypes.isMemberExpression(callee) || + !babelTypes.isIdentifier(callee.object) || + !babelTypes.isIdentifier(callee.property)) { + return; + } + if (WRAPPED_MEMBER_EXPRESSIONS.some( + ([object, property]) => callee.object.name === object && + callee.property.name === property)) { + path.replaceWith(wrapTryCatchInFunction(path.node)); + path.skip(); + } +} + +function replaceAndSkip(path) { + if (!babelTypes.isLabeledStatement(path.parent) || + !babelTypes.isLoop(path.node)) { + // Don't wrap loops with labels as it makes continue + // statements syntactically invalid. We wrap the label + // instead below. + path.replaceWith(wrapTryCatch(path.node)); + } + // Prevent infinite looping. + path.skip(); +} + +class AddTryCatchMutator extends mutator.Mutator { + callWithProb(path, fun) { + const probability = random.random(); + if (probability < this.skipProb * this.loc) { + // Entirely skip try-catch wrapper. + path.skip(); + } else if (probability < (this.skipProb + this.toplevelProb) * this.loc) { + // Only wrap on top-level. + fun(path); + } + } + + get visitor() { + const thisMutator = this; + const accessStatement = { + enter(path) { + thisMutator.callWithProb(path, replaceAndSkip); + }, + exit(path) { + // Apply nested wrapping (is only executed if not skipped above). + replaceAndSkip(path); + } + }; + return { + Program: { + enter(path) { + // Track original source location fraction in [0, 1). + thisMutator.loc = 0; + // Target probability for skipping try-catch. + thisMutator.skipProb = DEFAULT_SKIP_PROB; + // Target probability for not nesting try-catch. + thisMutator.toplevelProb = DEFAULT_TOPLEVEL_PROB; + // Maybe deviate from target probability for the entire test. + if (random.choose(IGNORE_DEFAULT_PROB)) { + thisMutator.skipProb = random.uniform(0, 1); + thisMutator.toplevelProb = random.uniform(0, 1); + thisMutator.annotate( + path.node, + 'Target skip probability ' + thisMutator.skipProb + + ' and toplevel probability ' + thisMutator.toplevelProb); + } + } + }, + Noop: { + enter(path) { + if (common.getSourceLoc(path.node)) { + thisMutator.loc = common.getSourceLoc(path.node); + } + }, + }, + ExpressionStatement: accessStatement, + IfStatement: accessStatement, + LabeledStatement: { + enter(path) { + // Apply an extra try-catch around the label of a loop, since we + // ignore the loop itself if it has a label. + if (babelTypes.isLoop(path.node.body)) { + thisMutator.callWithProb(path, replaceAndSkip); + } + }, + exit(path) { + // Apply nested wrapping (is only executed if not skipped above). + if (babelTypes.isLoop(path.node.body)) { + replaceAndSkip(path); + } + }, + }, + // This covers {While|DoWhile|ForIn|ForOf|For}Statement. + Loop: accessStatement, + NewExpression: { + enter(path) { + thisMutator.callWithProb(path, replaceNewExpression); + }, + exit(path) { + // Apply nested wrapping (is only executed if not skipped above). + replaceNewExpression(path); + } + }, + SwitchStatement: accessStatement, + VariableDeclaration: { + enter(path) { + if (path.node.kind !== 'var' || babelTypes.isLoop(path.parent)) + return; + thisMutator.callWithProb(path, replaceAndSkip); + }, + exit(path) { + if (path.node.kind !== 'var' || babelTypes.isLoop(path.parent)) + return; + // Apply nested wrapping (is only executed if not skipped above). + replaceAndSkip(path); + } + }, + WithStatement: accessStatement, + }; + } +} + +module.exports = { + AddTryCatchMutator: AddTryCatchMutator, +} diff --git a/compiler/forget/packages/js-fuzzer/mutators/variable_mutator.js b/compiler/forget/packages/js-fuzzer/mutators/variable_mutator.js new file mode 100644 index 0000000000..4222932058 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/variable_mutator.js @@ -0,0 +1,73 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Variables mutator. + */ + +'use strict'; + +const babelTypes = require('@babel/types'); + +const common = require('./common.js'); +const random = require('../random.js'); +const mutator = require('./mutator.js'); + +function _isInFunctionParam(path) { + const child = path.find(p => p.parent && babelTypes.isFunction(p.parent)); + return child && child.parentKey === 'params'; +} + +class VariableMutator extends mutator.Mutator { + constructor(settings) { + super(); + this.settings = settings; + } + + get visitor() { + const thisMutator = this; + + return { + Identifier(path) { + if (!random.choose(thisMutator.settings.MUTATE_VARIABLES)) { + return; + } + + if (!common.isVariableIdentifier(path.node.name)) { + return; + } + + // Don't mutate variables that are being declared. + if (babelTypes.isVariableDeclarator(path.parent)) { + return; + } + + // Don't mutate function params. + if (_isInFunctionParam(path)) { + return; + } + + if (common.isInForLoopCondition(path) || + common.isInWhileLoop(path)) { + return; + } + + const randVar = common.randomVariable(path); + if (!randVar) { + return; + } + + const newName = randVar.name; + thisMutator.annotate( + path.node, + `Replaced ${path.node.name} with ${newName}`); + path.node.name = newName; + } + }; + } +} + +module.exports = { + VariableMutator: VariableMutator, +}; diff --git a/compiler/forget/packages/js-fuzzer/mutators/variable_or_object_mutation.js b/compiler/forget/packages/js-fuzzer/mutators/variable_or_object_mutation.js new file mode 100644 index 0000000000..3c96464c81 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/mutators/variable_or_object_mutation.js @@ -0,0 +1,154 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Variables mutator. + */ + +'use strict'; + +const babelTemplate = require('@babel/template').default; +const babelTypes = require('@babel/types'); + +const common = require('./common.js'); +const random = require('../random.js'); +const mutator = require('./mutator.js'); + +const MAX_MUTATION_RECURSION_DEPTH = 5; + +class VariableOrObjectMutator extends mutator.Mutator { + constructor(settings) { + super(); + this.settings = settings; + } + + _randomVariableOrObject(path) { + const randomVar = common.randomVariable(path); + if (random.choose(0.05) || !randomVar) { + return common.randomObject(); + } + + return randomVar; + } + + _randomVariableOrObjectMutations(path, recurseDepth=0) { + if (recurseDepth >= MAX_MUTATION_RECURSION_DEPTH) { + return new Array(); + } + + const probability = random.random(); + + if (probability < 0.3) { + const first = this._randomVariableOrObjectMutations(path, recurseDepth + 1); + const second = this._randomVariableOrObjectMutations( + path, recurseDepth + 1); + return first.concat(second); + } + + const randVarOrObject = this._randomVariableOrObject(path); + const randProperty = common.randomProperty(randVarOrObject); + let newRandVarOrObject = randVarOrObject; + if (random.choose(0.2)) { + newRandVarOrObject = this._randomVariableOrObject(path); + } + + const mutations = new Array(); + + if (probability < 0.4) { + const template = babelTemplate( + 'delete IDENTIFIER[PROPERTY], __callGC()') + mutations.push(template({ + IDENTIFIER: randVarOrObject, + PROPERTY: randProperty + })); + } else if (probability < 0.5) { + const template = babelTemplate( + 'IDENTIFIER[PROPERTY], __callGC()') + mutations.push(template({ + IDENTIFIER: randVarOrObject, + PROPERTY: randProperty + })); + } else if (probability < 0.6) { + const template = babelTemplate( + 'IDENTIFIER[PROPERTY] = RANDOM, __callGC()') + mutations.push(template({ + IDENTIFIER: randVarOrObject, + PROPERTY: randProperty, + RANDOM: common.randomValue(path), + })); + } else if (probability < 0.7) { + mutations.push( + babelTypes.expressionStatement( + common.callRandomFunction(path, randVarOrObject))); + } else if (probability < 0.8) { + const template = babelTemplate( + 'VAR = IDENTIFIER, __callGC()') + var randomVar = common.randomVariable(path); + if (!randomVar) { + return mutations; + } + + mutations.push(template({ + VAR: randomVar, + IDENTIFIER: randVarOrObject, + })); + } else if (probability < 0.9) { + const template = babelTemplate( + 'if (IDENTIFIER != null && typeof(IDENTIFIER) == "object") ' + + 'Object.defineProperty(IDENTIFIER, PROPERTY, {value: VALUE})') + mutations.push(template({ + IDENTIFIER: newRandVarOrObject, + PROPERTY: randProperty, + VALUE: common.randomValue(path), + })); + } else { + const template = babelTemplate( + 'if (IDENTIFIER != null && typeof(IDENTIFIER) == "object") ' + + 'Object.defineProperty(IDENTIFIER, PROPERTY, {' + + 'get: function() { GETTER_MUTATION ; return VALUE; },' + + 'set: function(value) { SETTER_MUTATION; }' + + '})'); + mutations.push(template({ + IDENTIFIER: newRandVarOrObject, + PROPERTY: randProperty, + GETTER_MUTATION: this._randomVariableOrObjectMutations( + path, recurseDepth + 1), + SETTER_MUTATION: this._randomVariableOrObjectMutations( + path, recurseDepth + 1), + VALUE: common.randomValue(path), + })); + } + + return mutations; + } + + + get visitor() { + const settings = this.settings; + const thisMutator = this; + + return { + ExpressionStatement(path) { + if (!random.choose(settings.ADD_VAR_OR_OBJ_MUTATIONS)) { + return; + } + + const mutations = thisMutator._randomVariableOrObjectMutations(path); + thisMutator.annotate(mutations[0], 'Random mutation'); + + if (random.choose(0.5)) { + thisMutator.insertBeforeSkip(path, mutations); + } else { + thisMutator.insertAfterSkip(path, mutations); + } + + path.skip(); + } + }; + } +} + +module.exports = { + VariableOrObjectMutator: VariableOrObjectMutator, +}; diff --git a/compiler/forget/packages/js-fuzzer/package.json b/compiler/forget/packages/js-fuzzer/package.json new file mode 100644 index 0000000000..962585d2e4 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/package.json @@ -0,0 +1,34 @@ +{ + "name": "ochang_js_fuzzer", + "version": "1.0.0", + "description": "", + "main": "run.js", + "scripts": { + "test": "echo 'no test'", + "build": "echo 'no build'" + }, + "bin": "run.js", + "author": "ochang@google.com", + "license": "ISC", + "dependencies": { + "@babel/generator": "^7.1.3", + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.4", + "@babel/types": "^7.1.3", + "@babel/parser": "^7.1.3", + "commander": "^2.11.0", + "globals": "^10.1.0", + "tempfile": "^3.0.0", + "tempy": "^0.5.0" + }, + "devDependencies": { + "eslint": "^6.8.0", + "mocha": "^3.5.3", + "pkg": "^4.3.4", + "prettier": "2.0.5", + "sinon": "^4.0.0" + }, + "pkg": { + "assets": "resources/**/*" + } +} diff --git a/compiler/forget/packages/js-fuzzer/package.sh b/compiler/forget/packages/js-fuzzer/package.sh new file mode 100755 index 0000000000..61ac465f69 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/package.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Copyright 2020 the V8 project authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +OS="linux" +OS_LABEL="Linux" +SUFFIX="" +if [[ -n "$1" && $1 == "win" ]]; then + OS="win" + OS_LABEL="Windows" + SUFFIX=".exe" +elif [[ -n "$1" && $1 == "macos" ]]; then + OS="macos" + OS_LABEL="MacOS" +fi + +echo "Building and packaging for $OS_LABEL..." +(set -x; $DIR/node_modules/.bin/pkg -t node10-$OS-x64 $DIR) + +rm -rf $DIR/output > /dev/null 2>&1 || true +rm $DIR/output.zip > /dev/null 2>&1 || true + +mkdir $DIR/output +cd $DIR/output +ln -s ../db db +ln -s ../ochang_js_fuzzer$SUFFIX run$SUFFIX +ln -s ../foozzie_launcher.py foozzie_launcher.py +echo "Creating $DIR/output.zip" +(set -x; zip -r $DIR/output.zip * > /dev/null) diff --git a/compiler/forget/packages/js-fuzzer/random.js b/compiler/forget/packages/js-fuzzer/random.js new file mode 100644 index 0000000000..f6d73b6f11 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/random.js @@ -0,0 +1,113 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Random helpers. + */ + +'use strict'; + +const assert = require('assert'); + +function randInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function choose(probability) { + return Math.random() < probability; +} + +function random() { + return Math.random(); +} + +function uniform(min, max) { + return Math.random() * (max - min) + min; +} + +function sample(iterable, count) { + const result = new Array(count); + let index = 0; + + for (const item of iterable) { + if (index < count) { + result[index] = item; + } else { + const randIndex = randInt(0, index); + if (randIndex < count) { + result[randIndex] = item; + } + } + + index++; + } + + if (index < count) { + // Not enough items. + result.length = index; + } + + return result; +} + +function swap(array, p1, p2) { + [array[p1], array[p2]] = [array[p2], array[p1]]; +} + +/** + * Returns "count" elements, randomly selected from "highProbArray" and + * "lowProbArray". Elements from highProbArray have a "factor" times + * higher chance to be chosen. As a side effect, this swaps the chosen + * elements to the end of the respective input arrays. The complexity is + * O(count). + */ +function twoBucketSample(lowProbArray, highProbArray, factor, count) { + // Track number of available elements for choosing. + let low = lowProbArray.length; + let high = highProbArray.length; + assert(low + high >= count); + const result = []; + for (let i = 0; i < count; i++) { + // Map a random number to the summarized indices of both arrays. Give + // highProbArray elements a "factor" times higher probability. + const p = random(); + const index = Math.floor(p * (high * factor + low)); + if (index < low) { + // If the index is in the low part, draw the element and discard it. + result.push(lowProbArray[index]); + swap(lowProbArray, index, --low); + } else { + // Same as above but for a highProbArray element. The index is first + // mapped back to the array's range. + const highIndex = Math.floor((index - low) / factor); + result.push(highProbArray[highIndex]); + swap(highProbArray, highIndex, --high); + } + } + return result; +} + +function single(array) { + return array[randInt(0, array.length - 1)]; +} + +function shuffle(array) { + for (let i = 0; i < array.length - 1; i++) { + const j = randInt(i, array.length - 1); + swap(array, i, j); + } + + return array; +} + +module.exports = { + choose: choose, + randInt: randInt, + random: random, + sample: sample, + shuffle: shuffle, + single: single, + twoBucketSample: twoBucketSample, + uniform: uniform, +} diff --git a/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_chakra.js b/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_chakra.js new file mode 100644 index 0000000000..250d0a3026 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_chakra.js @@ -0,0 +1,17 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +// Adjust chakra behavior for differential fuzzing. + +this.WScript = new Proxy({}, { + get(target, name) { + switch (name) { + case 'Echo': + return __prettyPrintExtra; + default: + return {}; + } + } +}); diff --git a/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_jstest.js b/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_jstest.js new file mode 100644 index 0000000000..5c4885cef2 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_jstest.js @@ -0,0 +1,11 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function debug(msg) { + __prettyPrintExtra(msg); +} + +function shouldBe(_a) { + __prettyPrintExtra((typeof _a == "function" ? _a() : eval(_a))); +} diff --git a/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_library.js b/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_library.js new file mode 100644 index 0000000000..37b95d15b2 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_library.js @@ -0,0 +1,122 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +// Helpers for printing in correctness fuzzing. + +// Global helper functions for printing. +var __prettyPrint; +var __prettyPrintExtra; + +// Track caught exceptions. +var __caught = 0; + +// Track a hash of all printed values - printing is cut off after a +// certain size. +var __hash = 0; + +(function() { + const charCodeAt = String.prototype.charCodeAt; + const join = Array.prototype.join; + const map = Array.prototype.map; + const substring = String.prototype.substring; + const toString = Object.prototype.toString; + + // Same as in mjsunit.js. + const classOf = function(object) { + // Argument must not be null or undefined. + const string = toString.call(object); + // String has format [object ]. + return substring.call(string, 8, string.length - 1); + }; + + // For standard cases use original prettyPrinted from mjsunit. + const origPrettyPrinted = prettyPrinted; + + // Override prettyPrinted with a version that also recusively prints objects + // and arrays with a depth of 4. We don't track circles, but we'd cut off + // after a depth of 4 if there are any. + prettyPrinted = function prettyPrinted(value, depth=4) { + if (depth <= 0) { + return "..."; + } + switch (typeof value) { + case "object": + if (value === null) return "null"; + switch (classOf(value)) { + case "Array": + return prettyPrintedArray(value, depth); + case "Object": + return prettyPrintedObject(value, depth); + } + } + // Fall through to original version for all other types. + return origPrettyPrinted(value); + } + + // Helper for pretty array with depth. + function prettyPrintedArray(array, depth) { + const result = map.call(array, (value, index, array) => { + if (value === undefined && !(index in array)) return ""; + return prettyPrinted(value, depth - 1); + }); + return `[${join.call(result, ", ")}]`; + } + + // Helper for pretty objects with depth. + function prettyPrintedObject(object, depth) { + const keys = Object.keys(object); + const prettyValues = map.call(keys, (key) => { + return `${key}: ${prettyPrinted(object[key], depth - 1)}`; + }); + const content = join.call(prettyValues, ", "); + return `${object.constructor.name || "Object"}{${content}}`; + } + + // Helper for calculating a hash code of a string. + function hashCode(str) { + let hash = 0; + if (str.length == 0) { + return hash; + } + for (let i = 0; i < str.length; i++) { + const char = charCodeAt.call(str, i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; + } + return hash; + } + + // Upper limit for calling extra printing. When reached, hashes of + // strings are tracked and printed instead. + let maxExtraPrinting = 100; + + // Helper for pretty printing. + __prettyPrint = function(value, extra=false) { + let str = prettyPrinted(value); + + // Change __hash with the contents of the full string to + // keep track of differences also when we don't print. + const hash = hashCode(str); + __hash = hashCode(hash + __hash.toString()); + + if (extra && maxExtraPrinting-- <= 0) { + return; + } + + // Cut off long strings to prevent overloading I/O. We still track + // the hash of the full string. + if (str.length > 64) { + const head = substring.call(str, 0, 54); + const tail = substring.call(str, str.length - 10, str.length - 1); + str = `${head}[...]${tail}`; + } + + print(str); + }; + + __prettyPrintExtra = function (value) { + __prettyPrint(value, true); + } +})(); diff --git a/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_mjsunit.js b/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_mjsunit.js new file mode 100644 index 0000000000..901299b90d --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_mjsunit.js @@ -0,0 +1,8 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +// Substitute for mjsunit. We reuse prettyPrinted from mjsunit, but only if +// it is loaded. If not, we use this substitute instead. +let prettyPrinted = value => value; diff --git a/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_suppressions.js b/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_suppressions.js new file mode 100644 index 0000000000..b964f786a5 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_suppressions.js @@ -0,0 +1,12 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +// Don't breach stack limit in differential fuzzing as it leads to +// early bailout. +runNearStackLimit = function(f) { + try { + f(); + } catch (e) {} +}; diff --git a/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_v8.js b/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_v8.js new file mode 100644 index 0000000000..f87641326e --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/resources/differential_fuzz_v8.js @@ -0,0 +1,29 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +// Adjust mjsunit behavior for differential fuzzing. + +// We're not interested in stack traces. +MjsUnitAssertionError = () => {}; + +// Do more printing in assertions for more correctness coverage. +failWithMessage = message => { __prettyPrint(message); }; +assertSame = (expected, found, name_opt) => { __prettyPrint(found); }; +assertNotSame = (expected, found, name_opt) => { __prettyPrint(found); }; +assertEquals = (expected, found, name_opt) => { __prettyPrint(found); }; +assertNotEquals = (expected, found, name_opt) => { __prettyPrint(found); }; +assertNull = (value, name_opt) => { __prettyPrint(value); }; +assertNotNull = (value, name_opt) => { __prettyPrint(value); }; + +// Suppress optimization status as it leads to false positives. +assertUnoptimized = () => {}; +assertOptimized = () => {}; +isNeverOptimize = () => {}; +isAlwaysOptimize = () => {}; +isInterpreted = () => {}; +isBaseline = () => {}; +isUnoptimized = () => {}; +isOptimized = () => {}; +isTurboFanned = () => {}; diff --git a/compiler/forget/packages/js-fuzzer/resources/fuzz_library.js b/compiler/forget/packages/js-fuzzer/resources/fuzz_library.js new file mode 100644 index 0000000000..343b4d149b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/resources/fuzz_library.js @@ -0,0 +1,116 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Slightly modified variants from http://code.fitness/post/2016/01/javascript-enumerate-methods.html. +function __isPropertyOfType(obj, name, type) { + let desc; + try { + desc = Object.getOwnPropertyDescriptor(obj, name); + } catch(e) { + return false; + } + + if (!desc) + return false; + + return typeof type === 'undefined' || typeof desc.value === type; +} + +function __getProperties(obj, type) { + if (typeof obj === "undefined" || obj === null) + return []; + + let properties = []; + for (let name of Object.getOwnPropertyNames(obj)) { + if (__isPropertyOfType(obj, name, type)) + properties.push(name); + } + + let proto = Object.getPrototypeOf(obj); + while (proto && proto != Object.prototype) { + Object.getOwnPropertyNames(proto) + .forEach (name => { + if (name !== 'constructor') { + if (__isPropertyOfType(proto, name, type)) + properties.push(name); + } + }); + proto = Object.getPrototypeOf(proto); + } + return properties; +} + +function* __getObjects(root = this, level = 0) { + if (level > 4) + return; + + let obj_names = __getProperties(root, 'object'); + for (let obj_name of obj_names) { + let obj = root[obj_name]; + if (obj === root) + continue; + + yield obj; + yield* __getObjects(obj, level + 1); + } +} + +function __getRandomObject(seed) { + let objects = []; + for (let obj of __getObjects()) { + objects.push(obj); + } + + return objects[seed % objects.length]; +} + +function __getRandomProperty(obj, seed) { + let properties = __getProperties(obj); + if (!properties.length) + return undefined; + + return properties[seed % properties.length]; +} + +function __callRandomFunction(obj, seed, ...args) +{ + let functions = __getProperties(obj, 'function'); + if (!functions.length) + return; + + let random_function = functions[seed % functions.length]; + try { + obj[random_function](...args); + } catch(e) { } +} + +function runNearStackLimit(f) { + function t() { + try { + return t(); + } catch (e) { + return f(); + } + }; + try { + return t(); + } catch (e) {} +} + +// Limit number of times we cause major GCs in tests to reduce hangs +// when called within larger loops. +let __callGC; +(function() { + let countGC = 0; + __callGC = function() { + if (countGC++ < 50) { + gc(); + } + }; +})(); + +// Neuter common test functions. +try { this.failWithMessage = nop; } catch(e) { } +try { this.triggerAssertFalse = nop; } catch(e) { } +try { this.quit = nop; } catch(e) { } diff --git a/compiler/forget/packages/js-fuzzer/resources/jstest_stubs.js b/compiler/forget/packages/js-fuzzer/resources/jstest_stubs.js new file mode 100644 index 0000000000..b5921e00c8 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/resources/jstest_stubs.js @@ -0,0 +1,41 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Minimally stub out methods from JSTest's standalone-pre.js. +function description(msg) {} +function debug(msg) {} + +function shouldBe(_a) { + print((typeof _a == "function" ? _a() : eval(_a))); +} + +function shouldBeTrue(_a) { shouldBe(_a); } +function shouldBeFalse(_a) { shouldBe(_a); } +function shouldBeNaN(_a) { shouldBe(_a); } +function shouldBeNull(_a) { shouldBe(_a); } +function shouldNotThrow(_a) { shouldBe(_a); } +function shouldThrow(_a) { shouldBe(_a); } + +function noInline() {} +function finishJSTest() {} + +// Stub out $vm. +try { + $vm; +} catch(e) { + const handler = { + get: function(x, prop) { + if (prop == Symbol.toPrimitive) { + return function() { return undefined; }; + } + return dummy; + }, + }; + const dummy = new Proxy(function() { return dummy; }, handler); + this.$vm = dummy; +} + +// Other functions. +function ensureArrayStorage() {} +function transferArrayBuffer() {} diff --git a/compiler/forget/packages/js-fuzzer/resources/stubs.js b/compiler/forget/packages/js-fuzzer/resources/stubs.js new file mode 100644 index 0000000000..37d1c628c6 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/resources/stubs.js @@ -0,0 +1,36 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Helper neuter function. +function nop() { return false; } + +// Stubs for non-standard functions. +try { gc; } catch(e) { + this.gc = function () { + for (let i = 0; i < 10000; i++) { + let s = new String("AAAA" + Math.random()); + } + } +} +try { uneval; } catch(e) { this.uneval = this.nop; } + +try { + // For Chakra tests. + WScript; +} catch(e) { + this.WScript = new Proxy({}, { + get(target, name) { + switch (name) { + case 'Echo': + return print; + default: + return {}; + } + + } + }); +} + +try { this.alert = console.log; } catch(e) { } +try { this.print = console.log; } catch(e) { } diff --git a/compiler/forget/packages/js-fuzzer/run.js b/compiler/forget/packages/js-fuzzer/run.js new file mode 100644 index 0000000000..29049e326c --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/run.js @@ -0,0 +1,241 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Description of this file. + */ + +'use strict'; + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const program = require('commander'); + +const corpus = require('./corpus.js'); +const differentialScriptMutator = require('./differential_script_mutator.js'); +const random = require('./random.js'); +const scriptMutator = require('./script_mutator.js'); +const sourceHelpers = require('./source_helpers.js'); + +// Maximum number of test inputs to use for one fuzz test. +const MAX_TEST_INPUTS_PER_TEST = 10; + +// Base implementations for default or differential fuzzing. +const SCRIPT_MUTATORS = { + default: scriptMutator.ScriptMutator, + foozzie: differentialScriptMutator.DifferentialScriptMutator, +}; + +function getRandomInputs(primaryCorpus, secondaryCorpora, count) { + count = random.randInt(2, count); + + // Choose 40%-80% of inputs from primary corpus. + const primaryCount = Math.floor(random.uniform(0.4, 0.8) * count); + count -= primaryCount; + + let inputs = primaryCorpus.getRandomTestcases(primaryCount); + + // Split remainder equally between the secondary corpora. + const secondaryCount = Math.floor(count / secondaryCorpora.length); + + for (let i = 0; i < secondaryCorpora.length; i++) { + let currentCount = secondaryCount; + if (i == secondaryCorpora.length - 1) { + // Last one takes the remainder. + currentCount = count; + } + + count -= currentCount; + if (currentCount) { + inputs = inputs.concat( + secondaryCorpora[i].getRandomTestcases(currentCount)); + } + } + + return random.shuffle(inputs); +} + +function collect(value, total) { + total.push(value); + return total; +} + +function overrideSettings(settings, settingOverrides) { + for (const setting of settingOverrides) { + const parts = setting.split('='); + settings[parts[0]] = parseFloat(parts[1]); + } +} + +function* randomInputGen(engine) { + const inputDir = path.resolve(program.input_dir); + + const v8Corpus = new corpus.Corpus(inputDir, 'v8'); + const chakraCorpus = new corpus.Corpus(inputDir, 'chakra'); + const spiderMonkeyCorpus = new corpus.Corpus(inputDir, 'spidermonkey'); + const jscCorpus = new corpus.Corpus(inputDir, 'WebKit/JSTests'); + const crashTestsCorpus = new corpus.Corpus(inputDir, 'CrashTests'); + + for (let i = 0; i < program.no_of_files; i++) { + let inputs; + if (engine === 'V8') { + inputs = getRandomInputs( + v8Corpus, + random.shuffle([chakraCorpus, spiderMonkeyCorpus, jscCorpus, + crashTestsCorpus, v8Corpus]), + MAX_TEST_INPUTS_PER_TEST); + } else if (engine == 'chakra') { + inputs = getRandomInputs( + chakraCorpus, + random.shuffle([v8Corpus, spiderMonkeyCorpus, jscCorpus, + crashTestsCorpus]), + MAX_TEST_INPUTS_PER_TEST); + } else if (engine == 'spidermonkey') { + inputs = getRandomInputs( + spiderMonkeyCorpus, + random.shuffle([v8Corpus, chakraCorpus, jscCorpus, + crashTestsCorpus]), + MAX_TEST_INPUTS_PER_TEST); + } else { + inputs = getRandomInputs( + jscCorpus, + random.shuffle([chakraCorpus, spiderMonkeyCorpus, v8Corpus, + crashTestsCorpus]), + MAX_TEST_INPUTS_PER_TEST); + } + + if (inputs.length > 0) { + yield inputs; + } + } +} + +function* corpusInputGen() { + const inputCorpus = new corpus.Corpus( + path.resolve(program.input_dir), + program.mutate_corpus, + program.extra_strict); + for (const input of inputCorpus.getAllTestcases()) { + yield [input]; + } +} + +function* enumerate(iterable) { + let i = 0; + for (const value of iterable) { + yield [i, value]; + i++; + } +} + +function main() { + Error.stackTraceLimit = Infinity; + + program + .version('0.0.1') + .option('-i, --input_dir ', 'Input directory.') + .option('-o, --output_dir ', 'Output directory.') + .option('-n, --no_of_files ', 'Output directory.', parseInt) + .option('-c, --mutate_corpus ', 'Mutate single files in a corpus.') + .option('-e, --extra_strict', 'Additionally parse files in strict mode.') + .option('-m, --mutate ', 'Mutate a file and output results.') + .option('-s, --setting [setting]', 'Settings overrides.', collect, []) + .option('-v, --verbose', 'More verbose printing.') + .option('-z, --zero_settings', 'Zero all settings.') + .parse(process.argv); + + const settings = scriptMutator.defaultSettings(); + if (program.zero_settings) { + for (const key of Object.keys(settings)) { + settings[key] = 0.0; + } + } + + if (program.setting.length > 0) { + overrideSettings(settings, program.setting); + } + + let app_name = process.env.APP_NAME; + if (app_name && app_name.endsWith('.exe')) { + app_name = app_name.substr(0, app_name.length - 4); + } + + if (app_name === 'd8' || + app_name === 'v8_simple_inspector_fuzzer' || + app_name === 'v8_foozzie.py') { + // V8 supports running the raw d8 executable, the inspector fuzzer or + // the differential fuzzing harness 'foozzie'. + settings.engine = 'V8'; + } else if (app_name === 'ch') { + settings.engine = 'chakra'; + } else if (app_name === 'js') { + settings.engine = 'spidermonkey'; + } else if (app_name === 'jsc') { + settings.engine = 'jsc'; + } else { + console.log('ERROR: Invalid APP_NAME'); + process.exit(1); + } + + const mode = process.env.FUZZ_MODE || 'default'; + assert(mode in SCRIPT_MUTATORS, `Unknown mode ${mode}`); + const mutator = new SCRIPT_MUTATORS[mode](settings); + + if (program.mutate) { + const absPath = path.resolve(program.mutate); + const baseDir = path.dirname(absPath); + const fileName = path.basename(absPath); + const input = sourceHelpers.loadSource( + baseDir, fileName, program.extra_strict); + const mutated = mutator.mutateMultiple([input]); + console.log(mutated.code); + return; + } + + let inputGen; + + if (program.mutate_corpus) { + inputGen = corpusInputGen(); + } else { + inputGen = randomInputGen(settings.engine); + } + + for (const [i, inputs] of enumerate(inputGen)) { + const outputPath = path.join(program.output_dir, 'fuzz-' + i + '.js'); + + const start = Date.now(); + const paths = inputs.map(input => input.relPath); + + try { + const mutated = mutator.mutateMultiple(inputs); + fs.writeFileSync(outputPath, mutated.code); + + if (settings.engine === 'V8' && mutated.flags && mutated.flags.length > 0) { + const flagsPath = path.join(program.output_dir, 'flags-' + i + '.js'); + fs.writeFileSync(flagsPath, mutated.flags.join(' ')); + } + } catch (e) { + if (e.message.startsWith('ENOSPC')) { + console.log('ERROR: No space left. Bailing out...'); + console.log(e); + return; + } + console.log(`ERROR: Exception during mutate: ${paths}`); + console.log(e); + continue; + } finally { + if (program.verbose) { + const duration = Date.now() - start; + console.log(`Mutating ${paths} took ${duration} ms.`); + } + } + if ((i + 1) % 10 == 0) { + console.log('Up to ', i + 1); + } + } +} + +main(); diff --git a/compiler/forget/packages/js-fuzzer/script_mutator.js b/compiler/forget/packages/js-fuzzer/script_mutator.js new file mode 100644 index 0000000000..8729a73f30 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/script_mutator.js @@ -0,0 +1,253 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Script mutator. + */ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const common = require('./mutators/common.js'); +const db = require('./db.js'); +const random = require('./random.js'); +const sourceHelpers = require('./source_helpers.js'); + +const { AddTryCatchMutator } = require('./mutators/try_catch.js'); +const { ArrayMutator } = require('./mutators/array_mutator.js'); +const { CrossOverMutator } = require('./mutators/crossover_mutator.js'); +const { ExpressionMutator } = require('./mutators/expression_mutator.js'); +const { FunctionCallMutator } = require('./mutators/function_call_mutator.js'); +const { IdentifierNormalizer } = require('./mutators/normalizer.js'); +const { NumberMutator } = require('./mutators/number_mutator.js'); +const { ObjectMutator } = require('./mutators/object_mutator.js'); +const { VariableMutator } = require('./mutators/variable_mutator.js'); +const { VariableOrObjectMutator } = require('./mutators/variable_or_object_mutation.js'); + +const MAX_EXTRA_MUTATIONS = 5; + +function defaultSettings() { + return { + ADD_VAR_OR_OBJ_MUTATIONS: 0.1, + DIFF_FUZZ_EXTRA_PRINT: 0.1, + DIFF_FUZZ_TRACK_CAUGHT: 0.4, + MUTATE_ARRAYS: 0.1, + MUTATE_CROSSOVER_INSERT: 0.05, + MUTATE_EXPRESSIONS: 0.1, + MUTATE_FUNCTION_CALLS: 0.1, + MUTATE_NUMBERS: 0.05, + MUTATE_OBJECTS: 0.1, + MUTATE_VARIABLES: 0.075, + SCRIPT_MUTATOR_EXTRA_MUTATIONS: 0.2, + SCRIPT_MUTATOR_SHUFFLE: 0.2, + }; +} + +class Result { + constructor(code, flags) { + this.code = code; + this.flags = flags; + } +} + +class ScriptMutator { + constructor(settings, db_path=undefined) { + // Use process.cwd() to bypass pkg's snapshot filesystem. + this.mutateDb = new db.MutateDb(db_path || path.join(process.cwd(), 'db')); + this.mutators = [ + new ArrayMutator(settings), + new ObjectMutator(settings), + new VariableMutator(settings), + new NumberMutator(settings), + new CrossOverMutator(settings, this.mutateDb), + new ExpressionMutator(settings), + new FunctionCallMutator(settings), + new VariableOrObjectMutator(settings), + ]; + this.trycatch = new AddTryCatchMutator(settings); + this.settings = settings; + } + + _addMjsunitIfNeeded(dependencies, input) { + if (dependencies.has('mjsunit')) { + return; + } + + if (!input.absPath.includes('mjsunit')) { + return; + } + + // Find mjsunit.js + let mjsunitPath = input.absPath; + while (path.dirname(mjsunitPath) != mjsunitPath && + path.basename(mjsunitPath) != 'mjsunit') { + mjsunitPath = path.dirname(mjsunitPath); + } + + if (path.basename(mjsunitPath) == 'mjsunit') { + mjsunitPath = path.join(mjsunitPath, 'mjsunit.js'); + dependencies.set('mjsunit', sourceHelpers.loadDependencyAbs( + input.baseDir, mjsunitPath)); + return; + } + + console.log('ERROR: Failed to find mjsunit.js'); + } + + _addSpiderMonkeyShellIfNeeded(dependencies, input) { + // Find shell.js files + const shellJsPaths = new Array(); + let currentDir = path.dirname(input.absPath); + + while (path.dirname(currentDir) != currentDir) { + const shellJsPath = path.join(currentDir, 'shell.js'); + if (fs.existsSync(shellJsPath)) { + shellJsPaths.push(shellJsPath); + } + + if (currentDir == 'spidermonkey') { + break; + } + currentDir = path.dirname(currentDir); + } + + // Add shell.js dependencies in reverse to add ones that are higher up in + // the directory tree first. + for (let i = shellJsPaths.length - 1; i >= 0; i--) { + if (!dependencies.has(shellJsPaths[i])) { + const dependency = sourceHelpers.loadDependencyAbs( + input.baseDir, shellJsPaths[i]); + dependencies.set(shellJsPaths[i], dependency); + } + } + } + + _addJSTestStubsIfNeeded(dependencies, input) { + if (dependencies.has('jstest_stubs') || + !input.absPath.includes('JSTests')) { + return; + } + dependencies.set( + 'jstest_stubs', sourceHelpers.loadResource('jstest_stubs.js')); + } + + mutate(source) { + let mutators = this.mutators.slice(); + let annotations = []; + if (random.choose(this.settings.SCRIPT_MUTATOR_SHUFFLE)){ + annotations.push(' Script mutator: using shuffled mutators'); + random.shuffle(mutators); + } + + if (random.choose(this.settings.SCRIPT_MUTATOR_EXTRA_MUTATIONS)){ + for (let i = random.randInt(1, MAX_EXTRA_MUTATIONS); i > 0; i--) { + let mutator = random.single(this.mutators); + mutators.push(mutator); + annotations.push(` Script mutator: extra ${mutator.constructor.name}`); + } + } + + // Try-catch wrapping should always be the last mutation. + mutators.push(this.trycatch); + + for (const mutator of mutators) { + mutator.mutate(source); + } + + for (const annotation of annotations.reverse()) { + sourceHelpers.annotateWithComment(source.ast, annotation); + } + } + + // Returns parsed dependencies for inputs. + resolveInputDependencies(inputs) { + const dependencies = new Map(); + + // Resolve test harness files. + inputs.forEach(input => { + try { + // TODO(machenbach): Some harness files contain load expressions + // that are not recursively resolved. We already remove them, but we + // also need to load the dependencies they point to. + this._addJSTestStubsIfNeeded(dependencies, input); + this._addMjsunitIfNeeded(dependencies, input) + this._addSpiderMonkeyShellIfNeeded(dependencies, input); + } catch (e) { + console.log( + 'ERROR: Failed to resolve test harness for', input.relPath); + throw e; + } + }); + + // Resolve dependencies loaded within the input files. + inputs.forEach(input => { + try { + input.loadDependencies(dependencies); + } catch (e) { + console.log( + 'ERROR: Failed to resolve dependencies for', input.relPath); + throw e; + } + }); + + // Map.values() returns values in insertion order. + return Array.from(dependencies.values()); + } + + // Combines input dependencies with fuzzer resources. + resolveDependencies(inputs) { + const dependencies = this.resolveInputDependencies(inputs); + + // Add stubs for non-standard functions in the beginning. + dependencies.unshift(sourceHelpers.loadResource('stubs.js')); + + // Add our fuzzing support helpers. This also overrides some common test + // functions from earlier dependencies that cause early bailouts. + dependencies.push(sourceHelpers.loadResource('fuzz_library.js')); + + return dependencies; + } + + // Normalizes, combines and mutates multiple inputs. + mutateInputs(inputs) { + const normalizerMutator = new IdentifierNormalizer(); + + for (const [index, input] of inputs.entries()) { + try { + normalizerMutator.mutate(input); + } catch (e) { + console.log('ERROR: Failed to normalize ', input.relPath); + throw e; + } + + common.setSourceLoc(input, index, inputs.length); + } + + // Combine ASTs into one. This is so that mutations have more context to + // cross over content between ASTs (e.g. variables). + const combinedSource = common.concatPrograms(inputs); + this.mutate(combinedSource); + + return combinedSource; + } + + mutateMultiple(inputs) { + // High level operation: + // 1) Compute dependencies from inputs. + // 2) Normalize, combine and mutate inputs. + // 3) Generate code with dependency code prepended. + const dependencies = this.resolveDependencies(inputs); + const combinedSource = this.mutateInputs(inputs); + const code = sourceHelpers.generateCode(combinedSource, dependencies); + const flags = common.concatFlags(dependencies.concat([combinedSource])); + return new Result(code, flags); + } +} + +module.exports = { + defaultSettings: defaultSettings, + ScriptMutator: ScriptMutator, +}; diff --git a/compiler/forget/packages/js-fuzzer/source_helpers.js b/compiler/forget/packages/js-fuzzer/source_helpers.js new file mode 100644 index 0000000000..28709e6b56 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/source_helpers.js @@ -0,0 +1,466 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Source loader. + */ + +const fs = require('fs'); +const fsPath = require('path'); + +const { EOL } = require('os'); + +const babelGenerator = require('@babel/generator').default; +const babelTraverse = require('@babel/traverse').default; +const babelTypes = require('@babel/types'); +const babylon = require('@babel/parser'); + +const exceptions = require('./exceptions.js'); + +const SCRIPT = Symbol('SCRIPT'); +const MODULE = Symbol('MODULE'); + +const V8_BUILTIN_PREFIX = '__V8Builtin'; +const V8_REPLACE_BUILTIN_REGEXP = new RegExp( + V8_BUILTIN_PREFIX + '(\\w+)\\(', 'g'); + +const BABYLON_OPTIONS = { + sourceType: 'script', + allowReturnOutsideFunction: true, + tokens: false, + ranges: false, + plugins: [ + 'asyncGenerators', + 'bigInt', + 'classPrivateMethods', + 'classPrivateProperties', + 'classProperties', + 'doExpressions', + 'exportDefaultFrom', + 'nullishCoalescingOperator', + 'numericSeparator', + 'objectRestSpread', + 'optionalCatchBinding', + 'optionalChaining', + ], +} + +const BABYLON_REPLACE_VAR_OPTIONS = Object.assign({}, BABYLON_OPTIONS); +BABYLON_REPLACE_VAR_OPTIONS['placeholderPattern'] = /^VAR_[0-9]+$/; + +function _isV8OrSpiderMonkeyLoad(path) { + // 'load' and 'loadRelativeToScript' used by V8 and SpiderMonkey. + return (babelTypes.isIdentifier(path.node.callee) && + (path.node.callee.name == 'load' || + path.node.callee.name == 'loadRelativeToScript') && + path.node.arguments.length == 1 && + babelTypes.isStringLiteral(path.node.arguments[0])); +} + +function _isChakraLoad(path) { + // 'WScript.LoadScriptFile' used by Chakra. + // TODO(ochang): The optional second argument can change semantics ("self", + // "samethread", "crossthread" etc). + // Investigate whether if it still makes sense to include them. + return (babelTypes.isMemberExpression(path.node.callee) && + babelTypes.isIdentifier(path.node.callee.property) && + path.node.callee.property.name == 'LoadScriptFile' && + path.node.arguments.length >= 1 && + babelTypes.isStringLiteral(path.node.arguments[0])); +} + +function _findPath(path, caseSensitive=true) { + // If the path exists, return the path. Otherwise return null. Used to handle + // case insensitive matches for Chakra tests. + if (caseSensitive) { + return fs.existsSync(path) ? path : null; + } + + path = fsPath.normalize(fsPath.resolve(path)); + const pathComponents = path.split(fsPath.sep); + let realPath = fsPath.resolve(fsPath.sep); + + for (let i = 1; i < pathComponents.length; i++) { + // For each path component, do a directory listing to see if there is a case + // insensitive match. + const curListing = fs.readdirSync(realPath); + let realComponent = null; + for (const component of curListing) { + if (i < pathComponents.length - 1 && + !fs.statSync(fsPath.join(realPath, component)).isDirectory()) { + continue; + } + + if (component.toLowerCase() == pathComponents[i].toLowerCase()) { + realComponent = component; + break; + } + } + + if (!realComponent) { + return null; + } + + realPath = fsPath.join(realPath, realComponent); + } + + return realPath; +} + +function _findDependentCodePath(filePath, baseDirectory, caseSensitive=true) { + const fullPath = fsPath.join(baseDirectory, filePath); + + const realPath = _findPath(fullPath, caseSensitive) + if (realPath) { + // Check base directory of current file. + return realPath; + } + + while (fsPath.dirname(baseDirectory) != baseDirectory) { + // Walk up the directory tree. + const testPath = fsPath.join(baseDirectory, filePath); + const realPath = _findPath(testPath, caseSensitive) + if (realPath) { + return realPath; + } + + baseDirectory = fsPath.dirname(baseDirectory); + } + + return null; +} + +/** + * Removes V8/Spidermonkey/Chakra load expressions in a source AST and returns + * their string values in an array. + * + * @param {string} originalFilePath Absolute path to file. + * @param {AST} ast Babel AST of the sources. + */ +function resolveLoads(originalFilePath, ast) { + const dependencies = []; + + babelTraverse(ast, { + CallExpression(path) { + const isV8OrSpiderMonkeyLoad = _isV8OrSpiderMonkeyLoad(path); + const isChakraLoad = _isChakraLoad(path); + if (!isV8OrSpiderMonkeyLoad && !isChakraLoad) { + return; + } + + let loadValue = path.node.arguments[0].extra.rawValue; + // Normalize Windows path separators. + loadValue = loadValue.replace(/\\/g, fsPath.sep); + + // Remove load call. + path.remove(); + + const resolvedPath = _findDependentCodePath( + loadValue, fsPath.dirname(originalFilePath), !isChakraLoad); + if (!resolvedPath) { + console.log('ERROR: Could not find dependent path for', loadValue); + return; + } + + if (exceptions.isTestSkippedAbs(resolvedPath)) { + // Dependency is skipped. + return; + } + + // Add the dependency path. + dependencies.push(resolvedPath); + } + }); + return dependencies; +} + +function isStrictDirective(directive) { + return (directive.value && + babelTypes.isDirectiveLiteral(directive.value) && + directive.value.value === 'use strict'); +} + +function replaceV8Builtins(code) { + return code.replace(/%(\w+)\(/g, V8_BUILTIN_PREFIX + '$1('); +} + +function restoreV8Builtins(code) { + return code.replace(V8_REPLACE_BUILTIN_REGEXP, '%$1('); +} + +function maybeUseStict(code, useStrict) { + if (useStrict) { + return `'use strict';${EOL}${EOL}${code}`; + } + return code; +} + +class Source { + constructor(baseDir, relPath, flags, dependentPaths) { + this.baseDir = baseDir; + this.relPath = relPath; + this.flags = flags; + this.dependentPaths = dependentPaths; + this.sloppy = exceptions.isTestSloppyRel(relPath); + } + + get absPath() { + return fsPath.join(this.baseDir, this.relPath); + } + + /** + * Specifies if the source isn't compatible with strict mode. + */ + isSloppy() { + return this.sloppy; + } + + /** + * Specifies if the source has a top-level 'use strict' directive. + */ + isStrict() { + throw Error('Not implemented'); + } + + /** + * Generates the code as a string without any top-level 'use strict' + * directives. V8 natives that were replaced before parsing are restored. + */ + generateNoStrict() { + throw Error('Not implemented'); + } + + /** + * Recursively adds dependencies of a this source file. + * + * @param {Map} dependencies Dependency map to which to add new, parsed + * dependencies unless they are already in the map. + * @param {Map} visitedDependencies A set for avoiding loops. + */ + loadDependencies(dependencies, visitedDependencies) { + visitedDependencies = visitedDependencies || new Set(); + + for (const absPath of this.dependentPaths) { + if (dependencies.has(absPath) || + visitedDependencies.has(absPath)) { + // Already added. + continue; + } + + // Prevent infinite loops. + visitedDependencies.add(absPath); + + // Recursively load dependencies. + const dependency = loadDependencyAbs(this.baseDir, absPath); + dependency.loadDependencies(dependencies, visitedDependencies); + + // Add the dependency. + dependencies.set(absPath, dependency); + } + } +} + +/** + * Represents sources whose AST can be manipulated. + */ +class ParsedSource extends Source { + constructor(ast, baseDir, relPath, flags, dependentPaths) { + super(baseDir, relPath, flags, dependentPaths); + this.ast = ast; + } + + isStrict() { + return !!this.ast.program.directives.filter(isStrictDirective).length; + } + + generateNoStrict() { + const allDirectives = this.ast.program.directives; + this.ast.program.directives = this.ast.program.directives.filter( + directive => !isStrictDirective(directive)); + try { + const code = babelGenerator(this.ast.program, {comments: true}).code; + return restoreV8Builtins(code); + } finally { + this.ast.program.directives = allDirectives; + } + } +} + +/** + * Represents sources with cached code. + */ +class CachedSource extends Source { + constructor(source) { + super(source.baseDir, source.relPath, source.flags, source.dependentPaths); + this.use_strict = source.isStrict(); + this.code = source.generateNoStrict(); + } + + isStrict() { + return this.use_strict; + } + + generateNoStrict() { + return this.code; + } +} + +/** + * Read file path into an AST. + * + * Post-processes the AST by replacing V8 natives and removing disallowed + * natives, as well as removing load expressions and adding the paths-to-load + * as meta data. + */ +function loadSource(baseDir, relPath, parseStrict=false) { + const absPath = fsPath.resolve(fsPath.join(baseDir, relPath)); + const data = fs.readFileSync(absPath, 'utf-8'); + + if (guessType(data) !== SCRIPT) { + return null; + } + + const preprocessed = maybeUseStict(replaceV8Builtins(data), parseStrict); + const ast = babylon.parse(preprocessed, BABYLON_OPTIONS); + + removeComments(ast); + cleanAsserts(ast); + annotateWithOriginalPath(ast, relPath); + + const flags = loadFlags(data); + const dependentPaths = resolveLoads(absPath, ast); + + return new ParsedSource(ast, baseDir, relPath, flags, dependentPaths); +} + +function guessType(data) { + if (data.includes('// MODULE')) { + return MODULE; + } + + return SCRIPT; +} + +/** + * Remove existing comments. + */ +function removeComments(ast) { + babelTraverse(ast, { + enter(path) { + babelTypes.removeComments(path.node); + } + }); +} + +/** + * Removes "Assert" from strings in spidermonkey shells or from older + * crash tests: https://crbug.com/1068268 + */ +function cleanAsserts(ast) { + function replace(string) { + return string == null ? null : string.replace(/[Aa]ssert/g, '*****t'); + } + babelTraverse(ast, { + StringLiteral(path) { + path.node.value = replace(path.node.value); + path.node.extra.raw = replace(path.node.extra.raw); + path.node.extra.rawValue = replace(path.node.extra.rawValue); + }, + TemplateElement(path) { + path.node.value.cooked = replace(path.node.value.cooked); + path.node.value.raw = replace(path.node.value.raw); + }, + }); +} + +/** + * Annotate code with top-level comment. + */ +function annotateWithComment(ast, comment) { + if (ast.program && ast.program.body && ast.program.body.length > 0) { + babelTypes.addComment( + ast.program.body[0], 'leading', comment, true); + } +} + +/** + * Annotate code with original file path. + */ +function annotateWithOriginalPath(ast, relPath) { + annotateWithComment(ast, ' Original: ' + relPath); +} + +// TODO(machenbach): Move this into the V8 corpus. Other test suites don't +// use this flag logic. +function loadFlags(data) { + const result = []; + let count = 0; + for (const line of data.split('\n')) { + if (count++ > 40) { + // No need to process the whole file. Flags are always added after the + // copyright header. + break; + } + const match = line.match(/\/\/ Flags:\s*(.*)\s*/); + if (!match) { + continue; + } + for (const flag of exceptions.filterFlags(match[1].split(/\s+/))) { + result.push(flag); + } + } + return result; +} + +// Convenience helper to load sources with absolute paths. +function loadSourceAbs(baseDir, absPath) { + return loadSource(baseDir, fsPath.relative(baseDir, absPath)); +} + +const dependencyCache = new Map(); + +function loadDependency(baseDir, relPath) { + const absPath = fsPath.join(baseDir, relPath); + let dependency = dependencyCache.get(absPath); + if (!dependency) { + const source = loadSource(baseDir, relPath); + dependency = new CachedSource(source); + dependencyCache.set(absPath, dependency); + } + return dependency; +} + +function loadDependencyAbs(baseDir, absPath) { + return loadDependency(baseDir, fsPath.relative(baseDir, absPath)); +} + +// Convenience helper to load a file from the resources directory. +function loadResource(fileName) { + return loadDependency(__dirname, fsPath.join('resources', fileName)); +} + +function generateCode(source, dependencies=[]) { + const allSources = dependencies.concat([source]); + const codePieces = allSources.map( + source => source.generateNoStrict()); + + if (allSources.some(source => source.isStrict()) && + !allSources.some(source => source.isSloppy())) { + codePieces.unshift('\'use strict\';'); + } + + return codePieces.join(EOL + EOL); +} + +module.exports = { + BABYLON_OPTIONS: BABYLON_OPTIONS, + BABYLON_REPLACE_VAR_OPTIONS: BABYLON_REPLACE_VAR_OPTIONS, + annotateWithComment: annotateWithComment, + generateCode: generateCode, + loadDependencyAbs: loadDependencyAbs, + loadResource: loadResource, + loadSource: loadSource, + loadSourceAbs: loadSourceAbs, + ParsedSource: ParsedSource, +} diff --git a/compiler/forget/packages/js-fuzzer/test/helpers.js b/compiler/forget/packages/js-fuzzer/test/helpers.js new file mode 100644 index 0000000000..172f8c8187 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/helpers.js @@ -0,0 +1,75 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Test helpers. + */ + +'use strict'; + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const sourceHelpers = require('../source_helpers.js'); + +const BASE_DIR = path.join(path.dirname(__dirname), 'test_data'); +const DB_DIR = path.join(BASE_DIR, 'fake_db'); + +const HEADER = `// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +`; + +/** + * Create a function that returns one of `probs` when called. It rotates + * through the values. Useful to replace `random.random()` in tests using + * the probabilities that trigger different interesting cases. + */ +function cycleProbabilitiesFun(probs) { + let index = 0; + return () => { + index = index % probs.length; + return probs[index++]; + }; +} + +/** + * Replace Math.random with a deterministic pseudo-random function. + */ +function deterministicRandom(sandbox) { + let seed = 1; + function random() { + const x = Math.sin(seed++) * 10000; + return x - Math.floor(x); + } + sandbox.stub(Math, 'random').callsFake(() => { return random(); }); +} + +function loadTestData(relPath) { + return sourceHelpers.loadSource(BASE_DIR, relPath); +} + +function assertExpectedResult(expectedPath, result) { + const absPath = path.join(BASE_DIR, expectedPath); + if (process.env.GENERATE) { + fs.writeFileSync(absPath, HEADER + result.trim() + '\n'); + return; + } + + // Omit copyright header when comparing files. + const expected = fs.readFileSync(absPath, 'utf-8').trim().split('\n'); + expected.splice(0, 4); + assert.strictEqual(expected.join('\n'), result.trim()); +} + +module.exports = { + BASE_DIR: BASE_DIR, + DB_DIR: DB_DIR, + assertExpectedResult: assertExpectedResult, + cycleProbabilitiesFun: cycleProbabilitiesFun, + deterministicRandom: deterministicRandom, + loadTestData: loadTestData, +} diff --git a/compiler/forget/packages/js-fuzzer/test/test_available_variables.js b/compiler/forget/packages/js-fuzzer/test/test_available_variables.js new file mode 100644 index 0000000000..bf699ec5a2 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_available_variables.js @@ -0,0 +1,34 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Tests for mutating variables + */ + +'use strict'; + +const babelTraverse = require('@babel/traverse').default; + +const common = require('../mutators/common.js'); +const helpers = require('./helpers.js'); + +describe('Available variables and functions', () => { + it('test', () => { + const source = helpers.loadTestData('available_variables.js'); + const result = new Array(); + + babelTraverse(source.ast, { + CallExpression(path) { + result.push({ + variables: common.availableVariables(path), + functions: common.availableFunctions(path), + }); + } + }); + + helpers.assertExpectedResult( + 'available_variables_expected.js', + JSON.stringify(result, null, 2)); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_corpus.js b/compiler/forget/packages/js-fuzzer/test/test_corpus.js new file mode 100644 index 0000000000..1f35199816 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_corpus.js @@ -0,0 +1,113 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Corpus loading. + */ + +'use strict'; + +const assert = require('assert'); +const sinon = require('sinon'); + +const exceptions = require('../exceptions.js'); +const corpus = require('../corpus.js'); + +const sandbox = sinon.createSandbox(); + +function testSoftSkipped(count, softSkipped, paths) { + sandbox.stub(exceptions, 'getSoftSkipped').callsFake(() => { + return softSkipped; + }); + const mjsunit = new corpus.Corpus('test_data', 'mjsunit_softskipped'); + const cases = mjsunit.getRandomTestcasePaths(count); + assert.deepEqual(paths, cases); +} + +describe('Loading corpus', () => { + afterEach(() => { + sandbox.restore(); + }); + + it('keeps all tests with no soft-skipped tests', () => { + sandbox.stub(Math, 'random').callsFake(() => 0.9); + testSoftSkipped( + 3, + [], + ['mjsunit_softskipped/permitted.js', + 'mjsunit_softskipped/object-literal.js', + 'mjsunit_softskipped/regress/binaryen-123.js']); + }); + + it('choose one test with no soft-skipped tests', () => { + sandbox.stub(Math, 'random').callsFake(() => 0.9); + testSoftSkipped( + 1, + [], + ['mjsunit_softskipped/permitted.js']); + }); + + it('keeps soft-skipped tests', () => { + sandbox.stub(Math, 'random').callsFake(() => 0.9); + testSoftSkipped( + 1, + [/^binaryen.*\.js/, 'object-literal.js'], + ['mjsunit_softskipped/permitted.js']); + }); + + it('keeps no generated soft-skipped tests', () => { + sandbox.stub(Math, 'random').callsFake(() => 0.9); + const softSkipped = [ + // Correctly listed full relative path of test case. + 'mjsunit_softskipped/regress/binaryen-123.js', + // Only basename doesn't match. + 'object-literal.js', + // Only pieces of the path don't match. + 'mjsunit_softskipped', + ]; + sandbox.stub(exceptions, 'getGeneratedSoftSkipped').callsFake( + () => { return new Set(softSkipped); }); + testSoftSkipped( + 2, + // None soft-skipped for basenames and regexps. + [], + // Only binaryen-123.js gets filtered out. + ['mjsunit_softskipped/object-literal.js', + 'mjsunit_softskipped/permitted.js']); + }); + + it('keeps soft-skipped tests by chance', () => { + sandbox.stub(Math, 'random').callsFake(() => 0); + testSoftSkipped( + 3, + [/^binaryen.*\.js/, 'object-literal.js'], + ['mjsunit_softskipped/object-literal.js', + 'mjsunit_softskipped/regress/binaryen-123.js', + 'mjsunit_softskipped/permitted.js']); + }); + + it('caches relative paths', () => { + sandbox.stub(Math, 'random').callsFake(() => 0); + sandbox.stub(exceptions, 'getSoftSkipped').callsFake( + () => { return ['object-literal.js']; }); + const generatedSoftSkipped = [ + 'mjsunit_softskipped/regress/binaryen-123.js', + ]; + sandbox.stub(exceptions, 'getGeneratedSoftSkipped').callsFake( + () => { return new Set(generatedSoftSkipped); }); + const mjsunit = new corpus.Corpus('test_data' , 'mjsunit_softskipped'); + assert.deepEqual( + ['mjsunit_softskipped/object-literal.js', + 'mjsunit_softskipped/regress/binaryen-123.js'], + mjsunit.softSkippedFiles); + assert.deepEqual( + ['mjsunit_softskipped/permitted.js'], + mjsunit.permittedFiles); + assert.deepEqual( + ['mjsunit_softskipped/permitted.js', + 'mjsunit_softskipped/object-literal.js', + 'mjsunit_softskipped/regress/binaryen-123.js'], + Array.from(mjsunit.relFiles())); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_db.js b/compiler/forget/packages/js-fuzzer/test/test_db.js new file mode 100644 index 0000000000..1b645865b7 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_db.js @@ -0,0 +1,33 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Test the script building the DB. + */ + +'use strict'; + +const assert = require('assert'); +const { execSync } = require("child_process"); +const fs = require('fs'); +const path = require('path'); +const tempy = require('tempy'); + +function buildDb(inputDir, corpusName, outputDir) { + execSync( + `node build_db.js -i ${inputDir} -o ${outputDir} ${corpusName}`, + {stdio: ['pipe']}); +} + +describe('DB tests', () => { + // Test feeds an expression that does not apply. + it('omits erroneous expressions', () => { + const outPath = tempy.directory(); + buildDb('test_data/db', 'this', outPath); + const indexFile = path.join(outPath, 'index.json'); + const indexJSON = JSON.parse(fs.readFileSync(indexFile), 'utf-8'); + assert.deepEqual( + indexJSON, {"statements": [], "superStatements": [], "all": []}); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_differential_fuzz.js b/compiler/forget/packages/js-fuzzer/test/test_differential_fuzz.js new file mode 100644 index 0000000000..31fe9898c8 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_differential_fuzz.js @@ -0,0 +1,141 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Tests for differential fuzzing. + */ + +'use strict'; + +const assert = require('assert'); +const program = require('commander'); +const sinon = require('sinon'); + +const helpers = require('./helpers.js'); +const scriptMutator = require('../script_mutator.js'); +const sourceHelpers = require('../source_helpers.js'); +const random = require('../random.js'); + +const { DifferentialFuzzMutator, DifferentialFuzzSuppressions } = require( + '../mutators/differential_fuzz_mutator.js'); +const { DifferentialScriptMutator } = require( + '../differential_script_mutator.js'); + +const sandbox = sinon.createSandbox(); + +function testMutators(settings, mutatorClass, inputFile, expectedFile) { + const source = helpers.loadTestData('differential_fuzz/' + inputFile); + + const mutator = new mutatorClass(settings); + mutator.mutate(source); + + const mutated = sourceHelpers.generateCode(source); + helpers.assertExpectedResult( + 'differential_fuzz/' + expectedFile, mutated); +} + +describe('Differential fuzzing', () => { + beforeEach(() => { + // Zero settings for all mutators. + this.settings = scriptMutator.defaultSettings(); + for (const key of Object.keys(this.settings)) { + this.settings[key] = 0.0; + } + // By default, deterministically use all mutations of differential + // fuzzing. + this.settings['DIFF_FUZZ_EXTRA_PRINT'] = 1.0; + this.settings['DIFF_FUZZ_TRACK_CAUGHT'] = 1.0; + + // Fake fuzzer being called with --input_dir flag. + this.oldInputDir = program.input_dir; + program.input_dir = helpers.BASE_DIR; + }); + + afterEach(() => { + sandbox.restore(); + program.input_dir = this.oldInputDir; + }); + + it('applies suppressions', () => { + // This selects the first random variable when replacing .arguments. + sandbox.stub(random, 'single').callsFake(a => a[0]); + testMutators( + this.settings, + DifferentialFuzzSuppressions, + 'suppressions.js', + 'suppressions_expected.js'); + }); + + it('adds extra printing', () => { + testMutators( + this.settings, + DifferentialFuzzMutator, + 'mutations.js', + 'mutations_expected.js'); + }); + + it('does no extra printing', () => { + this.settings['DIFF_FUZZ_EXTRA_PRINT'] = 0.0; + testMutators( + this.settings, + DifferentialFuzzMutator, + 'exceptions.js', + 'exceptions_expected.js'); + }); + + it('runs end to end', () => { + // Don't choose any zeroed settings or IGNORE_DEFAULT_PROB in try-catch + // mutator. Choose using original flags with >= 2%. + const chooseOrigFlagsProb = 0.2; + sandbox.stub(random, 'choose').callsFake((p) => p >= chooseOrigFlagsProb); + + // Fake build directory from which two json configurations for flags are + // loaded. + const env = { + APP_DIR: 'test_data/differential_fuzz', + GENERATE: process.env.GENERATE, + }; + sandbox.stub(process, 'env').value(env); + + // Fake loading resources and instead load one fixed fake file for each. + sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => { + return helpers.loadTestData('differential_fuzz/fake_resource.js'); + }); + + // Load input files. + const files = [ + 'differential_fuzz/input1.js', + 'differential_fuzz/input2.js', + ]; + const sources = files.map(helpers.loadTestData); + + // Apply top-level fuzzing, with all probabilistic configs switched off. + this.settings['DIFF_FUZZ_EXTRA_PRINT'] = 0.0; + this.settings['DIFF_FUZZ_TRACK_CAUGHT'] = 0.0; + const mutator = new DifferentialScriptMutator( + this.settings, helpers.DB_DIR); + const mutated = mutator.mutateMultiple(sources); + helpers.assertExpectedResult( + 'differential_fuzz/combined_expected.js', mutated.code); + + // Flags for v8_foozzie.py are calculated from v8_fuzz_experiments.json and + // v8_fuzz_flags.json in test_data/differential_fuzz. + const expectedFlags = [ + '--first-config=ignition', + '--second-config=ignition_turbo', + '--second-d8=d8', + '--second-config-extra-flags=--foo1', + '--second-config-extra-flags=--foo2', + '--first-config-extra-flags=--flag1', + '--second-config-extra-flags=--flag1', + '--first-config-extra-flags=--flag2', + '--second-config-extra-flags=--flag2', + '--first-config-extra-flags=--flag3', + '--second-config-extra-flags=--flag3', + '--first-config-extra-flags=--flag4', + '--second-config-extra-flags=--flag4' + ]; + assert.deepEqual(expectedFlags, mutated.flags); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_differential_fuzz_library.js b/compiler/forget/packages/js-fuzzer/test/test_differential_fuzz_library.js new file mode 100644 index 0000000000..58e7c88e69 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_differential_fuzz_library.js @@ -0,0 +1,113 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Tests for the differential-fuzzing library files. + */ + +'use strict'; + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const libPath = path.resolve( + path.join(__dirname, '..', 'resources', 'differential_fuzz_library.js')); +const code = fs.readFileSync(libPath, 'utf-8'); + +// We wire the print function to write to this result variable. +const resultDummy = 'let result; const print = text => { result = text; };'; + +// The prettyPrinted function from mjsunit is reused in the library. +const prettyPrint = 'let prettyPrinted = value => value;'; + +const hookedUpCode = resultDummy + prettyPrint + code; + +// Runs the library, adds test code and verifies the result. +function testLibrary(testCode, expected) { + // The code isn't structured as a module. The test code is expected to + // evaluate to a result which we store in actual. + const actual = eval(hookedUpCode + testCode); + assert.deepEqual(expected, actual); +} + +describe('Differential fuzzing library', () => { + it('prints objects', () => { + testLibrary( + '__prettyPrint([0, 1, 2, 3]); result;', + '[0, 1, 2, 3]'); + testLibrary( + '__prettyPrint({0: 1, 2: 3}); result;', + 'Object{0: 1, 2: 3}'); + testLibrary( + 'const o = {}; o.k = 42;__prettyPrint(o); result;', + 'Object{k: 42}'); + }); + + it('cuts off deep nesting', () => { + // We print only until a nesting depth of 4. + testLibrary( + '__prettyPrint({0: [1, 2, [3, {4: []}]]}); result;', + 'Object{0: [1, 2, [3, Object{4: ...}]]}'); + }); + + it('cuts off long strings', () => { + const long = new Array(66).join('a'); + const head = new Array(55).join('a'); + const tail = new Array(10).join('a'); + testLibrary( + `__prettyPrint("${long}"); result;`, + `${head}[...]${tail}`); + // If the string gets longer, the cut-off version is still the same. + const veryLong = new Array(100).join('a'); + testLibrary( + `__prettyPrint("${veryLong}"); result;`, + `${head}[...]${tail}`); + }); + + it('tracks hash difference', () => { + // Test that we track a hash value for each string we print. + const long = new Array(66).join('a'); + testLibrary( + `__prettyPrint("${long}"); __hash;`, + 2097980794); + // Test that the hash value differs, also when the cut-off result doesn't. + const veryLong = new Array(100).join('a'); + testLibrary( + `__prettyPrint("${veryLong}"); __hash;`, + -428472866); + // Test that repeated calls update the hash. + testLibrary( + `__prettyPrint("${long}");__prettyPrint("${long}"); __hash;`, + -909224493); + }); + + it('limits extra printing', () => { + // Test that after exceeding the limit for calling extra printing, there + // is no new string printed (in the test case no new result added). + testLibrary( + 'for (let i = 0; i < 20; i++) __prettyPrintExtra(i); result;', + '19'); + testLibrary( + 'for (let i = 0; i < 101; i++) __prettyPrintExtra(i); result;', + '99'); + testLibrary( + 'for (let i = 0; i < 102; i++) __prettyPrintExtra(i); result;', + '99'); + }); + + it('tracks hash after limit', () => { + // Test that after exceeding the limit for calling extra printing, the + // hash is still updated. + testLibrary( + 'for (let i = 0; i < 20; i++) __prettyPrintExtra(i); __hash;', + -945753644); + testLibrary( + 'for (let i = 0; i < 101; i++) __prettyPrintExtra(i); __hash;', + 1907055979); + testLibrary( + 'for (let i = 0; i < 102; i++) __prettyPrintExtra(i); __hash;', + -590842070); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_load.js b/compiler/forget/packages/js-fuzzer/test/test_load.js new file mode 100644 index 0000000000..202bbbe576 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_load.js @@ -0,0 +1,69 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Test normalization. + */ + +'use strict'; + +const sinon = require('sinon'); + +const helpers = require('./helpers.js'); +const sourceHelpers = require('../source_helpers.js'); + +const { ScriptMutator } = require('../script_mutator.js'); + +const sandbox = sinon.createSandbox(); + +function testLoad(testPath, expectedPath) { + const mutator = new ScriptMutator({}, helpers.DB_DIR); + const source = helpers.loadTestData(testPath); + const dependencies = mutator.resolveInputDependencies([source]); + const code = sourceHelpers.generateCode(source, dependencies); + helpers.assertExpectedResult(expectedPath, code); +} + +describe('V8 dependencies', () => { + it('test', () => { + testLoad( + 'mjsunit/test_load.js', + 'mjsunit/test_load_expected.js'); + + }); + it('does not loop indefinitely', () => { + testLoad( + 'mjsunit/test_load_self.js', + 'mjsunit/test_load_self_expected.js'); + }); +}); + +describe('Chakra dependencies', () => { + it('test', () => { + testLoad( + 'chakra/load.js', + 'chakra/load_expected.js'); + }); +}); + +describe('JSTest dependencies', () => { + afterEach(() => { + sandbox.restore(); + }); + + it('test', () => { + const fakeStubs = sourceHelpers.loadSource( + helpers.BASE_DIR, 'JSTests/fake_stub.js'); + sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => fakeStubs); + testLoad('JSTests/load.js', 'JSTests/load_expected.js'); + }); +}); + +describe('SpiderMonkey dependencies', () => { + it('test', () => { + testLoad( + 'spidermonkey/test/load.js', + 'spidermonkey/test/load_expected.js'); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_mutate_arrays.js b/compiler/forget/packages/js-fuzzer/test/test_mutate_arrays.js new file mode 100644 index 0000000000..1af1094725 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_mutate_arrays.js @@ -0,0 +1,47 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Tests for mutating arrays + */ + +'use strict'; + +const sinon = require('sinon'); + +const babylon = require('@babel/parser'); + +const common = require('../mutators/common.js'); +const helpers = require('./helpers.js'); +const scriptMutator = require('../script_mutator.js'); +const sourceHelpers = require('../source_helpers.js'); + +const {ArrayMutator} = require('../mutators/array_mutator.js'); + +const sandbox = sinon.createSandbox(); + +describe('Mutate arrays', () => { + afterEach(() => { + sandbox.restore(); + }); + + it('performs all mutations', () => { + // Make random operations deterministic. + sandbox.stub(common, 'randomValue').callsFake( + () => babylon.parseExpression('""')); + helpers.deterministicRandom(sandbox); + + const source = helpers.loadTestData('mutate_arrays.js'); + + const settings = scriptMutator.defaultSettings(); + settings['MUTATE_ARRAYS'] = 1.0; + + const mutator = new ArrayMutator(settings); + mutator.mutate(source); + + const mutated = sourceHelpers.generateCode(source); + helpers.assertExpectedResult( + 'mutate_arrays_expected.js', mutated); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_mutate_expressions.js b/compiler/forget/packages/js-fuzzer/test/test_mutate_expressions.js new file mode 100644 index 0000000000..0b808a77a4 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_mutate_expressions.js @@ -0,0 +1,79 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Tests for mutating expressions + */ + +'use strict'; + +const assert = require('assert'); + +const babelTypes = require('@babel/types'); +const sinon = require('sinon'); + +const common = require('../mutators/common.js'); +const expressionMutator = require('../mutators/expression_mutator.js'); +const helpers = require('./helpers.js'); +const scriptMutator = require('../script_mutator.js'); +const sourceHelpers = require('../source_helpers.js'); +const random = require('../random.js'); + +const sandbox = sinon.createSandbox(); + +function testCloneSiblings(expected_file) { + const source = helpers.loadTestData('mutate_expressions.js'); + + const settings = scriptMutator.defaultSettings(); + settings['MUTATE_EXPRESSIONS'] = 1.0; + + const mutator = new expressionMutator.ExpressionMutator(settings); + mutator.mutate(source); + + const mutated = sourceHelpers.generateCode(source); + helpers.assertExpectedResult(expected_file, mutated); +} + +describe('Mutate expressions', () => { + beforeEach(() => { + // Select the previous sibling. + sandbox.stub(random, 'randInt').callsFake((a, b) => b); + // This chooses cloning siblings. + sandbox.stub(random, 'random').callsFake(() => 0.8); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('clones previous to current', () => { + // Keep the order of [previous, current], select previous. + sandbox.stub(random, 'shuffle').callsFake(a => a); + // Insert after. Keep returning true for the MUTATE_EXPRESSIONS check. + sandbox.stub(random, 'choose').callsFake(a => a === 1); + + testCloneSiblings('mutate_expressions_previous_expected.js'); + }); + + it('clones current to previous', () => { + // Switch the order of [previous, current], select current. + sandbox.stub(random, 'shuffle').callsFake(a => [a[1], a[0]]); + // Insert before. + sandbox.stub(random, 'choose').callsFake(() => true); + + testCloneSiblings('mutate_expressions_current_expected.js'); + }); +}); + +describe('Cloning', () => { + // Ensure that the source location we add are not cloned. + it('is not copying added state', () => { + const source = helpers.loadTestData('mutate_expressions.js'); + common.setSourceLoc(source, 5, 10); + const noopNode = source.ast.program.body[0]; + assert.equal(0.5, common.getSourceLoc(noopNode)); + const cloned = babelTypes.cloneDeep(noopNode); + assert.equal(undefined, common.getSourceLoc(cloned)); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_mutate_function_calls.js b/compiler/forget/packages/js-fuzzer/test/test_mutate_function_calls.js new file mode 100644 index 0000000000..70b6038dd6 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_mutate_function_calls.js @@ -0,0 +1,84 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Tests for mutating funciton calls. + */ + +'use strict'; + +const sinon = require('sinon'); + +const helpers = require('./helpers.js'); +const random = require('../random.js'); +const scriptMutator = require('../script_mutator.js'); +const sourceHelpers = require('../source_helpers.js'); +const functionCallMutator = require('../mutators/function_call_mutator.js'); + +const sandbox = sinon.createSandbox(); + +function loadAndMutate(input_file) { + const source = helpers.loadTestData(input_file); + + const settings = scriptMutator.defaultSettings(); + settings['engine'] = 'V8'; + settings['MUTATE_FUNCTION_CALLS'] = 1.0; + + const mutator = new functionCallMutator.FunctionCallMutator(settings); + mutator.mutate(source); + return source; +} + +describe('Mutate functions', () => { + afterEach(() => { + sandbox.restore(); + }); + + it('is robust without available functions', () => { + sandbox.stub(random, 'random').callsFake(() => { return 0.2; }); + + // We just ensure here that mutating this file doesn't throw. + loadAndMutate('mutate_function_call.js'); + }); + + it('optimizes functions with turbofan in V8', () => { + sandbox.stub(random, 'random').callsFake(() => { return 0.5; }); + sandbox.stub(random, 'choose').callsFake(p => true); + + const source = loadAndMutate('mutate_function_call.js'); + const mutated = sourceHelpers.generateCode(source); + helpers.assertExpectedResult( + 'mutate_function_call_expected.js', mutated); + }); + + it('optimizes functions with maglev in V8', () => { + sandbox.stub(random, 'random').callsFake(() => { return 0.5; }); + // False-path takes 'Maglev'. Other calls to choose should return + // true. It's also used to determine if a mutator should be chosen. + sandbox.stub(random, 'choose').callsFake(p => p == 0.7 ? false : true); + + const source = loadAndMutate('mutate_function_call.js'); + const mutated = sourceHelpers.generateCode(source); + helpers.assertExpectedResult( + 'mutate_function_call_maglev_expected.js', mutated); + }); + + it('compiles functions in V8 to baseline', () => { + sandbox.stub(random, 'random').callsFake(() => { return 0.7; }); + + const source = loadAndMutate('mutate_function_call.js'); + const mutated = sourceHelpers.generateCode(source); + helpers.assertExpectedResult( + 'mutate_function_call_baseline_expected.js', mutated); + }); + + it('deoptimizes functions in V8', () => { + sandbox.stub(random, 'random').callsFake(() => { return 0.8; }); + + const source = loadAndMutate('mutate_function_call.js'); + const mutated = sourceHelpers.generateCode(source); + helpers.assertExpectedResult( + 'mutate_function_call_deopt_expected.js', mutated); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_mutate_numbers.js b/compiler/forget/packages/js-fuzzer/test/test_mutate_numbers.js new file mode 100644 index 0000000000..b9d63116c8 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_mutate_numbers.js @@ -0,0 +1,54 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Tests for mutating variables + */ + +'use strict'; + +const babelTypes = require('@babel/types'); +const sinon = require('sinon'); + +const common = require('../mutators/common.js'); +const helpers = require('./helpers.js'); +const scriptMutator = require('../script_mutator.js'); +const sourceHelpers = require('../source_helpers.js'); +const numberMutator = require('../mutators/number_mutator.js'); +const random = require('../random.js'); + +const sandbox = sinon.createSandbox(); + +describe('Mutate numbers', () => { + beforeEach(() => { + sandbox.stub(common, 'nearbyRandomNumber').callsFake( + () => { return babelTypes.numericLiteral(-3) }); + sandbox.stub(common, 'randomInterestingNumber').callsFake( + () => { return babelTypes.numericLiteral(-4) }); + sandbox.stub(random, 'randInt').callsFake(() => { return -5 }); + + // Interesting cases from number mutator. + const interestingProbs = [0.009, 0.05, 0.5]; + sandbox.stub(random, 'random').callsFake( + helpers.cycleProbabilitiesFun(interestingProbs)); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('test', () => { + const source = helpers.loadTestData('mutate_numbers.js'); + + const settings = scriptMutator.defaultSettings(); + settings['MUTATE_NUMBERS'] = 1.0; + + const mutator = new numberMutator.NumberMutator(settings); + mutator.mutate(source); + + const mutated = sourceHelpers.generateCode(source); + helpers.assertExpectedResult( + 'mutate_numbers_expected.js', mutated); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_mutate_objects.js b/compiler/forget/packages/js-fuzzer/test/test_mutate_objects.js new file mode 100644 index 0000000000..184e627c7a --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_mutate_objects.js @@ -0,0 +1,47 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Tests for mutating object expressions + */ + +'use strict'; + +const sinon = require('sinon'); + +const babylon = require('@babel/parser'); + +const common = require('../mutators/common.js'); +const helpers = require('./helpers.js'); +const scriptMutator = require('../script_mutator.js'); +const sourceHelpers = require('../source_helpers.js'); + +const {ObjectMutator} = require('../mutators/object_mutator.js'); + +const sandbox = sinon.createSandbox(); + +describe('Mutate objects', () => { + afterEach(() => { + sandbox.restore(); + }); + + it('performs all mutations', () => { + // Make random operations deterministic. + sandbox.stub(common, 'randomValue').callsFake( + () => babylon.parseExpression('""')); + helpers.deterministicRandom(sandbox); + + const source = helpers.loadTestData('mutate_objects.js'); + + const settings = scriptMutator.defaultSettings(); + settings['MUTATE_OBJECTS'] = 1.0; + + const mutator = new ObjectMutator(settings); + mutator.mutate(source); + + const mutated = sourceHelpers.generateCode(source); + helpers.assertExpectedResult( + 'mutate_objects_expected.js', mutated); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_mutate_variable_or_object.js b/compiler/forget/packages/js-fuzzer/test/test_mutate_variable_or_object.js new file mode 100644 index 0000000000..7bdfe90cb6 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_mutate_variable_or_object.js @@ -0,0 +1,72 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Test variable-or-object mutator. + */ + +'use strict'; + +const babylon = require('@babel/parser'); +const sinon = require('sinon'); + +const common = require('../mutators/common.js'); +const helpers = require('./helpers.js'); +const variableOrObject = require('../mutators/variable_or_object_mutation.js'); +const random = require('../random.js'); +const sourceHelpers = require('../source_helpers.js'); + +const sandbox = sinon.createSandbox(); + +function testMutations(testPath, expectedPath) { + const source = helpers.loadTestData(testPath); + + const mutator = new variableOrObject.VariableOrObjectMutator( + { ADD_VAR_OR_OBJ_MUTATIONS: 1.0 }); + + mutator.mutate(source); + + const mutated = sourceHelpers.generateCode(source); + helpers.assertExpectedResult(expectedPath, mutated); +} + +describe('Variable or object mutator', () => { + beforeEach(() => { + // Make before/after insertion deterministic. This also chooses + // random objects. + sandbox.stub(random, 'choose').callsFake(() => { return true; }); + // This stubs out the random seed. + sandbox.stub(random, 'randInt').callsFake(() => { return 123; }); + // Random value is itself dependent on too much randomization. + sandbox.stub(common, 'randomValue').callsFake( + () => { return babylon.parseExpression('0'); }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('test', () => { + let index = 0; + // Test different cases of _randomVariableOrObjectMutations in + // variable_or_object_mutation.js. + const choices = [ + 0.2, // Trigger recursive case. + 0.3, // Recursion 1: Delete. + 0.4, // Recursion 2: Property access. + 0.5, // Random assignment. + // 0.6 case for randomFunction omitted as it has too much randomization. + 0.7, // Variable assignment. + 0.8, // Object.defineProperty. + 0.9, // Object.defineProperty recursive. + 0.3, // Recursion 1: Delete. + 0.4, // Recursion 2: Property access. + ]; + sandbox.stub(random, 'random').callsFake( + () => { return choices[index++]; }); + testMutations( + 'mutate_var_or_obj.js', + 'mutate_var_or_obj_expected.js'); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_mutate_variables.js b/compiler/forget/packages/js-fuzzer/test/test_mutate_variables.js new file mode 100644 index 0000000000..e54459910b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_mutate_variables.js @@ -0,0 +1,47 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Tests for mutating variables + */ + +'use strict'; + +const babelTypes = require('@babel/types'); +const sinon = require('sinon'); + +const common = require('../mutators/common.js'); +const helpers = require('./helpers.js'); +const scriptMutator = require('../script_mutator.js'); +const sourceHelpers = require('../source_helpers.js'); +const variableMutator = require('../mutators/variable_mutator.js'); + +const sandbox = sinon.createSandbox(); + +describe('Mutate variables', () => { + beforeEach(() => { + sandbox.stub( + common, 'randomVariable').callsFake( + () => { return babelTypes.identifier('REPLACED') }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('test', () => { + + const source = helpers.loadTestData('mutate_variables.js'); + + const settings = scriptMutator.defaultSettings(); + settings['MUTATE_VARIABLES'] = 1.0; + + const mutator = new variableMutator.VariableMutator(settings); + mutator.mutate(source); + + const mutated = sourceHelpers.generateCode(source); + helpers.assertExpectedResult( + 'mutate_variables_expected.js', mutated); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_mutation_order.js b/compiler/forget/packages/js-fuzzer/test/test_mutation_order.js new file mode 100644 index 0000000000..4d7fbfed54 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_mutation_order.js @@ -0,0 +1,56 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Test shuffling mutators and extra mutations. + * + * Use minimal probability settings to demonstrate order changes of top-level + * mutators. Which mutations are used exactly is not relevant to the test and + * handled pseudo-randomly. + */ + +'use strict'; + +const sinon = require('sinon'); + +const helpers = require('./helpers.js'); +const scriptMutator = require('../script_mutator.js'); +const sourceHelpers = require('../source_helpers.js'); +const random = require('../random.js'); + +const sandbox = sinon.createSandbox(); + +describe('Toplevel mutations', () => { + afterEach(() => { + sandbox.restore(); + }); + + it('shuffle their order', () => { + // Make random operations deterministic. + helpers.deterministicRandom(sandbox); + + this.settings = { + ADD_VAR_OR_OBJ_MUTATIONS: 0.0, + MUTATE_CROSSOVER_INSERT: 0.0, + MUTATE_EXPRESSIONS: 0.0, + MUTATE_FUNCTION_CALLS: 1.0, + MUTATE_NUMBERS: 1.0, + MUTATE_VARIABLES: 0.0, + SCRIPT_MUTATOR_SHUFFLE: 1.0, + SCRIPT_MUTATOR_EXTRA_MUTATIONS: 1.0, + engine: 'V8', + testing: true, + }; + + const source = helpers.loadTestData('mutation_order/input.js'); + const mutator = new scriptMutator.ScriptMutator(this.settings, helpers.DB_DIR); + const mutated = mutator.mutateInputs([source]); + const code = sourceHelpers.generateCode(mutated); + + // The test data should be rich enough to produce a pattern from the + // FunctionCallMutator that afterwards gets mutated by the NumberMutator. + helpers.assertExpectedResult( + 'mutation_order/output_expected.js', code); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_normalize.js b/compiler/forget/packages/js-fuzzer/test/test_normalize.js new file mode 100644 index 0000000000..d92ad572e3 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_normalize.js @@ -0,0 +1,42 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Test normalization. + */ + +'use strict'; + +const helpers = require('./helpers.js'); +const normalizer = require('../mutators/normalizer.js'); +const sourceHelpers = require('../source_helpers.js'); + +describe('Normalize', () => { + it('test basic', () => { + const source = helpers.loadTestData('normalize.js'); + + const mutator = new normalizer.IdentifierNormalizer(); + mutator.mutate(source); + + const normalized_0 = sourceHelpers.generateCode(source); + helpers.assertExpectedResult( + 'normalize_expected_0.js', normalized_0); + + mutator.mutate(source); + const normalized_1 = sourceHelpers.generateCode(source); + helpers.assertExpectedResult( + 'normalize_expected_1.js', normalized_1); + }); + + it('test simple_test.js', () => { + const source = helpers.loadTestData('simple_test.js'); + + const mutator = new normalizer.IdentifierNormalizer(); + mutator.mutate(source); + + const normalized = sourceHelpers.generateCode(source); + helpers.assertExpectedResult( + 'simple_test_expected.js', normalized); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_random.js b/compiler/forget/packages/js-fuzzer/test/test_random.js new file mode 100644 index 0000000000..b27b992720 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_random.js @@ -0,0 +1,51 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Test random utilities. + */ + +'use strict'; + +const assert = require('assert'); +const sinon = require('sinon'); + +const { twoBucketSample } = require('../random.js'); + +const sandbox = sinon.createSandbox(); + + +describe('Two-bucket choosing', () => { + afterEach(() => { + sandbox.restore(); + }); + + it('with one empty', () => { + sandbox.stub(Math, 'random').callsFake(() => 0.5); + assert.deepEqual([1, 2], twoBucketSample([0, 1, 2], [], 1, 2)); + assert.deepEqual([1, 2], twoBucketSample([], [0, 1, 2], 1, 2)); + assert.deepEqual([0], twoBucketSample([0], [], 1, 1)); + assert.deepEqual([0], twoBucketSample([], [0], 1, 1)); + }); + + it('chooses with 0.3', () => { + sandbox.stub(Math, 'random').callsFake(() => 0.3); + assert.deepEqual([1, 2], twoBucketSample([0, 1, 2], [3, 4, 5], 1, 2)); + // Higher factor. + assert.deepEqual([3, 5], twoBucketSample([0, 1, 2], [3, 4, 5], 4, 2)); + }); + + it('chooses with 0.7', () => { + sandbox.stub(Math, 'random').callsFake(() => 0.7); + assert.deepEqual([4, 3], twoBucketSample([0, 1, 2], [3, 4, 5], 1, 2)); + }); + + it('chooses with 0.5', () => { + sandbox.stub(Math, 'random').callsFake(() => 0.5); + assert.deepEqual([3], twoBucketSample([0, 1], [2, 3, 4, 5], 1, 1)); + assert.deepEqual([3], twoBucketSample([0, 1, 2, 3], [4, 5], 1, 1)); + // Higher factor. + assert.deepEqual([4], twoBucketSample([0, 1, 2, 3], [4, 5], 2, 1)); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_regressions.js b/compiler/forget/packages/js-fuzzer/test/test_regressions.js new file mode 100644 index 0000000000..62481f1f40 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_regressions.js @@ -0,0 +1,113 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Regression tests. + */ + +'use strict'; + +const assert = require('assert'); +const { execSync } = require("child_process"); +const fs = require('fs'); +const sinon = require('sinon'); +const tempfile = require('tempfile'); +const tempy = require('tempy'); + +const exceptions = require('../exceptions.js'); +const helpers = require('./helpers.js'); +const scriptMutator = require('../script_mutator.js'); + +const sandbox = sinon.createSandbox(); + +const SYNTAX_ERROR_RE = /.*SyntaxError.*/ + +function createFuzzTest(fake_db, settings, inputFiles) { + const sources = inputFiles.map(input => helpers.loadTestData(input)); + + const mutator = new scriptMutator.ScriptMutator(settings, fake_db); + const result = mutator.mutateMultiple(sources); + + const output_file = tempfile('.js'); + fs.writeFileSync(output_file, result.code); + return output_file; +} + +function execFile(jsFile) { + execSync("node " + jsFile, {stdio: ['pipe']}); +} + +describe('Regression tests', () => { + beforeEach(() => { + helpers.deterministicRandom(sandbox); + + this.settings = { + ADD_VAR_OR_OBJ_MUTATIONS: 0.0, + MUTATE_CROSSOVER_INSERT: 0.0, + MUTATE_EXPRESSIONS: 0.0, + MUTATE_FUNCTION_CALLS: 0.0, + MUTATE_NUMBERS: 0.0, + MUTATE_VARIABLES: 0.0, + engine: 'V8', + testing: true, + } + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('combine strict and with', () => { + // Test that when a file with "use strict" is used in the inputs, + // the result is only strict if no other file contains anything + // prohibited in strict mode (here a with statement). + // It is assumed that such input files are marked as sloppy in the + // auto generated exceptions. + sandbox.stub(exceptions, 'getGeneratedSloppy').callsFake( + () => { return new Set(['regress/strict/input_with.js']); }); + const file = createFuzzTest( + 'test_data/regress/strict/db', + this.settings, + ['regress/strict/input_strict.js', 'regress/strict/input_with.js']); + execFile(file); + }); + + it('combine strict and delete', () => { + // As above with unqualified delete. + sandbox.stub(exceptions, 'getGeneratedSloppy').callsFake( + () => { return new Set(['regress/strict/input_delete.js']); }); + const file = createFuzzTest( + 'test_data/regress/strict/db', + this.settings, + ['regress/strict/input_strict.js', 'regress/strict/input_delete.js']); + execFile(file); + }); + + it('mutates negative value', () => { + // This tests that the combination of number, function call and expression + // mutator does't produce an update expression. + // Previously the 1 in -1 was replaced with another negative number leading + // to e.g. -/*comment/*-2. Then cloning the expression removed the + // comment and produced --2 in the end. + this.settings['MUTATE_NUMBERS'] = 1.0; + this.settings['MUTATE_FUNCTION_CALLS'] = 1.0; + this.settings['MUTATE_EXPRESSIONS'] = 1.0; + const file = createFuzzTest( + 'test_data/regress/numbers/db', + this.settings, + ['regress/numbers/input_negative.js']); + execFile(file); + }); + + it('mutates indices', () => { + // Test that indices are not replaced with a negative number causing a + // syntax error (e.g. {-1: ""}). + this.settings['MUTATE_NUMBERS'] = 1.0; + const file = createFuzzTest( + 'test_data/regress/numbers/db', + this.settings, + ['regress/numbers/input_indices.js']); + execFile(file); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test/test_try_catch.js b/compiler/forget/packages/js-fuzzer/test/test_try_catch.js new file mode 100644 index 0000000000..b7cd296404 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test/test_try_catch.js @@ -0,0 +1,85 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Test normalization. + */ + +'use strict'; + +const sinon = require('sinon'); + +const common = require('../mutators/common.js'); +const helpers = require('./helpers.js'); +const random = require('../random.js'); +const sourceHelpers = require('../source_helpers.js'); +const tryCatch = require('../mutators/try_catch.js'); + +const sandbox = sinon.createSandbox(); + +function loadSource() { + return helpers.loadTestData('try_catch.js'); +} + +function testTryCatch(source, expected) { + const mutator = new tryCatch.AddTryCatchMutator(); + mutator.mutate(source); + + const mutated = sourceHelpers.generateCode(source); + helpers.assertExpectedResult(expected, mutated); +} + +describe('Try catch', () => { + afterEach(() => { + sandbox.restore(); + }); + + // Wrap on exit, hence wrap everything nested. + it('wraps all', () => { + sandbox.stub(random, 'choose').callsFake(() => { return false; }); + sandbox.stub(random, 'random').callsFake(() => { return 0.7; }); + testTryCatch(loadSource(), 'try_catch_expected.js'); + }); + + // Wrap on enter and skip. + it('wraps toplevel', () => { + sandbox.stub(random, 'choose').callsFake(() => { return false; }); + sandbox.stub(random, 'random').callsFake(() => { return 0.04; }); + const source = loadSource(); + + // Fake source fraction 0.1 (i.e. the second of 10 files). + // Probability for toplevel try-catch is 0.05. + common.setSourceLoc(source, 1, 10); + + testTryCatch(source, 'try_catch_toplevel_expected.js'); + }); + + // Choose the rare case of skipping try-catch. + it('wraps nothing', () => { + sandbox.stub(random, 'choose').callsFake(() => { return false; }); + sandbox.stub(random, 'random').callsFake(() => { return 0.01; }); + const source = loadSource(); + + // Fake source fraction 0.1 (i.e. the second of 10 files). + // Probability for skipping is 0.02. + common.setSourceLoc(source, 1, 10); + + testTryCatch(source, 'try_catch_nothing_expected.js'); + }); + + // Choose to alter the target probability to 0.9 resulting in skipping + // all try-catch. + it('wraps nothing with high target probability', () => { + sandbox.stub(random, 'choose').callsFake(() => { return true; }); + sandbox.stub(random, 'uniform').callsFake(() => { return 0.9; }); + sandbox.stub(random, 'random').callsFake(() => { return 0.8; }); + const source = loadSource(); + + // Fake source fraction 0.9 (i.e. the last of 10 files). + // Probability for skipping is 0.81 (0.9 * 0.9). + common.setSourceLoc(source, 9, 10); + + testTryCatch(source, 'try_catch_alternate_expected.js'); + }); +}); diff --git a/compiler/forget/packages/js-fuzzer/test_data/JSTests/fake_stub.js b/compiler/forget/packages/js-fuzzer/test_data/JSTests/fake_stub.js new file mode 100644 index 0000000000..57f50060e2 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/JSTests/fake_stub.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +print("Fake stub"); diff --git a/compiler/forget/packages/js-fuzzer/test_data/JSTests/load.js b/compiler/forget/packages/js-fuzzer/test_data/JSTests/load.js new file mode 100644 index 0000000000..41582e01b2 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/JSTests/load.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +print("JSTest"); diff --git a/compiler/forget/packages/js-fuzzer/test_data/JSTests/load_expected.js b/compiler/forget/packages/js-fuzzer/test_data/JSTests/load_expected.js new file mode 100644 index 0000000000..113397c208 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/JSTests/load_expected.js @@ -0,0 +1,9 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: JSTests/fake_stub.js +print("Fake stub"); + +// Original: JSTests/load.js +print("JSTest"); diff --git a/compiler/forget/packages/js-fuzzer/test_data/available_variables.js b/compiler/forget/packages/js-fuzzer/test_data/available_variables.js new file mode 100644 index 0000000000..d711567829 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/available_variables.js @@ -0,0 +1,33 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +let __v_0 = 0; +let __v_1 = 0; + +console.log(__v_0, __v_1, __f_0, __f_1); + +function __f_0() { + let __v_2 = 0; + console.log(__v_0, __v_1, __v_2, __f_0, __f_1); +} + +let __v_3 = 0; + +console.log(__v_0, __v_1, __v_3, __f_0, __f_1); + +function __f_1(__v_7) { + let __v_4 = 0; + + console.log(__v_0, __v_1, __v_3, __v_4, __v_7, __f_0, __f_1); + { + let __v_5 = 0; + var __v_6 = 0; + console.log(__v_0, __v_1, __v_3, __v_4, __v_5, __v_6, __v_7, __f_0, __f_1, __f_2); + function __f_2 () {}; + console.log(__v_0, __v_1, __v_3, __v_4, __v_5, __v_6, __v_7, __f_0, __f_1, __f_2); + } + // TODO(machenbach): __f_2 is missing as available identifier. + console.log(__v_0, __v_1, __v_3, __v_4, __v_6, __v_7, __f_0, __f_1, __f_2); +} +console.log(__v_0, __v_1, __v_3, __f_0, __f_1); diff --git a/compiler/forget/packages/js-fuzzer/test_data/available_variables_expected.js b/compiler/forget/packages/js-fuzzer/test_data/available_variables_expected.js new file mode 100644 index 0000000000..4562bb23e4 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/available_variables_expected.js @@ -0,0 +1,270 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[ + { + "variables": [ + { + "type": "Identifier", + "name": "__v_0" + }, + { + "type": "Identifier", + "name": "__v_1" + } + ], + "functions": [ + { + "type": "Identifier", + "name": "__f_0" + }, + { + "type": "Identifier", + "name": "__f_1" + } + ] + }, + { + "variables": [ + { + "type": "Identifier", + "name": "__v_2" + }, + { + "type": "Identifier", + "name": "__v_0" + }, + { + "type": "Identifier", + "name": "__v_1" + } + ], + "functions": [ + { + "type": "Identifier", + "name": "__f_0" + }, + { + "type": "Identifier", + "name": "__f_1" + } + ] + }, + { + "variables": [ + { + "type": "Identifier", + "name": "__v_0" + }, + { + "type": "Identifier", + "name": "__v_1" + }, + { + "type": "Identifier", + "name": "__v_3" + } + ], + "functions": [ + { + "type": "Identifier", + "name": "__f_0" + }, + { + "type": "Identifier", + "name": "__f_1" + } + ] + }, + { + "variables": [ + { + "type": "Identifier", + "name": "__v_7" + }, + { + "type": "Identifier", + "name": "__v_4" + }, + { + "type": "Identifier", + "name": "__v_0" + }, + { + "type": "Identifier", + "name": "__v_1" + }, + { + "type": "Identifier", + "name": "__v_3" + } + ], + "functions": [ + { + "type": "Identifier", + "name": "__f_0" + }, + { + "type": "Identifier", + "name": "__f_1" + } + ] + }, + { + "variables": [ + { + "type": "Identifier", + "name": "__v_5" + }, + { + "type": "Identifier", + "name": "__v_7" + }, + { + "type": "Identifier", + "name": "__v_4" + }, + { + "type": "Identifier", + "name": "__v_6" + }, + { + "type": "Identifier", + "name": "__v_0" + }, + { + "type": "Identifier", + "name": "__v_1" + }, + { + "type": "Identifier", + "name": "__v_3" + } + ], + "functions": [ + { + "type": "Identifier", + "name": "__f_2" + }, + { + "type": "Identifier", + "name": "__f_0" + }, + { + "type": "Identifier", + "name": "__f_1" + } + ] + }, + { + "variables": [ + { + "type": "Identifier", + "name": "__v_5" + }, + { + "type": "Identifier", + "name": "__v_7" + }, + { + "type": "Identifier", + "name": "__v_4" + }, + { + "type": "Identifier", + "name": "__v_6" + }, + { + "type": "Identifier", + "name": "__v_0" + }, + { + "type": "Identifier", + "name": "__v_1" + }, + { + "type": "Identifier", + "name": "__v_3" + } + ], + "functions": [ + { + "type": "Identifier", + "name": "__f_2" + }, + { + "type": "Identifier", + "name": "__f_0" + }, + { + "type": "Identifier", + "name": "__f_1" + } + ] + }, + { + "variables": [ + { + "type": "Identifier", + "name": "__v_7" + }, + { + "type": "Identifier", + "name": "__v_4" + }, + { + "type": "Identifier", + "name": "__v_6" + }, + { + "type": "Identifier", + "name": "__v_0" + }, + { + "type": "Identifier", + "name": "__v_1" + }, + { + "type": "Identifier", + "name": "__v_3" + } + ], + "functions": [ + { + "type": "Identifier", + "name": "__f_0" + }, + { + "type": "Identifier", + "name": "__f_1" + } + ] + }, + { + "variables": [ + { + "type": "Identifier", + "name": "__v_0" + }, + { + "type": "Identifier", + "name": "__v_1" + }, + { + "type": "Identifier", + "name": "__v_3" + } + ], + "functions": [ + { + "type": "Identifier", + "name": "__f_0" + }, + { + "type": "Identifier", + "name": "__f_1" + } + ] + } +] diff --git a/compiler/forget/packages/js-fuzzer/test_data/chakra/dir/load3.js b/compiler/forget/packages/js-fuzzer/test_data/chakra/dir/load3.js new file mode 100644 index 0000000000..c836f1b567 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/chakra/dir/load3.js @@ -0,0 +1,6 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +WScript.LoadScriptFile("..\\load2.js", "self"); +console.log('load3'); diff --git a/compiler/forget/packages/js-fuzzer/test_data/chakra/load.js b/compiler/forget/packages/js-fuzzer/test_data/chakra/load.js new file mode 100644 index 0000000000..bfddb5d69e --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/chakra/load.js @@ -0,0 +1,9 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +if (this.WScript && this.WScript.LoadScriptFile) { + WScript.LoadScriptFile("load1.js"); +} + +console.log('load.js'); diff --git a/compiler/forget/packages/js-fuzzer/test_data/chakra/load1.js b/compiler/forget/packages/js-fuzzer/test_data/chakra/load1.js new file mode 100644 index 0000000000..14f6814220 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/chakra/load1.js @@ -0,0 +1,8 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Test case insensitivity. +WScript.LoadScriptFile("DIR\\LoAd3.js"); + +console.log('load1.js'); diff --git a/compiler/forget/packages/js-fuzzer/test_data/chakra/load2.js b/compiler/forget/packages/js-fuzzer/test_data/chakra/load2.js new file mode 100644 index 0000000000..a8ee5d296e --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/chakra/load2.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +console.log('load2.js'); diff --git a/compiler/forget/packages/js-fuzzer/test_data/chakra/load_expected.js b/compiler/forget/packages/js-fuzzer/test_data/chakra/load_expected.js new file mode 100644 index 0000000000..9711227ac3 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/chakra/load_expected.js @@ -0,0 +1,17 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: chakra/load2.js +console.log('load2.js'); + +// Original: chakra/dir/load3.js +console.log('load3'); + +// Original: chakra/load1.js +console.log('load1.js'); + +// Original: chakra/load.js +if (this.WScript && this.WScript.LoadScriptFile) {} + +console.log('load.js'); diff --git a/compiler/forget/packages/js-fuzzer/test_data/cross_over_mutator_class_input.js b/compiler/forget/packages/js-fuzzer/test_data/cross_over_mutator_class_input.js new file mode 100644 index 0000000000..f16fb2fe53 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/cross_over_mutator_class_input.js @@ -0,0 +1,11 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +class __C { + foo() { + let __v_0 = 2; + let __v_1 = 2; + Math.pow(__v_0, __v_1); + } +} diff --git a/compiler/forget/packages/js-fuzzer/test_data/db/this/file.js b/compiler/forget/packages/js-fuzzer/test_data/db/this/file.js new file mode 100644 index 0000000000..115616da0d --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/db/this/file.js @@ -0,0 +1,9 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function C() { + this.c = "c"; +} + +var c = new C(); diff --git a/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/combined_expected.js b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/combined_expected.js new file mode 100644 index 0000000000..1caeebd13f --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/combined_expected.js @@ -0,0 +1,59 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: differential_fuzz/fake_resource.js +print("I'm a resource."); + +// Original: differential_fuzz/fake_resource.js +print("I'm a resource."); + +// Original: differential_fuzz/fake_resource.js +print("I'm a resource."); + +// Original: differential_fuzz/fake_resource.js +print("I'm a resource."); + +// Original: differential_fuzz/fake_resource.js +print("I'm a resource."); + +/* DifferentialFuzzMutator: Print variables and exceptions from section */ +try { + print("Hash: " + __hash); + print("Caught: " + __caught); +} catch (e) {} + +print("v8-foozzie source: differential_fuzz/input1.js"); + +// Original: differential_fuzz/input1.js +try { + var __v_0 = 0; +} catch (e) {} + +try { + /* DifferentialFuzzMutator: Pretty printing */ + __prettyPrintExtra(__v_0); +} catch (e) {} + +/* DifferentialFuzzMutator: Print variables and exceptions from section */ +try { + print("Hash: " + __hash); + print("Caught: " + __caught); + + __prettyPrint(__v_0); +} catch (e) {} + +print("v8-foozzie source: differential_fuzz/input2.js"); + +// Original: differential_fuzz/input2.js +let __v_1 = 1; + +/* DifferentialFuzzMutator: Print variables and exceptions from section */ +try { + print("Hash: " + __hash); + print("Caught: " + __caught); + + __prettyPrint(__v_0); + + __prettyPrint(__v_1); +} catch (e) {} diff --git a/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/exceptions.js b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/exceptions.js new file mode 100644 index 0000000000..2ff571f716 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/exceptions.js @@ -0,0 +1,8 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +try { + let __v_0 = boom; +} catch (e) {} diff --git a/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/exceptions_expected.js b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/exceptions_expected.js new file mode 100644 index 0000000000..b5498a52f1 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/exceptions_expected.js @@ -0,0 +1,16 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: differential_fuzz/exceptions.js +try { + let __v_0 = boom; +} catch (e) { + __caught++; +} + +/* DifferentialFuzzMutator: Print variables and exceptions from section */ +try { + print("Hash: " + __hash); + print("Caught: " + __caught); +} catch (e) {} diff --git a/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/fake_resource.js b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/fake_resource.js new file mode 100644 index 0000000000..c417acd365 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/fake_resource.js @@ -0,0 +1,7 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +// This file represents anything loaded from the resources directory. +print("I'm a resource."); diff --git a/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/input1.js b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/input1.js new file mode 100644 index 0000000000..cbf2e853c0 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/input1.js @@ -0,0 +1,9 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --flag1 --flag2 +// Flags: --flag3 + +var a = 0; +print(a); diff --git a/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/input2.js b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/input2.js new file mode 100644 index 0000000000..e22b1cc106 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/input2.js @@ -0,0 +1,7 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --flag4 + +let b = 1; diff --git a/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/mutations.js b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/mutations.js new file mode 100644 index 0000000000..606527bb2a --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/mutations.js @@ -0,0 +1,26 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +// Print after declaration. +var __v_0 = [1, 2, 3]; + +// Don't print after declarations or assigments in loops. +for (let __v_1 = 0; __v_1 < 3; __v_1 += 1) { + + // Print after multiple declarations. + let __v_2, __v_3 = 0; + + // Print after assigning to member. + __v_0.foo = undefined; + + // Replace with deep printing. + print(0); + + // Print exception. + try { + // Print after assignment. + __v_1 += 1; + } catch(e) {} +} diff --git a/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/mutations_expected.js b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/mutations_expected.js new file mode 100644 index 0000000000..5b7ff6f413 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/mutations_expected.js @@ -0,0 +1,44 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: differential_fuzz/mutations.js +var __v_0 = [1, 2, 3]; + +/* DifferentialFuzzMutator: Extra variable printing */ +__prettyPrintExtra(__v_0); + +for (let __v_1 = 0; __v_1 < 3; __v_1 += 1) { + let __v_2, + __v_3 = 0; + + /* DifferentialFuzzMutator: Extra variable printing */ + __prettyPrintExtra(__v_2); + + __prettyPrintExtra(__v_3); + + __v_0.foo = undefined; + + /* DifferentialFuzzMutator: Extra variable printing */ + __prettyPrintExtra(__v_0); + + /* DifferentialFuzzMutator: Pretty printing */ + __prettyPrintExtra(0); + + try { + __v_1 += 1; + + /* DifferentialFuzzMutator: Extra variable printing */ + __prettyPrintExtra(__v_1); + } catch (e) { + __prettyPrintExtra(e); + } +} + +/* DifferentialFuzzMutator: Print variables and exceptions from section */ +try { + print("Hash: " + __hash); + print("Caught: " + __caught); + + __prettyPrint(__v_0); +} catch (e) {} diff --git a/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/suppressions.js b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/suppressions.js new file mode 100644 index 0000000000..59be2c3cbb --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/suppressions.js @@ -0,0 +1,15 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// These statements might come from a CrashTest. +print("v8-foozzie source: some/file/name"); +print('v8-foozzie source: some/file/name'); + +function foo(__v_0) { + // This is an unsupported language feature. + return 1 in foo.arguments; +} + +// This leads to precision differences in optimized code. +print(192 ** -0.5); diff --git a/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/suppressions_expected.js b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/suppressions_expected.js new file mode 100644 index 0000000000..bf78cae1b5 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/suppressions_expected.js @@ -0,0 +1,21 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: differential_fuzz/suppressions.js +print( +/* DifferentialFuzzSuppressions: Replaced magic string */ +"v***************e: some/file/name"); +print( +/* DifferentialFuzzSuppressions: Replaced magic string */ +"v***************e: some/file/name"); + +function foo(__v_0) { + return 1 in + /* DifferentialFuzzSuppressions: Replaced .arguments */ + __v_0; +} + +print( +/* DifferentialFuzzSuppressions: Replaced ** */ +192 + -0.5); diff --git a/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/v8_fuzz_experiments.json b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/v8_fuzz_experiments.json new file mode 100644 index 0000000000..9f83ff503b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/v8_fuzz_experiments.json @@ -0,0 +1,3 @@ +[ + [100, "ignition", "ignition_turbo", "d8"] +] diff --git a/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/v8_fuzz_flags.json b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/v8_fuzz_flags.json new file mode 100644 index 0000000000..cbb498c987 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/differential_fuzz/v8_fuzz_flags.json @@ -0,0 +1,3 @@ +[ + [1.0, "--foo1 --foo2"] +] diff --git a/compiler/forget/packages/js-fuzzer/test_data/fake_db/index.json b/compiler/forget/packages/js-fuzzer/test_data/fake_db/index.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/fake_db/index.json @@ -0,0 +1 @@ +{} diff --git a/compiler/forget/packages/js-fuzzer/test_data/mjsunit/mjsunit.js b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/mjsunit.js new file mode 100644 index 0000000000..1506cd3912 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/mjsunit.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var fakeMjsunit = 'fake'; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load.js b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load.js new file mode 100644 index 0000000000..342c9d87a3 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load.js @@ -0,0 +1,7 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var testLoad = 'test_load'; +load('test_data/mjsunit/test_load_1.js'); +load('test_load_0.js'); diff --git a/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_0.js b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_0.js new file mode 100644 index 0000000000..d0e66e4a9f --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_0.js @@ -0,0 +1,8 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +load('test_data/mjsunit/test_load_1.js'); +load('test_load_2.js'); +load('test_load_3.js'); +var testLoad0 = 'test_load_0'; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_1.js b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_1.js new file mode 100644 index 0000000000..03c9166975 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_1.js @@ -0,0 +1,6 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +load('test_load_2.js'); +var testLoad1 = 'test_load_1'; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_2.js b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_2.js new file mode 100644 index 0000000000..47ef1c7933 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_2.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var testLoad2 = 'test_load_2'; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_3.js b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_3.js new file mode 100644 index 0000000000..c6a15467be --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_3.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var testLoad3 = 'test_load_3'; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_expected.js new file mode 100644 index 0000000000..e545df8ead --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_expected.js @@ -0,0 +1,21 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: mjsunit/mjsunit.js +var fakeMjsunit = 'fake'; + +// Original: mjsunit/test_load_2.js +var testLoad2 = 'test_load_2'; + +// Original: mjsunit/test_load_1.js +var testLoad1 = 'test_load_1'; + +// Original: mjsunit/test_load_3.js +var testLoad3 = 'test_load_3'; + +// Original: mjsunit/test_load_0.js +var testLoad0 = 'test_load_0'; + +// Original: mjsunit/test_load.js +var testLoad = 'test_load'; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_self.js b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_self.js new file mode 100644 index 0000000000..31a9f4c507 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_self.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +load("test_load_self.js"); diff --git a/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_self_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_self_expected.js new file mode 100644 index 0000000000..495ebf9d0b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mjsunit/test_load_self_expected.js @@ -0,0 +1,6 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: mjsunit/mjsunit.js +var fakeMjsunit = 'fake'; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mjsunit_softskipped/object-literal.js b/compiler/forget/packages/js-fuzzer/test_data/mjsunit_softskipped/object-literal.js new file mode 100644 index 0000000000..65ffc18c50 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mjsunit_softskipped/object-literal.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Fake file diff --git a/compiler/forget/packages/js-fuzzer/test_data/mjsunit_softskipped/permitted.js b/compiler/forget/packages/js-fuzzer/test_data/mjsunit_softskipped/permitted.js new file mode 100644 index 0000000000..65ffc18c50 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mjsunit_softskipped/permitted.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Fake file diff --git a/compiler/forget/packages/js-fuzzer/test_data/mjsunit_softskipped/regress/binaryen-123.js b/compiler/forget/packages/js-fuzzer/test_data/mjsunit_softskipped/regress/binaryen-123.js new file mode 100644 index 0000000000..65ffc18c50 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mjsunit_softskipped/regress/binaryen-123.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Fake file diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_arrays.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_arrays.js new file mode 100644 index 0000000000..8d907ae8d9 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_arrays.js @@ -0,0 +1,34 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[]; +[]; +[]; +[]; +[]; +[]; +[]; +[]; +[]; +[]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; +[1, 2, 3]; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_arrays_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_arrays_expected.js new file mode 100644 index 0000000000..ce53377029 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_arrays_expected.js @@ -0,0 +1,109 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: mutate_arrays.js + +/* ArrayMutator: Remove elements */ + +/* ArrayMutator: Insert a hole */ +[]; +[]; + +/* ArrayMutator: Shuffle array */ +[]; + +/* ArrayMutator: Insert a random value */ +[""]; + +/* ArrayMutator: Insert a random value (replaced) */ +[""]; + +/* ArrayMutator: Insert a hole (replaced) */ +[,]; +[]; + +/* ArrayMutator: Insert a hole (replaced) */ +[,]; + +/* ArrayMutator: Remove elements */ +[]; + +/* ArrayMutator: Remove elements */ +[]; + +/* ArrayMutator: Duplicate an element */ +[1, 1, 2, 3]; + +/* ArrayMutator: Insert a random value (replaced) */ +[1, "", 3]; + +/* ArrayMutator: Remove elements */ +[]; + +/* ArrayMutator: Duplicate an element */ +[1, 2, 3, 2]; + +/* ArrayMutator: Remove elements */ +[3]; + +/* ArrayMutator: Duplicate an element (replaced) */ +[1, 2, 3]; + +/* ArrayMutator: Insert a hole (replaced) */ + +/* ArrayMutator: Duplicate an element (replaced) */ +[1, 2,,]; + +/* ArrayMutator: Remove elements */ +[1, 2]; + +/* ArrayMutator: Insert a hole (replaced) */ + +/* ArrayMutator: Duplicate an element */ +[1, 1, 2,,]; + +/* ArrayMutator: Shuffle array */ +[2, 1, 3]; + +/* ArrayMutator: Remove elements */ + +/* ArrayMutator: Remove elements */ +[3]; + +/* ArrayMutator: Duplicate an element (replaced) */ +[1, 2, 1]; + +/* ArrayMutator: Duplicate an element (replaced) */ + +/* ArrayMutator: Duplicate an element (replaced) */ +[1, 2, 2]; + +/* ArrayMutator: Insert a random value */ +[1, 2, 3, ""]; + +/* ArrayMutator: Duplicate an element */ +[1, 2, 3, 3]; + +/* ArrayMutator: Remove elements */ + +/* ArrayMutator: Duplicate an element */ +[1, 2]; + +/* ArrayMutator: Insert a random value (replaced) */ + +/* ArrayMutator: Duplicate an element (replaced) */ +[1, 2, ""]; + +/* ArrayMutator: Insert a random value (replaced) */ + +/* ArrayMutator: Insert a random value (replaced) */ +["", 2, 3]; + +/* ArrayMutator: Duplicate an element */ + +/* ArrayMutator: Remove elements */ +[1, 1, 3]; + +/* ArrayMutator: Remove elements */ +[1, 2]; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_expressions.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_expressions.js new file mode 100644 index 0000000000..0ffae115ab --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_expressions.js @@ -0,0 +1,8 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +1; +let foo = undefined; +2; +3; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_expressions_current_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_expressions_current_expected.js new file mode 100644 index 0000000000..f3d4e49a1f --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_expressions_current_expected.js @@ -0,0 +1,15 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: mutate_expressions.js +1; + +/* ExpressionMutator: Cloned sibling */ +2; +let foo = undefined; + +/* ExpressionMutator: Cloned sibling */ +3; +2; +3; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_expressions_previous_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_expressions_previous_expected.js new file mode 100644 index 0000000000..1473183034 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_expressions_previous_expected.js @@ -0,0 +1,12 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: mutate_expressions.js +1; +let foo = undefined; +2; +3; + +/* ExpressionMutator: Cloned sibling */ +2; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call.js new file mode 100644 index 0000000000..2da557df3d --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call.js @@ -0,0 +1,7 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +__f_0(1); +a = __f_0(1); +foo(1, __f_0()); diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_baseline_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_baseline_expected.js new file mode 100644 index 0000000000..1c02041cc5 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_baseline_expected.js @@ -0,0 +1,16 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* FunctionCallMutator: Compiling baseline __f_0 */ +%CompileBaseline(__f_0); + +// Original: mutate_function_call.js +__f_0(1); + +a = ( +/* FunctionCallMutator: Compiling baseline __f_0 */ +%CompileBaseline(__f_0), __f_0(1)); +foo(1, ( +/* FunctionCallMutator: Compiling baseline __f_0 */ +%CompileBaseline(__f_0), __f_0())); diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_deopt_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_deopt_expected.js new file mode 100644 index 0000000000..085322c612 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_deopt_expected.js @@ -0,0 +1,19 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var _temp, _temp2; + +// Original: mutate_function_call.js + +/* FunctionCallMutator: Deoptimizing __f_0 */ +__f_0(1); + +%DeoptimizeFunction(__f_0); + +a = ( +/* FunctionCallMutator: Deoptimizing __f_0 */ +_temp = __f_0(1), %DeoptimizeFunction(__f_0), _temp); +foo(1, ( +/* FunctionCallMutator: Deoptimizing __f_0 */ +_temp2 = __f_0(), %DeoptimizeFunction(__f_0), _temp2)); diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_expected.js new file mode 100644 index 0000000000..f4fe3cb9e8 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_expected.js @@ -0,0 +1,23 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +%PrepareFunctionForOptimization(__f_0); + +__f_0(1); + +__f_0(1); + +%OptimizeFunctionOnNextCall(__f_0); + +// Original: mutate_function_call.js + +/* FunctionCallMutator: Optimizing __f_0 */ +__f_0(1); + +a = ( +/* FunctionCallMutator: Optimizing __f_0 */ +%PrepareFunctionForOptimization(__f_0), __f_0(1), __f_0(1), %OptimizeFunctionOnNextCall(__f_0), __f_0(1)); +foo(1, ( +/* FunctionCallMutator: Optimizing __f_0 */ +%PrepareFunctionForOptimization(__f_0), __f_0(), __f_0(), %OptimizeFunctionOnNextCall(__f_0), __f_0())); diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_maglev_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_maglev_expected.js new file mode 100644 index 0000000000..f6b8823635 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_function_call_maglev_expected.js @@ -0,0 +1,23 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +%PrepareFunctionForOptimization(__f_0); + +__f_0(1); + +__f_0(1); + +%OptimizeMaglevOnNextCall(__f_0); + +// Original: mutate_function_call.js + +/* FunctionCallMutator: Optimizing __f_0 */ +__f_0(1); + +a = ( +/* FunctionCallMutator: Optimizing __f_0 */ +%PrepareFunctionForOptimization(__f_0), __f_0(1), __f_0(1), %OptimizeMaglevOnNextCall(__f_0), __f_0(1)); +foo(1, ( +/* FunctionCallMutator: Optimizing __f_0 */ +%PrepareFunctionForOptimization(__f_0), __f_0(), __f_0(), %OptimizeMaglevOnNextCall(__f_0), __f_0())); diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_numbers.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_numbers.js new file mode 100644 index 0000000000..44bfc4a262 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_numbers.js @@ -0,0 +1,22 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +function foo() { + let a = 123; + for (let i = 0; i < 456; i++) { + a += 1; + } + + let b = 0; + while (b < 10) { + b += 2; + } + + a += 1; +} + +var a = {0: "", 1: "", get 1(){}}; +var b = -10; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_numbers_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_numbers_expected.js new file mode 100644 index 0000000000..cac1c6004b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_numbers_expected.js @@ -0,0 +1,46 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +// Original: mutate_numbers.js +function foo() { + let a = + /* NumberMutator: Replaced 123 with -5 */ + -5; + + for (let i = 0; i < 456; i++) { + a += + /* NumberMutator: Replaced 1 with -4 */ + -4; + } + + let b = + /* NumberMutator: Replaced 0 with -3 */ + -3; + + while (b < 10) { + b += 2; + } + + a += + /* NumberMutator: Replaced 1 with -5 */ + -5; +} + +var a = { + /* NumberMutator: Replaced 0 with 4 */ + 4: "", + + /* NumberMutator: Replaced 1 with 3 */ + 3: "", + + get + /* NumberMutator: Replaced 1 with 5 */ + 5() {} + +}; +var b = +/* NumberMutator: Replaced -10 with -4 */ +-4; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_objects.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_objects.js new file mode 100644 index 0000000000..3f70329c44 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_objects.js @@ -0,0 +1,41 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Empty objects are not manipulated. +a = {}; +a = {}; +a = {}; +a = {}; +a = {}; +a = {}; + +// Small objects only get some mutations. +a = {1: 0}; +a = {a: 0}; +a = {"s": 0}; +a = {1: 0}; +a = {a: 0}; +a = {"s": 0}; + +// Larger objects get all mutations. +a = {1: "a", 2: "b", 3: "c"}; +a = {1: "a", 2: "b", 3: "c"}; +a = {1: "a", 2: "b", 3: "c"}; +a = {1: "a", 2: "b", 3: "c"}; +a = {1: "a", 2: "b", 3: "c"}; +a = {1: "a", 2: "b", 3: "c"}; +a = {1: "a", 2: "b", 3: "c"}; +a = {1: "a", 2: "b", 3: "c"}; +a = {1: "a", 2: "b", 3: "c"}; +a = {1: "a", 2: "b", 3: "c"}; + +// Getters and setters are ignored. +a = {get bar() { return 0 }, 1: 0, set bar(t) {}}; +a = {get bar() { return 0 }, 1: 0, set bar(t) {}}; +a = {get bar() { return 0 }, 1: 0, set bar(t) {}}; + +// Recursive. +a = {1: {4: "4", 5: "5", 6: "6"}, 2: {3: "3"}}; +a = {1: {4: "4", 5: "5", 6: "6"}, 2: {3: "3"}}; +a = {1: {4: "4", 5: "5", 6: "6"}, 2: {3: "3"}}; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_objects_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_objects_expected.js new file mode 100644 index 0000000000..6c3e3cf6d7 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_objects_expected.js @@ -0,0 +1,182 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: mutate_objects.js +a = {}; +a = {}; +a = {}; +a = {}; +a = {}; +a = {}; +a = +/* ObjectMutator: Insert a random value */ +{ + 1: "" +}; +a = { + a: 0 +}; +a = +/* ObjectMutator: Insert a random value */ +{ + "s": "" +}; +a = +/* ObjectMutator: Stringify a property key */ +{ + "1": 0 +}; +a = +/* ObjectMutator: Remove a property */ +{}; +a = { + "s": 0 +}; +a = +/* ObjectMutator: Swap properties */ +{ + 1: "c", + 2: "b", + 3: "a" +}; +a = +/* ObjectMutator: Remove a property */ +{ + 2: "b", + 3: "c" +}; +a = +/* ObjectMutator: Insert a random value */ +{ + 1: "a", + 2: "", + 3: "c" +}; +a = +/* ObjectMutator: Swap properties */ +{ + 1: "b", + 2: "a", + 3: "c" +}; +a = +/* ObjectMutator: Swap properties */ +{ + 1: "c", + 2: "b", + 3: "a" +}; +a = +/* ObjectMutator: Stringify a property key */ +{ + "1": "a", + 2: "b", + 3: "c" +}; +a = +/* ObjectMutator: Remove a property */ +{ + 2: "b", + 3: "c" +}; +a = +/* ObjectMutator: Swap properties */ +{ + 1: "b", + 2: "a", + 3: "c" +}; +a = +/* ObjectMutator: Duplicate a property value */ +{ + 1: "c", + 2: "b", + 3: "c" +}; +a = +/* ObjectMutator: Duplicate a property value */ +{ + 1: "a", + 2: "b", + 3: "b" +}; +a = { + get bar() { + return 0; + }, + + 1: 0, + + set bar(t) {} + +}; +a = +/* ObjectMutator: Insert a random value */ +{ + get bar() { + return 0; + }, + + 1: "", + + set bar(t) {} + +}; +a = +/* ObjectMutator: Remove a property */ +{ + get bar() { + return 0; + }, + + set bar(t) {} + +}; +a = +/* ObjectMutator: Duplicate a property value */ +{ + 1: + /* ObjectMutator: Remove a property */ + {}, + 2: + /* ObjectMutator: Stringify a property key */ + { + "3": "3" + } +}; +a = +/* ObjectMutator: Duplicate a property value */ +{ + 1: + /* ObjectMutator: Swap properties */ + { + 4: "4", + 5: "6", + 6: "5" + }, + 2: + /* ObjectMutator: Remove a property */ + { + 5: "5", + 6: "6" + } +}; +a = +/* ObjectMutator: Duplicate a property value */ +{ + 1: + /* ObjectMutator: Swap properties */ + { + 4: "6", + 5: "5", + 6: "4" + }, + 2: + /* ObjectMutator: Stringify a property key */ + { + 4: "4", + 5: "5", + "6": "6" + } +}; diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_var_or_obj.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_var_or_obj.js new file mode 100644 index 0000000000..d8a6583eac --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_var_or_obj.js @@ -0,0 +1,10 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +let __v_0 = {}; +Math.pow(1, 2); +Math.pow(1, 2); +Math.pow(1, 2); +Math.pow(1, 2); +Math.pow(1, 2); diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_var_or_obj_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_var_or_obj_expected.js new file mode 100644 index 0000000000..19a4d5fd47 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_var_or_obj_expected.js @@ -0,0 +1,37 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: mutate_var_or_obj.js +let __v_0 = {}; + +/* VariableOrObjectMutator: Random mutation */ +delete __getRandomObject(123)[__getRandomProperty(__getRandomObject(123), 123)], __callGC(); +__getRandomObject(123)[__getRandomProperty(__getRandomObject(123), 123)], __callGC(); +Math.pow(1, 2); + +/* VariableOrObjectMutator: Random mutation */ +__getRandomObject(123)[__getRandomProperty(__getRandomObject(123), 123)] = 0, __callGC(); +Math.pow(1, 2); + +/* VariableOrObjectMutator: Random mutation */ +__v_0 = __getRandomObject(123), __callGC(); +Math.pow(1, 2); + +/* VariableOrObjectMutator: Random mutation */ +if (__getRandomObject(123) != null && typeof __getRandomObject(123) == "object") Object.defineProperty(__getRandomObject(123), __getRandomProperty(__getRandomObject(123), 123), { + value: 0 +}); +Math.pow(1, 2); + +/* VariableOrObjectMutator: Random mutation */ +if (__getRandomObject(123) != null && typeof __getRandomObject(123) == "object") Object.defineProperty(__getRandomObject(123), __getRandomProperty(__getRandomObject(123), 123), { + get: function () { + delete __getRandomObject(123)[__getRandomProperty(__getRandomObject(123), 123)], __callGC(); + return 0; + }, + set: function (value) { + __getRandomObject(123)[__getRandomProperty(__getRandomObject(123), 123)], __callGC(); + } +}); +Math.pow(1, 2); diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_variables.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_variables.js new file mode 100644 index 0000000000..93fed1b53f --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_variables.js @@ -0,0 +1,30 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function __f_0(__v_10, __v_11) { + let __v_4 = 4; + let __v_5 = 5; + let __v_6 = 6; + let __v_7 = 7; + console.log(__v_4); + console.log(__v_5); + console.log(__v_6); + console.log(__v_7); + for (let __v_9 = 0; __v_9 < 10; __v_9++) { + console.log(__v_4); + } + let __v_8 = 0; + while (__v_8 < 10) { + __v_8++; + } +} +let __v_0 = 1; +let __v_1 = 2; +let __v_2 = 3; +let __v_3 = 4; +console.log(__v_0); +console.log(__v_1); +console.log(__v_2); +console.log(__v_3); +__f_0(); diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutate_variables_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mutate_variables_expected.js new file mode 100644 index 0000000000..920e740447 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutate_variables_expected.js @@ -0,0 +1,54 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: mutate_variables.js +function __f_0(__v_10, __v_11) { + let __v_4 = 4; + let __v_5 = 5; + let __v_6 = 6; + let __v_7 = 7; + console.log( + /* VariableMutator: Replaced __v_4 with REPLACED */ + REPLACED); + console.log( + /* VariableMutator: Replaced __v_5 with REPLACED */ + REPLACED); + console.log( + /* VariableMutator: Replaced __v_6 with REPLACED */ + REPLACED); + console.log( + /* VariableMutator: Replaced __v_7 with REPLACED */ + REPLACED); + + for (let __v_9 = 0; __v_9 < 10; __v_9++) { + console.log( + /* VariableMutator: Replaced __v_4 with REPLACED */ + REPLACED); + } + + let __v_8 = 0; + + while (__v_8 < 10) { + __v_8++; + } +} + +let __v_0 = 1; +let __v_1 = 2; +let __v_2 = 3; +let __v_3 = 4; +console.log( +/* VariableMutator: Replaced __v_0 with REPLACED */ +REPLACED); +console.log( +/* VariableMutator: Replaced __v_1 with REPLACED */ +REPLACED); +console.log( +/* VariableMutator: Replaced __v_2 with REPLACED */ +REPLACED); +console.log( +/* VariableMutator: Replaced __v_3 with REPLACED */ +REPLACED); + +__f_0(); diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutation_order/input.js b/compiler/forget/packages/js-fuzzer/test_data/mutation_order/input.js new file mode 100644 index 0000000000..b58810985b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutation_order/input.js @@ -0,0 +1,23 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var i = 1; +var j = 'str'; +var k = undefined; +var l = {0: 1}; + +function foo(a, b) { + return a + b; +} + +foo(i, 3); + +function bar(a) { + return foo(a, a); +} + +foo('foo', j); +bar(2, foo(i, j)); +foo(i, j); +bar(j, 3); diff --git a/compiler/forget/packages/js-fuzzer/test_data/mutation_order/output_expected.js b/compiler/forget/packages/js-fuzzer/test_data/mutation_order/output_expected.js new file mode 100644 index 0000000000..ddad108080 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/mutation_order/output_expected.js @@ -0,0 +1,119 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Script mutator: using shuffled mutators +// Script mutator: extra ArrayMutator +// Script mutator: extra VariableMutator +// Script mutator: extra ExpressionMutator +// Script mutator: extra ArrayMutator + +// Original: mutation_order/input.js +try { + var __v_0 = + /* NumberMutator: Replaced 1 with -10 */ + -10; +} catch (e) {} + +try { + var __v_1 = 'str'; +} catch (e) {} + +try { + var __v_2 = undefined; +} catch (e) {} + +try { + var __v_3 = { + /* NumberMutator: Replaced 0 with 8 */ + 8: + /* NumberMutator: Replaced 1 with 3 */ + 3 + }; +} catch (e) {} + +function __f_0(__v_4, __v_5) { + return __v_4 + __v_5; +} + +try { + %PrepareFunctionForOptimization(__f_0); +} catch (e) {} + +try { + __f_0(__v_0, + /* NumberMutator: Replaced 3 with 5 */ + 5); +} catch (e) {} + +try { + __f_0(__v_0, + /* NumberMutator: Replaced 3 with NaN */ + NaN); +} catch (e) {} + +try { + %OptimizeFunctionOnNextCall(__f_0); +} catch (e) {} + +try { + /* FunctionCallMutator: Optimizing __f_0 */ + __f_0(__v_0, + /* NumberMutator: Replaced 3 with 2 */ + 2); +} catch (e) {} + +function __f_1(__v_6) { + return ( + /* FunctionCallMutator: Replaced __f_0 with __f_0 */ + __f_0(__v_6, __v_6) + ); +} + +try { + %PrepareFunctionForOptimization(__f_0); +} catch (e) {} + +try { + __f_0('foo', __v_1); +} catch (e) {} + +try { + __f_0('foo', __v_1); +} catch (e) {} + +try { + %OptimizeFunctionOnNextCall(__f_0); +} catch (e) {} + +try { + /* FunctionCallMutator: Optimizing __f_0 */ + __f_0('foo', __v_1); +} catch (e) {} + +try { + /* FunctionCallMutator: Compiling baseline __f_1 */ + %CompileBaseline(__f_1); +} catch (e) {} + +try { + __f_1( + /* NumberMutator: Replaced 2 with -10 */ + -10, __f_0(__v_0, __v_1)); +} catch (e) {} + +try { + /* FunctionCallMutator: Deoptimizing __f_0 */ + __f_0(__v_0, __v_1); +} catch (e) {} + +try { + %DeoptimizeFunction(__f_0); +} catch (e) {} + +try { + /* FunctionCallMutator: Replaced __f_1 with __f_1 */ + __f_1(__v_1, + /* NumberMutator: Replaced 3 with 16 */ + 16); +} catch (e) {} diff --git a/compiler/forget/packages/js-fuzzer/test_data/normalize.js b/compiler/forget/packages/js-fuzzer/test_data/normalize.js new file mode 100644 index 0000000000..ddf5b9f9fe --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/normalize.js @@ -0,0 +1,23 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +class Class { + constructor() { + this.abc = 789; + this.selfRef = Class; + } +} + +function foo() { + let a = 123; + console.log(a); +} + +foo(); +let a = 456; +console.log(a); +let b = new Class(); +console.log(b.abc); diff --git a/compiler/forget/packages/js-fuzzer/test_data/normalize_expected_0.js b/compiler/forget/packages/js-fuzzer/test_data/normalize_expected_0.js new file mode 100644 index 0000000000..5f832f7065 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/normalize_expected_0.js @@ -0,0 +1,28 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +// Original: normalize.js +class __c_0 { + constructor() { + this.abc = 789; + this.selfRef = __c_0; + } + +} + +function __f_0() { + let __v_2 = 123; + console.log(__v_2); +} + +__f_0(); + +let __v_0 = 456; +console.log(__v_0); + +let __v_1 = new __c_0(); + +console.log(__v_1.abc); diff --git a/compiler/forget/packages/js-fuzzer/test_data/normalize_expected_1.js b/compiler/forget/packages/js-fuzzer/test_data/normalize_expected_1.js new file mode 100644 index 0000000000..672f29e5f5 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/normalize_expected_1.js @@ -0,0 +1,28 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +// Original: normalize.js +class __c_1 { + constructor() { + this.abc = 789; + this.selfRef = __c_1; + } + +} + +function __f_1() { + let __v_5 = 123; + console.log(__v_5); +} + +__f_1(); + +let __v_3 = 456; +console.log(__v_3); + +let __v_4 = new __c_1(); + +console.log(__v_4.abc); diff --git a/compiler/forget/packages/js-fuzzer/test_data/regress/numbers/db/index.json b/compiler/forget/packages/js-fuzzer/test_data/regress/numbers/db/index.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/regress/numbers/db/index.json @@ -0,0 +1 @@ +{} diff --git a/compiler/forget/packages/js-fuzzer/test_data/regress/numbers/input_indices.js b/compiler/forget/packages/js-fuzzer/test_data/regress/numbers/input_indices.js new file mode 100644 index 0000000000..685453f5b7 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/regress/numbers/input_indices.js @@ -0,0 +1,11 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +let a = { + 0: "", + 1: "", + 2: "", + 3: "", + 4: "", +}; diff --git a/compiler/forget/packages/js-fuzzer/test_data/regress/numbers/input_negative.js b/compiler/forget/packages/js-fuzzer/test_data/regress/numbers/input_negative.js new file mode 100644 index 0000000000..d6747bb006 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/regress/numbers/input_negative.js @@ -0,0 +1,8 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +foo(-1); +foo(-1); +foo(-1); +foo(-1); diff --git a/compiler/forget/packages/js-fuzzer/test_data/regress/spidermonkey/db/index.json b/compiler/forget/packages/js-fuzzer/test_data/regress/spidermonkey/db/index.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/regress/spidermonkey/db/index.json @@ -0,0 +1 @@ +{} diff --git a/compiler/forget/packages/js-fuzzer/test_data/regress/spidermonkey/input.js b/compiler/forget/packages/js-fuzzer/test_data/regress/spidermonkey/input.js new file mode 100644 index 0000000000..fc34ae5cbd --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/regress/spidermonkey/input.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +print("Won't see this."); diff --git a/compiler/forget/packages/js-fuzzer/test_data/regress/spidermonkey/shell.js b/compiler/forget/packages/js-fuzzer/test_data/regress/spidermonkey/shell.js new file mode 100644 index 0000000000..4967ffbdfa --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/regress/spidermonkey/shell.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +loadRelativeToScript('PatternAsserts.js'); diff --git a/compiler/forget/packages/js-fuzzer/test_data/regress/strict/db/index.json b/compiler/forget/packages/js-fuzzer/test_data/regress/strict/db/index.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/regress/strict/db/index.json @@ -0,0 +1 @@ +{} diff --git a/compiler/forget/packages/js-fuzzer/test_data/regress/strict/input_delete.js b/compiler/forget/packages/js-fuzzer/test_data/regress/strict/input_delete.js new file mode 100644 index 0000000000..154bb60828 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/regress/strict/input_delete.js @@ -0,0 +1,6 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var x; +delete x; diff --git a/compiler/forget/packages/js-fuzzer/test_data/regress/strict/input_strict.js b/compiler/forget/packages/js-fuzzer/test_data/regress/strict/input_strict.js new file mode 100644 index 0000000000..779fa08fbf --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/regress/strict/input_strict.js @@ -0,0 +1,7 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +"use strict"; + +print("Hello"); diff --git a/compiler/forget/packages/js-fuzzer/test_data/regress/strict/input_with.js b/compiler/forget/packages/js-fuzzer/test_data/regress/strict/input_with.js new file mode 100644 index 0000000000..abb0215953 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/regress/strict/input_with.js @@ -0,0 +1,7 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +with (Math) { + print(PI); +} diff --git a/compiler/forget/packages/js-fuzzer/test_data/simple_test.js b/compiler/forget/packages/js-fuzzer/test_data/simple_test.js new file mode 100644 index 0000000000..bb53b404f6 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/simple_test.js @@ -0,0 +1,87 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Test comment. +// Flags: --gc-interval = 1 +var abs = Math.abs; +var v1 = 5, v2; var v3; +if (v1) { + var v4 = 3; + for (var v5 = 0; v5 < 4; v5++) { + console.log('Value of v5: ' + + v5); + } +} +let v6 = 3; +const v7 = 5 + \u{0076}6; +v1 = {['p' + v6]: ''}; +v1 = `test\` +value is ${ v6 + v7 }` + '\0\400\377' +v1 = (v8=2, {v9 = eval('v8')},) => { return v8 + v9 + 4; }; +v1 = () => 4 + 5; +v1 = v10 => { return v10 + 4; } +v1 = async v11 => v11 + 4; +v12 = [0, 1, 2,]; +v13 = [3, 4, 5]; +v14 = [...v12, ...v13]; +v15 = ([v16, v17] = [1, 2], {v31: v18} = {v31: v16 + v17}) => v16 + v17 + v18; +v16 = 170%16/16 + 2**32; +v17 = 0o1 + 0O1 + 01 + 0b011 + 0B011; +for (var v18 of [1, 2, 3]) console.log(v18); +function f1(v19,) {} +f1(); +%OptimizeFunctionOnNextCall(f1); +function f2() { + var v20 = 5; + return v20 + 6; +} +(async function f3() { + var v21 = await 1; + console.log(v21); +})(); +function* f4(v22=2, ...v23) { + yield* [1, 2, 3]; +} +function* f5() { (yield 3) + (yield); } +{ function f6() { } } +v23 = { v6, [v6]: 3, f7() { }, get f8 () { }, *f9 () { }, async f10 () { } } +var [v24, v25, ...v26] = [10, 20], {v27, v28} = {v27: 10, v28: 20}; +class c1 { + f11(v29) { + return v29 + 1; + } + static* f12() { + yield 'a' + super.f12(); + } + constructor(v30) { + console.log(new.target.name); + } + [0]() { } +} +class c2 extends c1 { } +do ; while(0); +v16 **= 4; +for (const v32 = 1; v32 < 1;); +for (let v33 = 1; v33 < 5; v33++); +for (var v34 = 1; v34 < 5; v34++); +for (const {v35 = 0, v36 = 3} = {}; v36 < 1;); +for (let {v37 = 0, v38 = 3} = {}; v38 != 0; v38--); +for (var {v39 = 0, v40 = 3} = {}; v40 != 0; v40--); +for (const v41 of [1, 2, 3]); +for (let v42 of [1, 2, 3]); +for (var v43 of [1, 2, 3]); +for (const v44 in [1, 2, 3]); +for (let v45 in [1, 2, 3]); +for (var v46 in [1, 2, 3]); +label: function f13() { } + +var a = function b() { + b(); +}; + +var c = class C { + constructor() { + console.log(C.name); + } +}; diff --git a/compiler/forget/packages/js-fuzzer/test_data/simple_test_expected.js b/compiler/forget/packages/js-fuzzer/test_data/simple_test_expected.js new file mode 100644 index 0000000000..0405fac09b --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/simple_test_expected.js @@ -0,0 +1,177 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: simple_test.js +var __v_0 = Math.abs; + +var __v_1 = 5, + __v_2; + +var __v_3; + +if (__v_1) { + var __v_4 = 3; + + for (var __v_5 = 0; __v_5 < 4; __v_5++) { + console.log('Value of v5: ' + __v_5); + } +} + +let __v_6 = 3; + +const __v_7 = 5 + __v_6; + +__v_1 = { + ['p' + __v_6]: '' +}; +__v_1 = `test\` +value is ${__v_6 + __v_7}` + '\0\400\377'; + +__v_1 = (__v_21 = 2, { + v9: __v_22 = eval('v8') +}) => { + return __v_21 + __v_22 + 4; +}; + +__v_1 = () => 4 + 5; + +__v_1 = __v_23 => { + return __v_23 + 4; +}; + +__v_1 = async __v_24 => __v_24 + 4; + +__v_25 = [0, 1, 2]; +__v_26 = [3, 4, 5]; +__v_27 = [...__v_25, ...__v_26]; + +__v_28 = ([__v_29, __v_30] = [1, 2], { + v31: __v_31 +} = { + v31: __v_29 + __v_30 +}) => __v_29 + __v_30 + __v_31; + +__v_42 = 170 % 16 / 16 + 2 ** 32; +__v_33 = 0o1 + 0O1 + 01 + 0b011 + 0B011; + +for (var __v_8 of [1, 2, 3]) console.log(__v_8); + +function __f_0(__v_34) {} + +__f_0(); + +%OptimizeFunctionOnNextCall(__f_0); + +function __f_1() { + var __v_35 = 5; + return __v_35 + 6; +} + +(async function __f_5() { + var __v_36 = await 1; + + console.log(__v_36); +})(); + +function* __f_2(__v_37 = 2, ...__v_38) { + yield* [1, 2, 3]; +} + +function* __f_3() { + (yield 3) + (yield); +} + +{ + function __f_6() {} +} +__v_39 = { + v6: __v_6, + [__v_6]: 3, + + f7() {}, + + get f8() {}, + + *f9() {}, + + async f10() {} + +}; +var [__v_9, __v_10, ...__v_11] = [10, 20], + { + v27: __v_12, + v28: __v_13 +} = { + v27: 10, + v28: 20 +}; + +class __c_0 { + f11(__v_40) { + return __v_40 + 1; + } + + static *f12() { + yield 'a' + super.f12(); + } + + constructor(__v_41) { + console.log(new.target.name); + } + + [0]() {} + +} + +class __c_1 extends __c_0 {} + +do ; while (0); + +__v_42 **= 4; + +for (const __v_43 = 1; __v_43 < 1;); + +for (let __v_44 = 1; __v_44 < 5; __v_44++); + +for (var __v_14 = 1; __v_14 < 5; __v_14++); + +for (const { + v35: __v_45 = 0, + v36: __v_46 = 3 +} = {}; __v_46 < 1;); + +for (let { + v37: __v_47 = 0, + v38: __v_48 = 3 +} = {}; __v_48 != 0; __v_48--); + +for (var { + v39: __v_15 = 0, + v40: __v_16 = 3 +} = {}; __v_16 != 0; __v_16--); + +for (const __v_49 of [1, 2, 3]); + +for (let __v_50 of [1, 2, 3]); + +for (var __v_17 of [1, 2, 3]); + +for (const __v_51 in [1, 2, 3]); + +for (let __v_52 in [1, 2, 3]); + +for (var __v_18 in [1, 2, 3]); + +label: function __f_4() {} + +var __v_19 = function __f_7() { + __f_7(); +}; + +var __v_20 = class __c_2 { + constructor() { + console.log(__c_2.name); + } + +}; diff --git a/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/load1.js b/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/load1.js new file mode 100644 index 0000000000..5e3f902ab1 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/load1.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +console.log('load1.js'); diff --git a/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/shell.js b/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/shell.js new file mode 100644 index 0000000000..7f138a53f3 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/shell.js @@ -0,0 +1,7 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +console.log('/shell.js'); +if (!ok) + throw new Error(`assertion failed: ${f} did not throw as expected`); diff --git a/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/load.js b/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/load.js new file mode 100644 index 0000000000..6ae5f80dea --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/load.js @@ -0,0 +1,15 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +load('load1.js'); +loadRelativeToScript('load2.js'); +console.log('load.js'); + +if (!ok) + throw new Error(`Assertion failed: Some text`); + +print("Assertion failed: Some text"); + +// Check that we can load template literals with null cooked value. +check()`\01`; diff --git a/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/load2.js b/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/load2.js new file mode 100644 index 0000000000..a8ee5d296e --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/load2.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +console.log('load2.js'); diff --git a/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/load_expected.js b/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/load_expected.js new file mode 100644 index 0000000000..f45e4aa0d4 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/load_expected.js @@ -0,0 +1,22 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: spidermonkey/shell.js +console.log('/shell.js'); +if (!ok) throw new Error(`*****tion failed: ${f} did not throw as expected`); + +// Original: spidermonkey/test/shell.js +console.log('/test/shell.js'); + +// Original: spidermonkey/load1.js +console.log('load1.js'); + +// Original: spidermonkey/test/load2.js +console.log('load2.js'); + +// Original: spidermonkey/test/load.js +console.log('load.js'); +if (!ok) throw new Error(`*****tion failed: Some text`); +print("*****tion failed: Some text"); +check()`\01`; diff --git a/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/shell.js b/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/shell.js new file mode 100644 index 0000000000..9c0b357679 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/spidermonkey/test/shell.js @@ -0,0 +1,5 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +console.log('/test/shell.js'); diff --git a/compiler/forget/packages/js-fuzzer/test_data/try_catch.js b/compiler/forget/packages/js-fuzzer/test_data/try_catch.js new file mode 100644 index 0000000000..9414a3b968 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/try_catch.js @@ -0,0 +1,41 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function blah() { + try { + var a = 10; + console.log(a); + } catch (e) {} + + label: for (var i = 0; i < 100; i++) { + var b = 0; + while (b < 10) { + console.log(b); + b += 2; + continue label; + } + } +} + +blah(); +blah(); + +(function () {1;1;})(); + +if (true) { + 2;2; +} else { + 3;3; +} + +let a = 0; +switch (a) { + case 1: 1; +} + +with (Math) { + cos(PI); +} + +let module = new WebAssembly.Module(builder.toBuffer()); diff --git a/compiler/forget/packages/js-fuzzer/test_data/try_catch_alternate_expected.js b/compiler/forget/packages/js-fuzzer/test_data/try_catch_alternate_expected.js new file mode 100644 index 0000000000..31fe2837b7 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/try_catch_alternate_expected.js @@ -0,0 +1,51 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* AddTryCatchMutator: Target skip probability 0.9 and toplevel probability 0.9 */ + +// Original: try_catch.js +function blah() { + try { + var a = 10; + console.log(a); + } catch (e) {} + + label: for (var i = 0; i < 100; i++) { + var b = 0; + + while (b < 10) { + console.log(b); + b += 2; + continue label; + } + } +} + +blah(); +blah(); + +(function () { + 1; + 1; +})(); + +if (true) { + 2; + 2; +} else { + 3; + 3; +} + +let a = 0; + +switch (a) { + case 1: + 1; +} + +with (Math) { + cos(PI); +} +let module = new WebAssembly.Module(builder.toBuffer()); diff --git a/compiler/forget/packages/js-fuzzer/test_data/try_catch_expected.js b/compiler/forget/packages/js-fuzzer/test_data/try_catch_expected.js new file mode 100644 index 0000000000..266662b259 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/try_catch_expected.js @@ -0,0 +1,104 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: try_catch.js +function blah() { + try { + try { + var a = 10; + } catch (e) {} + + try { + console.log(a); + } catch (e) {} + } catch (e) {} + + try { + label: for (var i = 0; i < 100; i++) { + try { + var b = 0; + } catch (e) {} + + try { + while (b < 10) { + try { + console.log(b); + } catch (e) {} + + try { + b += 2; + } catch (e) {} + + continue label; + } + } catch (e) {} + } + } catch (e) {} +} + +try { + blah(); +} catch (e) {} + +try { + blah(); +} catch (e) {} + +try { + (function () { + try { + 1; + } catch (e) {} + + try { + 1; + } catch (e) {} + })(); +} catch (e) {} + +try { + if (true) { + try { + 2; + } catch (e) {} + + try { + 2; + } catch (e) {} + } else { + try { + 3; + } catch (e) {} + + try { + 3; + } catch (e) {} + } +} catch (e) {} + +let a = 0; + +try { + switch (a) { + case 1: + try { + 1; + } catch (e) {} + + } +} catch (e) {} + +try { + with (Math) { + try { + cos(PI); + } catch (e) {} + } +} catch (e) {} + +let module = function () { + try { + return new WebAssembly.Module(builder.toBuffer()); + } catch (e) {} +}(); diff --git a/compiler/forget/packages/js-fuzzer/test_data/try_catch_nothing_expected.js b/compiler/forget/packages/js-fuzzer/test_data/try_catch_nothing_expected.js new file mode 100644 index 0000000000..b7b86f1b59 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/try_catch_nothing_expected.js @@ -0,0 +1,49 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: try_catch.js +function blah() { + try { + var a = 10; + console.log(a); + } catch (e) {} + + label: for (var i = 0; i < 100; i++) { + var b = 0; + + while (b < 10) { + console.log(b); + b += 2; + continue label; + } + } +} + +blah(); +blah(); + +(function () { + 1; + 1; +})(); + +if (true) { + 2; + 2; +} else { + 3; + 3; +} + +let a = 0; + +switch (a) { + case 1: + 1; +} + +with (Math) { + cos(PI); +} +let module = new WebAssembly.Module(builder.toBuffer()); diff --git a/compiler/forget/packages/js-fuzzer/test_data/try_catch_toplevel_expected.js b/compiler/forget/packages/js-fuzzer/test_data/try_catch_toplevel_expected.js new file mode 100644 index 0000000000..39ee907a48 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_data/try_catch_toplevel_expected.js @@ -0,0 +1,74 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original: try_catch.js +function blah() { + try { + try { + var a = 10; + } catch (e) {} + + try { + console.log(a); + } catch (e) {} + } catch (e) {} + + try { + label: for (var i = 0; i < 100; i++) { + var b = 0; + + while (b < 10) { + console.log(b); + b += 2; + continue label; + } + } + } catch (e) {} +} + +try { + blah(); +} catch (e) {} + +try { + blah(); +} catch (e) {} + +try { + (function () { + 1; + 1; + })(); +} catch (e) {} + +try { + if (true) { + 2; + 2; + } else { + 3; + 3; + } +} catch (e) {} + +let a = 0; + +try { + switch (a) { + case 1: + 1; + } +} catch (e) {} + +try { + with (Math) { + cos(PI); + } +} catch (e) {} + +let module = function () { + try { + return new WebAssembly.Module(builder.toBuffer()); + } catch (e) {} +}(); diff --git a/compiler/forget/packages/js-fuzzer/test_db.js b/compiler/forget/packages/js-fuzzer/test_db.js new file mode 100644 index 0000000000..35f7956b76 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/test_db.js @@ -0,0 +1,66 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Test all expressions in DB. + */ + +const fs = require('fs'); +const fsPath = require('path'); +const program = require('commander'); +const sinon = require('sinon'); + +const crossOverMutator = require('./mutators/crossover_mutator.js'); +const db = require('./db.js'); +const random = require('./random.js'); +const sourceHelpers = require('./source_helpers.js'); + +const sandbox = sinon.createSandbox(); + +function main() { + program + .version('0.0.1') + .option('-i, --input_dir ', 'DB directory.') + .parse(process.argv); + + if (!program.input_dir) { + console.log('Need to specify DB dir.'); + return; + } + + const mutateDb = new db.MutateDb(program.input_dir); + const mutator = new crossOverMutator.CrossOverMutator( + { MUTATE_CROSSOVER_INSERT: 1.0, testing: true }, mutateDb); + + let nPass = 0; + let nFail = 0; + // Iterate over all statements saved in the DB. + for (const statementPath of mutateDb.index.all) { + const expression = JSON.parse(fs.readFileSync( + fsPath.join(program.input_dir, statementPath)), 'utf-8'); + // Stub out choosing random variables in cross-over mutator. + sandbox.stub(random, 'single').callsFake((a) => { return a[0]; }); + // Ensure we are selecting the statement of the current iteration. + sandbox.stub(mutateDb, 'getRandomStatement').callsFake( + () => { return expression; }); + // Use a source that will try to insert one statement, allowing + // super. + const source = sourceHelpers.loadSource( + __dirname, + 'test_data/cross_over_mutator_class_input.js'); + try { + mutator.mutate(source); + nPass++; + } catch (e) { + console.log('******************************************************') + console.log(expression); + console.log(e.message); + nFail++; + } + sandbox.restore(); + } + console.log(`Result: ${nPass} passed, ${nFail} failed.`) +} + +main(); diff --git a/compiler/forget/packages/js-fuzzer/tools/fuzz_one.py b/compiler/forget/packages/js-fuzzer/tools/fuzz_one.py new file mode 100644 index 0000000000..c3df79ec27 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/tools/fuzz_one.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# Copyright 2020 the V8 project authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +""" +Helper script to execute a single-processed fuzzing session. + +Creates fuzz tests in workdir/output/dir-/fuzz-XXX.js. +Expects the as single parameter. +""" + +import os +import subprocess +import sys +import time + +BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +APP_DIR = os.path.join(BASE_PATH, 'workdir', 'app_dir') +FUZZ_EXE = os.path.join(BASE_PATH, 'workdir', 'fuzzer', 'ochang_js_fuzzer') +INPUT_DIR = os.path.join(BASE_PATH, 'workdir', 'input') +TEST_CASES = os.path.join(BASE_PATH, 'workdir', 'output') + +COUNT = 64 +FUZZ = ('FUZZ_MODE=foozzie APP_NAME=d8 APP_DIR=%s %s -o %%s -n %s -i %s > %%s' + % (APP_DIR, FUZZ_EXE, COUNT, INPUT_DIR)) + +assert(len(sys.argv) > 1) +dir_number = int(sys.argv[1]) +assert(dir_number >= 0) + +path = os.path.join(TEST_CASES, 'dir-%d' % dir_number) +assert not os.path.exists(path), 'Need fresh workdir for fuzzing' +os.makedirs(path) + +start = time.time() +subprocess.check_call( + FUZZ % (path, os.path.join(path, 'out.log')), shell=True) +duration = int(time.time() - start) + +with open(os.path.join(path, 'duration.log'), 'w') as f: + f.write(str(duration)) diff --git a/compiler/forget/packages/js-fuzzer/tools/minimize.py b/compiler/forget/packages/js-fuzzer/tools/minimize.py new file mode 100644 index 0000000000..de4e136860 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/tools/minimize.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# Copyright 2020 the V8 project authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +""" +Helper script to forge a command line for clusterfuzz' minimizer for +each failure found during a fuzzing session with workbench.py. + +Expects the path to the minimizer tools, e.g. something like: +path/to/src/python/bot/minimizer +""" + +import json +from multiprocessing import cpu_count +import os +import sys + +PROCESSES = cpu_count() +BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +OUT_PATH = os.path.join(BASE_PATH, 'out.js') +FAILURES_JSON_PATH = os.path.join( + BASE_PATH, 'workdir', 'output', 'failures.json') + +assert len(sys.argv) > 1, 'Need to specify minimizer path.' +minimizer_path = sys.argv[1] + +def getcmd(command): + parts = command.split(' ') + prefix = command[:-(len(parts[-1]) + 1)] + return ('python %s/run.py -t%d -mjs -o %s "%s" %s' % + (minimizer_path, PROCESSES, OUT_PATH, prefix, parts[-1])) + +with open(FAILURES_JSON_PATH) as f: + failures = json.load(f) + +for failure in failures: + print('*********************************************************') + print('Source: ' + failure['source']) + print('Command:') + print(failure['command']) + print('Minimize:') + print(getcmd(failure['command'])) diff --git a/compiler/forget/packages/js-fuzzer/tools/run_one.py b/compiler/forget/packages/js-fuzzer/tools/run_one.py new file mode 100644 index 0000000000..8e7fc76a12 --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/tools/run_one.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# Copyright 2020 the V8 project authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +""" +Helper script to execute fuzz tests in a single process. + +Expects fuzz tests in workdir/output/dir-/fuzz-XXX.js. +Expects the as single parameter. +""" + +import json +import os +import random +import re +import subprocess +import sys + +BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +FOOZZIE = os.path.join(BASE_PATH, 'workdir', 'app_dir', 'v8_foozzie.py') +TEST_CASES = os.path.join(BASE_PATH, 'workdir', 'output') + +assert os.path.exists(FOOZZIE) + +# Output pattern from foozzie.py when it finds a failure. +FAILURE_RE = re.compile( + r'# V8 correctness failure.' + r'# V8 correctness configs: (?P.*).' + r'# V8 correctness sources: (?P.*).' + r'# V8 correctness suppression:.*', re.S) + +assert(len(sys.argv) > 1) +dir_number = int(sys.argv[1]) +assert(dir_number >= 0) + +test_dir = os.path.join(TEST_CASES, 'dir-%d' % dir_number) +assert os.path.exists(test_dir) + +def failure_state(command, stdout): + return dict(FAILURE_RE.search(stdout).groupdict(), command=command) + +def random_seed(): + """Returns random, non-zero seed.""" + seed = 0 + while not seed: + seed = random.SystemRandom().randint(-2147483648, 2147483647) + return seed + +def run(fuzz_file, flag_file): + """Executes the differential-fuzzing harness foozzie with one fuzz test.""" + with open(flag_file) as f: + flags = f.read().split(' ') + args = [FOOZZIE, '--random-seed=%d' % random_seed()] + flags + [fuzz_file] + cmd = ' '.join(args) + try: + output = subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True) + return (cmd, output.decode('utf-8')) + except Exception as e: + return (cmd, e.output.decode('utf-8')) + + +def list_tests(): + """Iterates all fuzz tests and corresponding flags in the given base dir.""" + for f in os.listdir(test_dir): + if f.startswith('fuzz'): + n = int(re.match(r'fuzz-(\d+)\.js', f).group(1)) + ff = 'flags-%d.js' % n + yield (os.path.join(test_dir, f), os.path.join(test_dir, ff)) + +# Some counters for the statistics. +count = 0 +count_timeout = 0 +count_crash = 0 +count_failure = 0 +failures = [] + +# Execute all tests in the given directory. Interpret foozzie's output and add +# it to the statistics. +for fuzz_file, flag_file in list_tests(): + cmd, output = run(fuzz_file, flag_file) + count += 1 + if '# V8 correctness - pass' in output: + continue + if '# V8 correctness - T-I-M-E-O-U-T' in output: + count_timeout += 1 + continue + if '# V8 correctness - C-R-A-S-H' in output: + count_crash += 1 + continue + count_failure += 1 + failures.append(failure_state(cmd, output)) + +with open(os.path.join(test_dir, 'failures.json'), 'w') as f: + json.dump(failures, f) + +stats = { + 'total': count, + 'timeout': count_timeout, + 'crash': count_crash, + 'failure': count_failure, +} + +with open(os.path.join(test_dir, 'stats.json'), 'w') as f: + json.dump(stats, f) diff --git a/compiler/forget/packages/js-fuzzer/tools/workbench.py b/compiler/forget/packages/js-fuzzer/tools/workbench.py new file mode 100644 index 0000000000..5a4fc0a19c --- /dev/null +++ b/compiler/forget/packages/js-fuzzer/tools/workbench.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# Copyright 2020 the V8 project authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +""" +Tool to execute multiprocessed fuzzing and testing sessions. + +Expects a single parameter with the number of sessions. + +Regularly updates a stats.json and failures.json during executions. E.g. +stay up-to-date with: +cat workdir/output/stats.json | python -m json.tool +""" + +# TODO(machenbach): This is currently tailored for differential fuzzing +# with foozzie. It could be generalized, but that'd require duplicating +# clusterfuzz' stack analysis to some degree. E.g. understanding asan +# or DCHECK failures. + +from __future__ import print_function + +import json +import math +from multiprocessing import Pool, cpu_count +import os +import random +import subprocess +import sys + +PROCESSES = cpu_count() +BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +TEST_CASES = os.path.join(BASE_PATH, 'workdir', 'output') +FUZZ_ONE = os.path.join(BASE_PATH, 'tools', 'fuzz_one.py') +RUN_ONE = os.path.join(BASE_PATH, 'tools', 'run_one.py') + +os.chdir(BASE_PATH) + +if os.path.exists(TEST_CASES): + if not os.path.isdir(TEST_CASES) or os.listdir(TEST_CASES): + sys.exit("'output' must be an empty directory") +else: + os.mkdir(TEST_CASES) + +# Use ~40000 for 24 hours of fuzzing on a modern work station. +RUNS = 8 +if len(sys.argv) > 1: + RUNS = int(sys.argv[1]) + +def run(n): + """Multiprocessed function that executes a single fuzz session and + afterwards executes all fuzz tests and collects the statistics. + + Args: + n: Subdirectory index of this run. + """ + subprocess.check_call([sys.executable, FUZZ_ONE, str(n)]) + subprocess.check_call([sys.executable, RUN_ONE, str(n)]) + test_dir = os.path.join(TEST_CASES, 'dir-%d' % n) + with open(os.path.join(test_dir, 'stats.json')) as f: + stats = json.load(f) + with open(os.path.join(test_dir, 'failures.json')) as f: + failures = json.load(f) + return (stats, failures) + + +class Stats(object): + def __init__(self): + self.total = 0 + self.crash = 0 + self.timeout = 0 + self.failure = 0 + self.dupe = 0 + self.failures = [] + self.known_states = set() + + def add(self, stats, failures): + # Aggregate common stats. + self.total += stats['total'] + self.crash += stats['crash'] + self.timeout += stats['timeout'] + + # Dedupe failures. + for failure in failures: + if failure['source'] in self.known_states: + self.dupe += 1 + continue + + self.known_states.add(failure['source']) + self.failure += 1 + self.failures.append(failure) + + @property + def stats(self): + return { + 'total': self.total, + 'crash': self.crash, + 'failure': self.failure, + 'dupe': self.dupe, + 'timeout': self.timeout, + } + +all_stats = Stats() +count = 0 +pool = Pool(processes=PROCESSES) + +# Iterate over all runs multiprocessed and merge the statistics and +# failure data of the single runs. +for stats, failures in pool.imap_unordered(run, range(RUNS)): + all_stats.add(stats, failures) + count += 1 + if count % max(1, int(RUNS / 20)) == 0: + print('Progress: %d runs (%d%%)' % (count, count * 100 / RUNS)) + + # Update overall stats. + with open(os.path.join(TEST_CASES, 'stats.json'), 'w') as f: + json.dump(all_stats.stats, f) + with open(os.path.join(TEST_CASES, 'failures.json'), 'w') as f: + json.dump(all_stats.failures, f) + +print('Ran %(total)d test cases (%(timeout)d timeouts, ' + '%(crash)d crashes, %(failure)d failures, %(dupe)d dupes)' + % all_stats.stats) + +for failure in all_stats.failures: + print(failure)