mirror of
https://github.com/nodejs/node.git
synced 2025-05-15 03:29:46 +00:00

PR-URL: https://github.com/nodejs/node/pull/54077 Reviewed-By: Jiawen Geng <technicalcute@gmail.com> Reviewed-By: Richard Lau <rlau@redhat.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
142 lines
4.0 KiB
Python
142 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright 2024 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.
|
|
"""List all available fuzz tests and create wrapper scripts for each.
|
|
|
|
Invoked by GN depending on the unit-tests target. Expected to be called
|
|
from the root out dir of GN.
|
|
|
|
Writes the wrappers and 'fuzztests.stamp' into the ./fuzztests/ directory.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import shutil
|
|
import stat
|
|
import subprocess
|
|
import sys
|
|
|
|
from pathlib import Path, PurePath
|
|
|
|
# Set up path to be able to import action_helpers
|
|
BASE_DIR = Path(__file__).absolute().parent.parent.parent
|
|
sys.path.append(str(BASE_DIR / 'build'))
|
|
import action_helpers
|
|
|
|
CENTIPEDE = 'centipede'
|
|
EXECUTABLE = 'v8_unittests'
|
|
FUZZ_TEST_DIR = 'fuzztests'
|
|
FUZZ_TEST_STAMP = 'fuzztests.stamp'
|
|
|
|
# When this script is enabled, we expect to find at least the demo tests.
|
|
MIN_FUZZTESTS = 2
|
|
|
|
# This is an arbitrary safeguard. If we run into it and it's legit,
|
|
# just double it.
|
|
MAX_FUZZTESTS = 100
|
|
|
|
WRAPPER_HEADER = """
|
|
#!/bin/sh
|
|
BINARY_DIR="$(cd "${{0%/*}}"/..; pwd)"
|
|
cd $BINARY_DIR
|
|
""".strip()
|
|
|
|
CENTIPEDE_WRAPPER = WRAPPER_HEADER + """
|
|
exec $BINARY_DIR/centipede $@
|
|
"""
|
|
|
|
FUZZTEST_WRAPPER = WRAPPER_HEADER + """
|
|
# Normal fuzzing.
|
|
if [ "$#" -eq "0" ]; then
|
|
exec $BINARY_DIR/v8_unittests --fuzz={test} --corpus_database=""
|
|
fi
|
|
# Fuzztest replay.
|
|
if [ "$#" -eq "1" ]; then
|
|
unset CENTIPEDE_RUNNER_FLAGS
|
|
FUZZTEST_REPLAY=$1 exec $BINARY_DIR/v8_unittests --fuzz={test} --corpus_database=""
|
|
fi
|
|
"""
|
|
|
|
FUZZER_NAME_RE = re.compile(r'^\w+\.\w+$')
|
|
|
|
|
|
def list_fuzz_tests(executable):
|
|
env = os.environ
|
|
env['ASAN_OPTIONS'] = 'detect_odr_violation=0'
|
|
env['CENTIPEDE_RUNNER_FLAGS'] = 'stack_limit_kb=0:'
|
|
test_list = subprocess.check_output(
|
|
[executable, '--list_fuzz_tests=true'],
|
|
env=env,
|
|
cwd='.',
|
|
).decode('utf-8')
|
|
return sorted(set(re.findall('Fuzz test: (.*)', test_list)))
|
|
|
|
|
|
def fuzz_test_to_file_name(test):
|
|
assert FUZZER_NAME_RE.match(test)
|
|
fuzztest_name = re.sub(r'((f|F)uzz(t|T)est|(t|T)est)', '', test)
|
|
fuzztest_name = re.sub(r'\.', ' ', fuzztest_name)
|
|
fuzztest_name = re.sub('([A-Z]+)', r' \1', fuzztest_name)
|
|
fuzztest_name = re.sub('([A-Z][a-z]+)', r' \1', fuzztest_name)
|
|
splitted = fuzztest_name.split()
|
|
splitted = map(str.lower, splitted)
|
|
splitted = filter(bool, splitted)
|
|
return 'v8_' + '_'.join(splitted) + '_fuzztest'
|
|
|
|
|
|
def create_wrapper(file_name, template, test=''):
|
|
with action_helpers.atomic_output(file_name) as f:
|
|
wrapper = template.format(test=test)
|
|
f.write(wrapper.encode('utf-8'))
|
|
|
|
# Make the wrapper world-executable.
|
|
st = os.stat(file_name)
|
|
m = st.st_mode
|
|
os.chmod(file_name, m | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
|
|
|
|
def setup_fuzztests_dir(cwd):
|
|
# This script owns the fuzztests subdirectory. Purge everything
|
|
# to enable consistent incremental builds.
|
|
fuzz_test_dir = cwd / FUZZ_TEST_DIR
|
|
shutil.rmtree(fuzz_test_dir, ignore_errors=True)
|
|
fuzz_test_dir.mkdir(exist_ok=True)
|
|
|
|
# Centipede is later expected to be side-by-side with the fuzz-test
|
|
# targets. Create a bash redirect so that shared libraries in cwd
|
|
# keep loading.
|
|
create_wrapper(fuzz_test_dir / CENTIPEDE, CENTIPEDE_WRAPPER)
|
|
|
|
return fuzz_test_dir
|
|
|
|
|
|
def create_fuzztest_wrapper(fuzz_test_dir, test_name):
|
|
fuzztest_path = fuzz_test_dir / fuzz_test_to_file_name(test_name)
|
|
create_wrapper(fuzztest_path, FUZZTEST_WRAPPER, test_name)
|
|
|
|
|
|
def main():
|
|
# The CWD is expected to be the root out dir of GN.
|
|
cwd = Path(os.getcwd())
|
|
|
|
# We expect the unit-test executable present in the root dir.
|
|
executable = cwd / EXECUTABLE
|
|
assert executable.exists()
|
|
|
|
fuzz_test_dir = setup_fuzztests_dir(cwd)
|
|
fuzz_tests = list_fuzz_tests(executable)
|
|
assert MIN_FUZZTESTS <= len(fuzz_tests) <= MAX_FUZZTESTS
|
|
|
|
for test_name in fuzz_tests:
|
|
create_fuzztest_wrapper(fuzz_test_dir, test_name)
|
|
|
|
# This is a place holder telling GN that we're done.
|
|
with action_helpers.atomic_output(fuzz_test_dir / FUZZ_TEST_STAMP) as f:
|
|
f.write('\n'.join(fuzz_tests).encode('utf-8'))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|