Add jni support to java_fuzz_host

Bug: 219782880
Test: m example_java_fuzzer_with_native_lib, go test -run TestJavaFuzz
Change-Id: I1d05fb449e5378a27a0096869d9c12ca0a1245c6
diff --git a/cc/cc.go b/cc/cc.go
index 00b82d5..154c353 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -746,6 +746,7 @@
 	runtimeDepTag         = installDependencyTag{name: "runtime lib"}
 	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 	stubImplDepTag        = dependencyTag{name: "stub_impl"}
+	JniFuzzLibTag         = dependencyTag{name: "jni_fuzz_lib_tag"}
 )
 
 func IsSharedDepTag(depTag blueprint.DependencyTag) bool {
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 814fef6..53169de 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -969,6 +969,22 @@
 				})
 			}
 		} else if sanitizeable, ok := mctx.Module().(Sanitizeable); ok {
+			// If it's a Java module with native dependencies through jni,
+			// set the sanitizer for them
+			if jniSanitizeable, ok := mctx.Module().(JniSanitizeable); ok {
+				if jniSanitizeable.IsSanitizerEnabledForJni(mctx, t.name()) {
+					mctx.VisitDirectDeps(func(child android.Module) {
+						if c, ok := child.(PlatformSanitizeable); ok &&
+							mctx.OtherModuleDependencyTag(child) == JniFuzzLibTag &&
+							c.SanitizePropDefined() &&
+							!c.SanitizeNever() &&
+							!c.IsSanitizerExplicitlyDisabled(t) {
+							c.SetSanitizeDep(true)
+						}
+					})
+				}
+			}
+
 			// If an APEX module includes a lib which is enabled for a sanitizer T, then
 			// the APEX module is also enabled for the same sanitizer type.
 			mctx.VisitDirectDeps(func(child android.Module) {
@@ -1280,6 +1296,11 @@
 	AddSanitizerDependencies(ctx android.BottomUpMutatorContext, sanitizerName string)
 }
 
+type JniSanitizeable interface {
+	android.Module
+	IsSanitizerEnabledForJni(ctx android.BaseModuleContext, sanitizerName string) bool
+}
+
 func (c *Module) MinimalRuntimeDep() bool {
 	return c.sanitize.Properties.MinimalRuntimeDep
 }
@@ -1407,7 +1428,7 @@
 			}
 			c.SetSanitizeDep(false)
 		} else if sanitizeable, ok := mctx.Module().(Sanitizeable); ok && sanitizeable.IsSanitizerEnabled(mctx, t.name()) {
-			// APEX modules fall here
+			// APEX and Java fuzz modules fall here
 			sanitizeable.AddSanitizerDependencies(mctx, t.name())
 			mctx.CreateVariations(t.variationName())
 		} else if c, ok := mctx.Module().(*Module); ok {
diff --git a/fuzz/fuzz_common.go b/fuzz/fuzz_common.go
index 89f8187..700cdf0 100644
--- a/fuzz/fuzz_common.go
+++ b/fuzz/fuzz_common.go
@@ -82,6 +82,9 @@
 	Hwasan_options []string `json:"hwasan_options,omitempty"`
 	// Additional options to be passed to HWASAN when running on host in Haiku.
 	Asan_options []string `json:"asan_options,omitempty"`
+	// If there's a Java fuzzer with JNI, a different version of Jazzer would
+	// need to be added to the fuzzer package than one without JNI
+	IsJni *bool `json:"is_jni,omitempty"`
 }
 
 type FuzzProperties struct {
diff --git a/java/androidmk.go b/java/androidmk.go
index 80b828d..f6ea6a9 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -132,6 +132,16 @@
 	return entriesList
 }
 
