1// Copyright 2020 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package rust
16
17import (
18	"path/filepath"
19	"sort"
20	"strings"
21
22	"android/soong/android"
23	"android/soong/cc"
24	"android/soong/rust/config"
25)
26
27func init() {
28	android.RegisterModuleType("rust_fuzz", RustFuzzFactory)
29	android.RegisterSingletonType("rust_fuzz_packaging", rustFuzzPackagingFactory)
30}
31
32type fuzzDecorator struct {
33	*binaryDecorator
34
35	Properties            cc.FuzzProperties
36	dictionary            android.Path
37	corpus                android.Paths
38	corpusIntermediateDir android.Path
39	config                android.Path
40	data                  android.Paths
41	dataIntermediateDir   android.Path
42}
43
44var _ compiler = (*binaryDecorator)(nil)
45
46// rust_binary produces a binary that is runnable on a device.
47func RustFuzzFactory() android.Module {
48	module, _ := NewRustFuzz(android.HostAndDeviceSupported)
49	return module.Init()
50}
51
52func NewRustFuzz(hod android.HostOrDeviceSupported) (*Module, *fuzzDecorator) {
53	module, binary := NewRustBinary(hod)
54	fuzz := &fuzzDecorator{
55		binaryDecorator: binary,
56	}
57
58	// Change the defaults for the binaryDecorator's baseCompiler
59	fuzz.binaryDecorator.baseCompiler.dir = "fuzz"
60	fuzz.binaryDecorator.baseCompiler.dir64 = "fuzz"
61	fuzz.binaryDecorator.baseCompiler.location = InstallInData
62	module.sanitize.SetSanitizer(cc.Fuzzer, true)
63	module.compiler = fuzz
64	return module, fuzz
65}
66
67func (fuzzer *fuzzDecorator) compilerFlags(ctx ModuleContext, flags Flags) Flags {
68	flags = fuzzer.binaryDecorator.compilerFlags(ctx, flags)
69
70	// `../lib` for installed fuzz targets (both host and device), and `./lib` for fuzz target packages.
71	flags.LinkFlags = append(flags.LinkFlags, `-Wl,-rpath,\$$ORIGIN/../lib`)
72	flags.LinkFlags = append(flags.LinkFlags, `-Wl,-rpath,\$$ORIGIN/lib`)
73
74	return flags
75}
76
77func (fuzzer *fuzzDecorator) compilerDeps(ctx DepsContext, deps Deps) Deps {
78	if libFuzzerRuntimeLibrary := config.LibFuzzerRuntimeLibrary(ctx.toolchain()); libFuzzerRuntimeLibrary != "" {
79		deps.StaticLibs = append(deps.StaticLibs, libFuzzerRuntimeLibrary)
80	}
81	deps.SharedLibs = append(deps.SharedLibs, "libc++")
82	deps.Rlibs = append(deps.Rlibs, "liblibfuzzer_sys")
83
84	deps = fuzzer.binaryDecorator.compilerDeps(ctx, deps)
85
86	return deps
87}
88
89func (fuzzer *fuzzDecorator) compilerProps() []interface{} {
90	return append(fuzzer.binaryDecorator.compilerProps(),
91		&fuzzer.Properties)
92}
93
94func (fuzzer *fuzzDecorator) stdLinkage(ctx *depsContext) RustLinkage {
95	return RlibLinkage
96}
97
98func (fuzzer *fuzzDecorator) autoDep(ctx android.BottomUpMutatorContext) autoDep {
99	return rlibAutoDep
100}
101
102// Responsible for generating GNU Make rules that package fuzz targets into
103// their architecture & target/host specific zip file.
104type rustFuzzPackager struct {
105	packages    android.Paths
106	fuzzTargets map[string]bool
107}
108
109func rustFuzzPackagingFactory() android.Singleton {
110	return &rustFuzzPackager{}
111}
112
113type fileToZip struct {
114	SourceFilePath        android.Path
115	DestinationPathPrefix string
116}
117
118type archOs struct {
119	hostOrTarget string
120	arch         string
121	dir          string
122}
123
124func (s *rustFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
125
126	// Map between each architecture + host/device combination.
127	archDirs := make(map[archOs][]fileToZip)
128
129	// List of individual fuzz targets.
130	s.fuzzTargets = make(map[string]bool)
131
132	ctx.VisitAllModules(func(module android.Module) {
133		// Discard non-fuzz targets.
134		rustModule, ok := module.(*Module)
135		if !ok {
136			return
137		}
138
139		fuzzModule, ok := rustModule.compiler.(*fuzzDecorator)
140		if !ok {
141			return
142		}
143
144		// Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of
145		// fuzz targets we're going to package anyway.
146		if !rustModule.Enabled() || rustModule.Properties.PreventInstall ||
147			rustModule.InRamdisk() || rustModule.InVendorRamdisk() || rustModule.InRecovery() {
148			return
149		}
150
151		// Discard modules that are in an unavailable namespace.
152		if !rustModule.ExportedToMake() {
153			return
154		}
155
156		hostOrTargetString := "target"
157		if rustModule.Host() {
158			hostOrTargetString = "host"
159		}
160
161		archString := rustModule.Arch().ArchType.String()
162		archDir := android.PathForIntermediates(ctx, "fuzz", hostOrTargetString, archString)
163		archOs := archOs{hostOrTarget: hostOrTargetString, arch: archString, dir: archDir.String()}
164
165		var files []fileToZip
166		builder := android.NewRuleBuilder(pctx, ctx)
167
168		// Package the corpora into a zipfile.
169		if fuzzModule.corpus != nil {
170			corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip")
171			command := builder.Command().BuiltTool("soong_zip").
172				Flag("-j").
173				FlagWithOutput("-o ", corpusZip)
174			rspFile := corpusZip.ReplaceExtension(ctx, "rsp")
175			command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.corpus)
176			files = append(files, fileToZip{corpusZip, ""})
177		}
178
179		// Package the data into a zipfile.
180		if fuzzModule.data != nil {
181			dataZip := archDir.Join(ctx, module.Name()+"_data.zip")
182			command := builder.Command().BuiltTool("soong_zip").
183				FlagWithOutput("-o ", dataZip)
184			for _, f := range fuzzModule.data {
185				intermediateDir := strings.TrimSuffix(f.String(), f.Rel())
186				command.FlagWithArg("-C ", intermediateDir)
187				command.FlagWithInput("-f ", f)
188			}
189			files = append(files, fileToZip{dataZip, ""})
190		}
191
192		// The executable.
193		files = append(files, fileToZip{rustModule.unstrippedOutputFile.Path(), ""})
194
195		// The dictionary.
196		if fuzzModule.dictionary != nil {
197			files = append(files, fileToZip{fuzzModule.dictionary, ""})
198		}
199
200		// Additional fuzz config.
201		if fuzzModule.config != nil {
202			files = append(files, fileToZip{fuzzModule.config, ""})
203		}
204
205		fuzzZip := archDir.Join(ctx, module.Name()+".zip")
206
207		command := builder.Command().BuiltTool("soong_zip").
208			Flag("-j").
209			FlagWithOutput("-o ", fuzzZip)
210
211		for _, file := range files {
212			if file.DestinationPathPrefix != "" {
213				command.FlagWithArg("-P ", file.DestinationPathPrefix)
214			} else {
215				command.Flag("-P ''")
216			}
217			command.FlagWithInput("-f ", file.SourceFilePath)
218		}
219
220		builder.Build("create-"+fuzzZip.String(),
221			"Package "+module.Name()+" for "+archString+"-"+hostOrTargetString)
222
223		// Don't add modules to 'make haiku-rust' that are set to not be
224		// exported to the fuzzing infrastructure.
225		if config := fuzzModule.Properties.Fuzz_config; config != nil {
226			if rustModule.Host() && !BoolDefault(config.Fuzz_on_haiku_host, true) {
227				return
228			} else if !BoolDefault(config.Fuzz_on_haiku_device, true) {
229				return
230			}
231		}
232
233		s.fuzzTargets[module.Name()] = true
234		archDirs[archOs] = append(archDirs[archOs], fileToZip{fuzzZip, ""})
235	})
236
237	var archOsList []archOs
238	for archOs := range archDirs {
239		archOsList = append(archOsList, archOs)
240	}
241	sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].dir < archOsList[j].dir })
242
243	for _, archOs := range archOsList {
244		filesToZip := archDirs[archOs]
245		arch := archOs.arch
246		hostOrTarget := archOs.hostOrTarget
247		builder := android.NewRuleBuilder(pctx, ctx)
248		outputFile := android.PathForOutput(ctx, "fuzz-rust-"+hostOrTarget+"-"+arch+".zip")
249		s.packages = append(s.packages, outputFile)
250
251		command := builder.Command().BuiltTool("soong_zip").
252			Flag("-j").
253			FlagWithOutput("-o ", outputFile).
254			Flag("-L 0") // No need to try and re-compress the zipfiles.
255
256		for _, fileToZip := range filesToZip {
257			if fileToZip.DestinationPathPrefix != "" {
258				command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix)
259			} else {
260				command.Flag("-P ''")
261			}
262			command.FlagWithInput("-f ", fileToZip.SourceFilePath)
263		}
264		builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget,
265			"Create fuzz target packages for "+arch+"-"+hostOrTarget)
266	}
267
268}
269
270func (s *rustFuzzPackager) MakeVars(ctx android.MakeVarsContext) {
271	packages := s.packages.Strings()
272	sort.Strings(packages)
273
274	ctx.Strict("SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES", strings.Join(packages, " "))
275
276	// Preallocate the slice of fuzz targets to minimise memory allocations.
277	fuzzTargets := make([]string, 0, len(s.fuzzTargets))
278	for target, _ := range s.fuzzTargets {
279		fuzzTargets = append(fuzzTargets, target)
280	}
281	sort.Strings(fuzzTargets)
282	ctx.Strict("ALL_RUST_FUZZ_TARGETS", strings.Join(fuzzTargets, " "))
283}
284
285func (fuzz *fuzzDecorator) install(ctx ModuleContext) {
286	fuzz.binaryDecorator.baseCompiler.dir = filepath.Join(
287		"fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
288	fuzz.binaryDecorator.baseCompiler.dir64 = filepath.Join(
289		"fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
290	fuzz.binaryDecorator.baseCompiler.install(ctx)
291
292	if fuzz.Properties.Corpus != nil {
293		fuzz.corpus = android.PathsForModuleSrc(ctx, fuzz.Properties.Corpus)
294	}
295	if fuzz.Properties.Data != nil {
296		fuzz.data = android.PathsForModuleSrc(ctx, fuzz.Properties.Data)
297	}
298	if fuzz.Properties.Dictionary != nil {
299		fuzz.dictionary = android.PathForModuleSrc(ctx, *fuzz.Properties.Dictionary)
300	}
301}
302