xref: /aosp_15_r20/external/cronet/testing/libfuzzer/fuzzer_test.gni (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1# Copyright 2015 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5# Defines fuzzer_test.
6#
7import("//build/config/features.gni")
8import("//build/config/sanitizers/sanitizers.gni")
9import("//testing/test.gni")
10
11# fuzzer_test is used to define individual libfuzzer tests.
12#
13# Supported attributes:
14# - (required) sources - fuzzer test source files
15# - deps - test dependencies
16# - libs - Additional libraries to link.
17# - frameworks - Apple-only. Additional frameworks to link.
18# - additional_configs - additional configs to be used for compilation
19# - dict - a dictionary file for the fuzzer.
20# - environment_variables - certain whitelisted environment variables for the
21# fuzzer (AFL_DRIVER_DONT_DEFER is the only one allowed currently).
22# - libfuzzer_options - options for the fuzzer (e.g. close_fd_mask=N).
23#   These are mostly applied only when the fuzzer is being run using the
24#   libfuzzer fuzzing engine, but a few of these may be interpreted by
25#   other fuzzing engines too
26# - centipede_options - options for the fuzzer (e.g. shmem_size_mb=N)
27#   when running using the centipede fuzzing engine.
28# - asan_options - AddressSanitizer options (e.g. allow_user_segv_handler=1).
29# - msan_options - MemorySanitizer options.
30# - ubsan_options - UndefinedBehaviorSanitizer options.
31# - seed_corpus - a directory with seed corpus.
32# - seed_corpus_deps - dependencies for generating the seed corpus.
33# - grammar_options - defines a grammar used by a grammar based mutator.
34# - exclude_main - if you're going to provide your own 'main' function
35# - high_end_job_required - whether the fuzzer requires bigger machines to run.
36#   Optional argument.
37#
38# If use_libfuzzer gn flag is defined, then proper fuzzer would be build.
39# Without use_libfuzzer or use_afl a unit-test style binary would be built on
40# linux and the whole target is a no-op otherwise.
41#
42# The template wraps test() target with appropriate dependencies.
43# If any test run-time options are present (dict or libfuzzer_options), then a
44# config (.options file) file would be generated or modified in root output
45# dir (next to test).
46template("fuzzer_test") {
47  _high_end_job_required = false
48  if (defined(invoker.high_end_job_required)) {
49    _high_end_job_required = invoker.high_end_job_required
50  }
51
52  # If the job is a high_end job and that we are currently building a high_end
53  # target, we should compile this fuzzer_test in. Otherwise, for now, we just
54  # compile everything in.
55  # TODO(crbug.com/333831251): Once CF supports high end jobs, do not compile
56  # high end targets except for high end jobs.
57  _should_build = (_high_end_job_required || !high_end_fuzzer_targets) &&
58                  !disable_libfuzzer && use_fuzzing_engine
59  if (_should_build) {
60    assert(defined(invoker.sources), "Need sources in $target_name.")
61
62    test_deps = []
63    if (defined(invoker.exclude_main) && invoker.exclude_main) {
64      test_deps += [ "//testing/libfuzzer:fuzzing_engine_no_main" ]
65    } else {
66      test_deps += [ "//testing/libfuzzer:fuzzing_engine_main" ]
67    }
68    test_data_deps = []
69
70    if (defined(invoker.deps)) {
71      test_deps += invoker.deps
72    }
73    if (defined(invoker.data_deps)) {
74      test_data_deps += invoker.data_deps
75    }
76
77    supporting_file_test_deps = []
78    supporting_file_test_data_deps = []
79
80    if (defined(invoker.seed_corpus) || defined(invoker.seed_corpuses)) {
81      assert(!(defined(invoker.seed_corpus) && defined(invoker.seed_corpuses)),
82             "Do not use both seed_corpus and seed_corpuses for $target_name.")
83
84      out = "$root_build_dir/$target_name" + "_seed_corpus.zip"
85
86      seed_corpus_deps = []
87
88      if (defined(invoker.seed_corpus_deps)) {
89        seed_corpus_deps += invoker.seed_corpus_deps
90      }
91
92      action(target_name + "_seed_corpus") {
93        script = "//testing/libfuzzer/archive_corpus.py"
94
95        testonly = true
96
97        args = [
98          "--output",
99          rebase_path(out, root_build_dir),
100        ]
101
102        if (defined(invoker.seed_corpus)) {
103          args += [ rebase_path(invoker.seed_corpus, root_build_dir) ]
104        }
105
106        if (defined(invoker.seed_corpuses)) {
107          foreach(seed_corpus_path, invoker.seed_corpuses) {
108            args += [ rebase_path(seed_corpus_path, root_build_dir) ]
109          }
110        }
111
112        outputs = [ out ]
113
114        deps = [ "//testing/libfuzzer:seed_corpus" ] + seed_corpus_deps
115      }
116
117      if (archive_seed_corpus) {
118        supporting_file_test_deps += [ ":" + target_name + "_seed_corpus" ]
119      }
120    }
121
122    if (defined(invoker.dict) || defined(invoker.libfuzzer_options) ||
123        defined(invoker.centipede_options) || defined(invoker.asan_options) ||
124        defined(invoker.msan_options) || defined(invoker.ubsan_options) ||
125        defined(invoker.environment_variables) ||
126        defined(invoker.grammar_options)) {
127      if (defined(invoker.dict)) {
128        # Copy dictionary to output.
129        copy(target_name + "_dict_copy") {
130          sources = [ invoker.dict ]
131          outputs = [ "$root_build_dir/" + target_name + ".dict" ]
132        }
133        supporting_file_test_deps += [ ":" + target_name + "_dict_copy" ]
134      }
135
136      fuzzer_name = target_name
137
138      # Generate .options file.
139      config_file_name = target_name + ".options"
140      action(config_file_name) {
141        script = "//testing/libfuzzer/gen_fuzzer_config.py"
142        args = [
143          "--config",
144          rebase_path("$root_build_dir/" + config_file_name, root_build_dir),
145        ]
146
147        if (defined(invoker.dict)) {
148          args += [
149            "--dict",
150            rebase_path("$root_build_dir/" + fuzzer_name + ".dict",
151                        root_build_dir),
152          ]
153        } else {
154          not_needed([ "fuzzer_name" ])
155        }
156
157        if (defined(invoker.libfuzzer_options)) {
158          args += [ "--libfuzzer_options" ]
159          args += invoker.libfuzzer_options
160        }
161
162        if (defined(invoker.centipede_options)) {
163          args += [ "--centipede_options" ]
164          args += invoker.centipede_options
165        }
166
167        if (defined(invoker.asan_options)) {
168          args += [ "--asan_options" ]
169          args += invoker.asan_options
170        }
171
172        if (defined(invoker.msan_options)) {
173          args += [ "--msan_options" ]
174          args += invoker.msan_options
175        }
176
177        if (defined(invoker.ubsan_options)) {
178          args += [ "--ubsan_options" ]
179          args += invoker.ubsan_options
180        }
181
182        if (defined(invoker.environment_variables)) {
183          args += [ "--environment_variables" ]
184          args += invoker.environment_variables
185        }
186
187        if (defined(invoker.grammar_options)) {
188          args += [ "--grammar_options" ]
189          args += invoker.grammar_options
190        }
191
192        outputs = [ "$root_build_dir/$config_file_name" ]
193      }
194      test_data_deps += [ ":" + config_file_name ]
195      supporting_file_test_deps += [ ":" + config_file_name ]
196    }
197
198    if (generate_fuzzer_owners) {
199      # Generating owners files is slow, only enable when fuzzing engine is
200      # used.
201      owners_file_name = target_name + ".owners"
202      action(owners_file_name) {
203        script = "//testing/libfuzzer/gen_fuzzer_owners.py"
204        pool = "//testing/libfuzzer:fuzzer_owners_pool"
205        args = [
206          "--owners",
207          rebase_path("$root_build_dir/" + owners_file_name, root_build_dir),
208        ]
209
210        if (defined(invoker.sources) && invoker.sources != []) {
211          args += [ "--sources" ] + rebase_path(invoker.sources, "//")
212        } else if (defined(invoker.deps) && invoker.deps != []) {
213          _full_deps = []
214          foreach(_dep, invoker.deps) {
215            _full_deps += [ get_label_info(_dep, "dir") + ":" +
216                            get_label_info(_dep, "name") ]
217          }
218          args += [
219                    "--build-dir",
220                    rebase_path("$root_build_dir/", root_build_dir),
221                    "--deps",
222                  ] + _full_deps
223        }
224
225        outputs = [ "$root_build_dir/$owners_file_name" ]
226      }
227      supporting_file_test_data_deps += [ ":" + owners_file_name ]
228    }
229
230    # Copy to executable folder and codesign for catalyst
231    if (is_ios) {
232      # iOS only supports fuzzer in catalyst environment.
233      assert(target_environment == "catalyst")
234
235      # Loop over two kinds of deps to codesign these in two |action_foreach|s.
236      # Use the "test_deps", "test_data_deps" as identifiers in for loop.
237      foreach(_deps_type_label,
238              [
239                "test_deps",
240                "test_data_deps",
241              ]) {
242        _dep_list = []
243        if (_deps_type_label == "test_deps") {
244          _dep_list = supporting_file_test_deps
245        } else {
246          _dep_list = supporting_file_test_data_deps
247        }
248        _files_to_sign = []
249        foreach(dep, _dep_list) {
250          _files_to_sign += get_target_outputs(dep)
251        }
252        if (_files_to_sign != []) {
253          _codesign_action_name =
254              target_name + "_codesign_supporting_files_" + _deps_type_label
255          action_foreach(_codesign_action_name) {
256            testonly = true
257            script = "//build/config/ios/codesign.py"
258            sources = _files_to_sign
259            _codesign_output_path =
260                "${root_build_dir}/codesign/{{source_file_part}}"
261            outputs = [ _codesign_output_path ]
262            args = [
263              "code-sign-file",
264              "--identity=" + ios_code_signing_identity,
265              "--output=" + rebase_path(_codesign_output_path, root_build_dir),
266              "{{source}}",
267            ]
268            deps = _dep_list
269          }
270          _bundle_data_name = target_name + "_bundle_data_" + _deps_type_label
271          bundle_data(_bundle_data_name) {
272            testonly = true
273            sources = get_target_outputs(":${_codesign_action_name}")
274            outputs = [ "{{bundle_executable_dir}}/{{source_file_part}}" ]
275            public_deps = [ ":${_codesign_action_name}" ]
276          }
277          if (_deps_type_label == "test_deps") {
278            test_deps += [ ":${_bundle_data_name}" ]
279          } else {
280            test_data_deps += [ ":${_bundle_data_name}" ]
281          }
282        }
283      }
284    } else {
285      test_deps += supporting_file_test_deps
286      test_data_deps += supporting_file_test_data_deps
287    }
288
289    test(target_name) {
290      forward_variables_from(invoker,
291                             [
292                               "cflags",
293                               "cflags_cc",
294                               "check_includes",
295                               "defines",
296                               "include_dirs",
297                               "output_name",
298                               "sources",
299                               "libs",
300                               "frameworks",
301                             ])
302      deps = test_deps
303      data_deps = test_data_deps
304
305      if (defined(invoker.additional_configs)) {
306        configs += invoker.additional_configs
307      }
308      configs += [ "//testing/libfuzzer:fuzzer_test_config" ]
309
310      # Used by WebRTC to suppress some Clang warnings in their codebase.
311      if (defined(invoker.suppressed_configs)) {
312        configs -= invoker.suppressed_configs
313      }
314
315      if (defined(invoker.generated_sources)) {
316        sources += invoker.generated_sources
317      }
318
319      if (is_ios) {
320        info_plist =
321            "//testing/libfuzzer/fuzzer_support_ios/fuzzer-engine-Info.plist"
322      }
323
324      if (is_mac) {
325        sources += [ "//testing/libfuzzer/libfuzzer_exports.h" ]
326      }
327    }
328  } else {
329    # noop on unsupported platforms.
330    # mark attributes as used.
331    not_needed(invoker, "*")
332    not_needed(invoker,
333               [
334                 "deps",
335                 "seed_corpus",
336                 "sources",
337               ])
338
339    group(target_name) {
340    }
341  }
342}
343