+func (j *JavaFuzzLibrary) AndroidMkEntries() []android.AndroidMkEntries {
+	entriesList := j.Library.AndroidMkEntries()
+	entries := &entriesList[0]
+	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+		entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", "null-suite")
+		androidMkWriteTestData(j.jniFilePaths, entries)
+	})
+	return entriesList
+}
+
 // Called for modules that are a component of a test suite.
 func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string, perTestcaseDirectory bool) {
 	entries.SetString("LOCAL_MODULE_TAGS", "tests")
diff --git a/java/fuzz.go b/java/fuzz.go
index 257f343..584c80b 100644
--- a/java/fuzz.go
+++ b/java/fuzz.go
@@ -15,14 +15,25 @@
 package java
 
 import (
-	"github.com/google/blueprint/proptools"
 	"sort"
 	"strings"
 
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
+	"android/soong/cc"
 	"android/soong/fuzz"
 )
 
+type jniProperties struct {
+	// list of jni libs
+	Jni_libs []string
+
+	// sanitization
+	Sanitizers []string
+}
+
 func init() {
 	RegisterJavaFuzzBuildComponents(android.InitRegistrationContext)
 }
@@ -35,11 +46,60 @@
 type JavaFuzzLibrary struct {
 	Library
 	fuzzPackagedModule fuzz.FuzzPackagedModule
+	jniProperties      jniProperties
+	jniFilePaths       android.Paths
+}
+
+// IsSanitizerEnabled implemented to make JavaFuzzLibrary implement
+// cc.Sanitizeable
+func (j *JavaFuzzLibrary) IsSanitizerEnabled(ctx android.BaseModuleContext, sanitizerName string) bool {
+	for _, s := range j.jniProperties.Sanitizers {
+		if sanitizerName == s {
+			return true
+		}
+	}
+	return false
+}
+
+// IsSanitizerEnabledForJni implemented to make JavaFuzzLibrary implement
+// cc.JniSanitizeable. It returns a bool for whether a cc dependency should be
+// sanitized for the given sanitizer or not.
+func (j *JavaFuzzLibrary) IsSanitizerEnabledForJni(ctx android.BaseModuleContext, sanitizerName string) bool {
+	return j.IsSanitizerEnabled(ctx, sanitizerName)
+}
+
+// EnableSanitizer implemented to make JavaFuzzLibrary implement
+// cc.Sanitizeable
+func (j *JavaFuzzLibrary) EnableSanitizer(sanitizerName string) {
+}
+
+// AddSanitizerDependencies implemented to make JavaFuzzLibrary implement
+// cc.Sanitizeable
+func (j *JavaFuzzLibrary) AddSanitizerDependencies(mctx android.BottomUpMutatorContext, sanitizerName string) {
+}
+
+// To verify that JavaFuzzLibrary implements cc.Sanitizeable
+var _ cc.Sanitizeable = (*JavaFuzzLibrary)(nil)
+
+func (j *JavaFuzzLibrary) DepsMutator(mctx android.BottomUpMutatorContext) {
+	if len(j.jniProperties.Jni_libs) > 0 {
+		if j.fuzzPackagedModule.FuzzProperties.Fuzz_config == nil {
+			config := &fuzz.FuzzConfig{}
+			j.fuzzPackagedModule.FuzzProperties.Fuzz_config = config
+		}
+		// this will be used by the ingestion pipeline to determine the version
+		// of jazzer to add to the fuzzer package
+		j.fuzzPackagedModule.FuzzProperties.Fuzz_config.IsJni = proptools.BoolPtr(true)
+
+		for _, target := range mctx.MultiTargets() {
+			sharedLibVariations := append(target.Variations(), blueprint.Variation{Mutator: "link", Variation: "shared"})
+			mctx.AddFarVariationDependencies(sharedLibVariations, cc.JniFuzzLibTag, j.jniProperties.Jni_libs...)
+		}
+	}
+	j.Library.DepsMutator(mctx)
 }
 
 func (j *JavaFuzzLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	j.Library.GenerateAndroidBuildActions(ctx)
-
 	if j.fuzzPackagedModule.FuzzProperties.Corpus != nil {
 		j.fuzzPackagedModule.Corpus = android.PathsForModuleSrc(ctx, j.fuzzPackagedModule.FuzzProperties.Corpus)
 	}
@@ -55,6 +115,23 @@
 		android.WriteFileRule(ctx, configPath, j.fuzzPackagedModule.FuzzProperties.Fuzz_config.String())
 		j.fuzzPackagedModule.Config = configPath
 	}
+
+	ctx.VisitDirectDepsWithTag(cc.JniFuzzLibTag, func(dep android.Module) {
+		sharedLibInfo := ctx.OtherModuleProvider(dep, cc.SharedLibraryInfoProvider).(cc.SharedLibraryInfo)
+		if sharedLibInfo.SharedLibrary != nil {
+			libPath := android.PathForModuleOut(ctx, sharedLibInfo.SharedLibrary.Base())
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.Cp,
+				Input:  sharedLibInfo.SharedLibrary,
+				Output: libPath,
+			})
+			j.jniFilePaths = append(j.jniFilePaths, libPath)
+		} else {
+			ctx.PropertyErrorf("jni_libs", "%q of type %q is not supported", dep.Name(), ctx.OtherModuleType(dep))
+		}
+	})
+
+	j.Library.GenerateAndroidBuildActions(ctx)
 }
 
 // java_fuzz builds and links sources into a `.jar` file for the host.
@@ -65,7 +142,8 @@
 	module := &JavaFuzzLibrary{}
 
 	module.addHostProperties()
-	module.Module.properties.Installable = proptools.BoolPtr(false)
+	module.AddProperties(&module.jniProperties)
+	module.Module.properties.Installable = proptools.BoolPtr(true)
 	module.AddProperties(&module.fuzzPackagedModule.FuzzProperties)
 
 	// java_fuzz packaging rules collide when both linux_glibc and linux_bionic are enabled, disable the linux_bionic variants.
@@ -83,7 +161,7 @@
 
 	module.initModuleAndImport(module)
 	android.InitSdkAwareModule(module)
-	InitJavaModule(module, android.HostSupported)
+	InitJavaModuleMultiTargets(module, android.HostSupported)
 	return module
 }
 
@@ -106,26 +184,26 @@
 
 	ctx.VisitAllModules(func(module android.Module) {
 		// Discard non-fuzz targets.
-		javaModule, ok := module.(*JavaFuzzLibrary)
+		javaFuzzModule, ok := module.(*JavaFuzzLibrary)
 		if !ok {
 			return
 		}
 
 		fuzzModuleValidator := fuzz.FuzzModule{
-			javaModule.ModuleBase,
-			javaModule.DefaultableModuleBase,
-			javaModule.ApexModuleBase,
+			javaFuzzModule.ModuleBase,
+			javaFuzzModule.DefaultableModuleBase,
+			javaFuzzModule.ApexModuleBase,
 		}
 
-		if ok := fuzz.IsValid(fuzzModuleValidator); !ok || *javaModule.Module.properties.Installable {
+		if ok := fuzz.IsValid(fuzzModuleValidator); !ok {
 			return
 		}
 
 		hostOrTargetString := "target"
-		if javaModule.Host() {
+		if javaFuzzModule.Host() {
 			hostOrTargetString = "host"
 		}
-		archString := javaModule.Arch().ArchType.String()
+		archString := javaFuzzModule.Arch().ArchType.String()
 
 		archDir := android.PathForIntermediates(ctx, "fuzz", hostOrTargetString, archString)
 		archOs := fuzz.ArchOs{HostOrTarget: hostOrTargetString, Arch: archString, Dir: archDir.String()}
@@ -134,12 +212,17 @@
 		builder := android.NewRuleBuilder(pctx, ctx)
 
 		// Package the artifacts (data, corpus, config and dictionary into a zipfile.
-		files = s.PackageArtifacts(ctx, module, javaModule.fuzzPackagedModule, archDir, builder)
+		files = s.PackageArtifacts(ctx, module, javaFuzzModule.fuzzPackagedModule, archDir, builder)
 
 		// Add .jar
-		files = append(files, fuzz.FileToZip{javaModule.outputFile, ""})
+		files = append(files, fuzz.FileToZip{javaFuzzModule.outputFile, ""})
 
-		archDirs[archOs], ok = s.BuildZipFile(ctx, module, javaModule.fuzzPackagedModule, files, builder, archDir, archString, "host", archOs, archDirs)
+		// Add jni .so files
+		for _, fPath := range javaFuzzModule.jniFilePaths {
+			files = append(files, fuzz.FileToZip{fPath, ""})
+		}
+
+		archDirs[archOs], ok = s.BuildZipFile(ctx, module, javaFuzzModule.fuzzPackagedModule, files, builder, archDir, archString, hostOrTargetString, archOs, archDirs)
 		if !ok {
 			return
 		}
diff --git a/java/fuzz_test.go b/java/fuzz_test.go
index cf063eb..0a2c945 100644
--- a/java/fuzz_test.go
+++ b/java/fuzz_test.go
@@ -15,13 +15,17 @@
 package java
 
 import (
-	"android/soong/android"
 	"path/filepath"
+	"runtime"
 	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
 )
 
 var prepForJavaFuzzTest = android.GroupFixturePreparers(
 	PrepareForTestWithJavaDefaultModules,
+	cc.PrepareForTestWithCcBuildComponents,
 	android.FixtureRegisterWithContext(RegisterJavaFuzzBuildComponents),
 )
 
@@ -32,6 +36,13 @@
 			srcs: ["a.java"],
 			libs: ["bar"],
 			static_libs: ["baz"],
+            jni_libs: [
+                "libjni",
+            ],
+            sanitizers: [
+                "address",
+                "fuzzer",
+            ],
 		}
 
 		java_library_host {
@@ -42,11 +53,21 @@
 		java_library_host {
 			name: "baz",
 			srcs: ["c.java"],
-		}`)
+		}
+
+		cc_library_shared {
+			name: "libjni",
+			host_supported: true,
+			device_supported: false,
+			stl: "none",
+		}
+		`)
 
 	osCommonTarget := result.Config.BuildOSCommonTarget.String()
-	javac := result.ModuleForTests("foo", osCommonTarget).Rule("javac")
-	combineJar := result.ModuleForTests("foo", osCommonTarget).Description("for javac")
+
+	osCommonTargetWithSan := osCommonTarget + "_asan" + "_fuzzer"
+	javac := result.ModuleForTests("foo", osCommonTargetWithSan).Rule("javac")
+	combineJar := result.ModuleForTests("foo", osCommonTargetWithSan).Description("for javac")
 
 	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
 		t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
@@ -62,4 +83,18 @@
 	if len(combineJar.Inputs) != 2 || combineJar.Inputs[1].String() != baz {
 		t.Errorf("foo combineJar inputs %v does not contain %q", combineJar.Inputs, baz)
 	}
+
+	ctx := result.TestContext
+	foo := ctx.ModuleForTests("foo", osCommonTargetWithSan).Module().(*JavaFuzzLibrary)
+
+	expected := "libjni.so"
+	if runtime.GOOS == "darwin" {
+		expected = "libjni.dylib"
+	}
+
+	fooJniFilePaths := foo.jniFilePaths
+	if len(fooJniFilePaths) != 1 || fooJniFilePaths[0].Rel() != expected {
+		t.Errorf(`expected foo test data relative path [%q], got %q`,
+			expected, fooJniFilePaths.Strings())
+	}
 }