// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cc

import (
	"fmt"
	"sort"
	"strings"
	"sync"

	"github.com/google/blueprint"
	"github.com/google/blueprint/proptools"

	"android/soong/android"
	"android/soong/cc/config"
	"android/soong/etc"
)

//go:generate go run ../../blueprint/gobtools/codegen/gob_gen.go

var (
	// Any C flags added by sanitizer which libTooling tools may not
	// understand also need to be added to ClangLibToolingUnknownCflags in
	// cc/config/clang.go

	asanCflags = []string{
		"-fno-omit-frame-pointer",
	}

	// DO NOT ADD MLLVM FLAGS HERE! ADD THEM BELOW TO hwasanCommonFlags.
	hwasanCflags = []string{
		"-fno-omit-frame-pointer",
		"-Wno-frame-larger-than=",
		"-fsanitize-hwaddress-abi=platform",
	}

	// ThinLTO performs codegen during link time, thus these flags need to
	// passed to both CFLAGS and LDFLAGS.
	hwasanCommonflags = []string{
		// The following improves debug location information
		// availability at the cost of its accuracy. It increases
		// the likelihood of a stack variable's frame offset
		// to be recorded in the debug info, which is important
		// for the quality of hwasan reports. The downside is a
		// higher number of "optimized out" stack variables.
		// b/112437883.
		"-instcombine-lower-dbg-declare=0",
		"-dom-tree-reachability-max-bbs-to-explore=128",
	}

	sanitizeIgnorelistPrefix = "-fsanitize-ignorelist="

	cfiBlocklistPath     = "external/compiler-rt/lib/cfi"
	cfiBlocklistFilename = "cfi_blocklist.txt"
	cfiEnableFlag        = "-fsanitize=cfi"
	cfiCrossDsoFlag      = "-fsanitize-cfi-cross-dso"
	cfiCflags            = []string{"-flto", cfiCrossDsoFlag,
		sanitizeIgnorelistPrefix + cfiBlocklistPath + "/" + cfiBlocklistFilename}
	// -flto and -fvisibility are required by clang when -fsanitize=cfi is
	// used, but have no effect on assembly files
	cfiAsflags = []string{"-flto", "-fvisibility=default"}
	cfiLdflags = []string{"-flto", cfiCrossDsoFlag, cfiEnableFlag,
		"-Wl,-plugin-opt,O1"}
	cfiExportsMapPath      = "build/soong/cc/config"
	cfiExportsMapFilename  = "cfi_exports.map"
	cfiAssemblySupportFlag = "-fno-sanitize-cfi-canonical-jump-tables"

	intOverflowCflags = []string{"-fsanitize-ignorelist=build/soong/cc/config/integer_overflow_blocklist.txt"}

	minimalRuntimeFlags = []string{"-fsanitize-minimal-runtime", "-fno-sanitize-trap=integer,undefined",
		"-fno-sanitize-recover=integer,undefined"}
	memtagStackCommonFlags = []string{"-Xclang -target-feature -Xclang +mte"}
	memtagStackLlvmFlags   = []string{"-dom-tree-reachability-max-bbs-to-explore=128"}

	hostOnlySanitizeFlags   = []string{"-fno-sanitize-recover=all"}
	deviceOnlySanitizeFlags = []string{"-fsanitize-trap=all"}

	noSanitizeLinkRuntimeFlag = "-fno-sanitize-link-runtime"
)

type SanitizerType int

const (
	Asan SanitizerType = iota + 1
	Hwasan
	tsan
	intOverflow
	scs
	Fuzzer
	Memtag_heap
	Memtag_stack
	Memtag_globals
	cfi // cfi is last to prevent it running before incompatible mutators
)

var Sanitizers = []SanitizerType{
	Asan,
	Hwasan,
	tsan,
	intOverflow,
	scs,
	Fuzzer,
	Memtag_heap,
	Memtag_stack,
	Memtag_globals,
	cfi, // cfi is last to prevent it running before incompatible mutators
}

// Name of the sanitizer variation for this sanitizer type
func (t SanitizerType) variationName() string {
	switch t {
	case Asan:
		return "asan"
	case Hwasan:
		return "hwasan"
	case tsan:
		return "tsan"
	case intOverflow:
		return "intOverflow"
	case cfi:
		return "cfi"
	case scs:
		return "scs"
	case Memtag_heap:
		return "memtag_heap"
	case Memtag_stack:
		return "memtag_stack"
	case Memtag_globals:
		return "memtag_globals"
	case Fuzzer:
		return "fuzzer"
	default:
		panic(fmt.Errorf("unknown SanitizerType %d", t))
	}
}

// This is the sanitizer names in SANITIZE_[TARGET|HOST]
func (t SanitizerType) name() string {
	switch t {
	case Asan:
		return "address"
	case Hwasan:
		return "hwaddress"
	case Memtag_heap:
		return "memtag_heap"
	case Memtag_stack:
		return "memtag_stack"
	case Memtag_globals:
		return "memtag_globals"
	case tsan:
		return "thread"
	case intOverflow:
		return "integer_overflow"
	case cfi:
		return "cfi"
	case scs:
		return "shadow-call-stack"
	case Fuzzer:
		return "fuzzer"
	default:
		panic(fmt.Errorf("unknown SanitizerType %d", t))
	}
}

func (t SanitizerType) registerMutators(ctx android.RegisterMutatorsContext) {
	switch t {
	case cfi, Hwasan, Asan, tsan, Fuzzer, scs, Memtag_stack:
		sanitizer := &sanitizerSplitMutator{t}
		ctx.Transition(t.variationName(), sanitizer)
	case Memtag_heap, Memtag_globals, intOverflow:
		// do nothing
	default:
		panic(fmt.Errorf("unknown SanitizerType %d", t))
	}
}

// shouldPropagateToSharedLibraryDeps returns whether a sanitizer type should propagate to share
// dependencies. In most cases, sanitizers only propagate to static dependencies; however, some
// sanitizers also must be enabled for shared libraries for linking.
func (t SanitizerType) shouldPropagateToSharedLibraryDeps() bool {
	switch t {
	case Fuzzer:
		// Typically, shared libs are not split. However, for fuzzer, we split even for shared libs
		// because a library sanitized for fuzzer can't be linked from a library that isn't sanitized
		// for fuzzer.
		return true
	default:
		return false
	}
}
func (*Module) SanitizerSupported(t SanitizerType) bool {
	switch t {
	case Asan:
		return true
	case Hwasan:
		return true
	case tsan:
		return true
	case intOverflow:
		return true
	case cfi:
		return true
	case scs:
		return true
	case Fuzzer:
		return true
	case Memtag_heap:
		return true
	case Memtag_stack:
		return true
	case Memtag_globals:
		return true
	default:
		return false
	}
}

// incompatibleWithCfi returns true if a sanitizer is incompatible with CFI.
func (t SanitizerType) incompatibleWithCfi() bool {
	return t == Asan || t == Fuzzer || t == Hwasan
}

// @auto-generate: gob
type SanitizeUserProps struct {
	// Prevent use of any sanitizers on this module
	Never *bool `android:"arch_variant"`

	// ASan (Address sanitizer), incompatible with static binaries.
	// Always runs in a diagnostic mode.
	// Use of address sanitizer disables cfi sanitizer.
	// Hwaddress sanitizer takes precedence over this sanitizer.
	Address *bool `android:"arch_variant"`
	// TSan (Thread sanitizer), incompatible with static binaries and 32 bit architectures.
	// Always runs in a diagnostic mode.
	// Use of thread sanitizer disables cfi and scudo sanitizers.
	// Hwaddress sanitizer takes precedence over this sanitizer.
	Thread *bool `android:"arch_variant"`
	// HWASan (Hardware Address sanitizer).
	// Use of hwasan sanitizer disables cfi, address, thread, and scudo sanitizers.
	Hwaddress *bool `android:"arch_variant"`

	// Undefined behavior sanitizer
	All_undefined *bool `android:"arch_variant"`
	// Subset of undefined behavior sanitizer
	Undefined *bool `android:"arch_variant"`
	// List of specific undefined behavior sanitizers to enable
	Misc_undefined []string `android:"arch_variant"`
	// Fuzzer, incompatible with static binaries.
	Fuzzer *bool `android:"arch_variant"`
	// safe-stack sanitizer, incompatible with 32-bit architectures.
	Safestack *bool `android:"arch_variant"`
	// cfi sanitizer, incompatible with asan, hwasan, fuzzer, or Darwin
	Cfi *bool `android:"arch_variant"`
	// signed/unsigned integer overflow sanitizer, incompatible with Darwin.
	Integer_overflow *bool `android:"arch_variant"`
	// scudo sanitizer, incompatible with asan, hwasan, tsan
	// This should not be used in Android 11+ : https://source.android.com/devices/tech/debug/scudo
	// deprecated
	Scudo *bool `android:"arch_variant"`
	// shadow-call-stack sanitizer, only available on arm64/riscv64.
	Scs *bool `android:"arch_variant"`
	// Memory-tagging, only available on arm64
	// if diag.memtag unset or false, enables async memory tagging
	Memtag_heap *bool `android:"arch_variant"`
	// Memory-tagging stack instrumentation, only available on arm64
	// Adds instrumentation to detect stack buffer overflows and use-after-scope using MTE.
	Memtag_stack *bool `android:"arch_variant"`
	// Memory-tagging globals instrumentation, only available on arm64
	// Adds instrumentation to detect global buffer overflows using MTE.
	Memtag_globals *bool `android:"arch_variant"`

	// A modifier for ASAN and HWASAN for write only instrumentation
	Writeonly *bool `android:"arch_variant"`

	// Sanitizers to run in the diagnostic mode (as opposed to the release mode).
	// Replaces abort() on error with a human-readable error message.
	// Address and Thread sanitizers always run in diagnostic mode.
	Diag struct {
		// Undefined behavior sanitizer, diagnostic mode
		Undefined *bool `android:"arch_variant"`
		// cfi sanitizer, diagnostic mode, incompatible with asan, hwasan, fuzzer, or Darwin
		Cfi *bool `android:"arch_variant"`
		// signed/unsigned integer overflow sanitizer, diagnostic mode, incompatible with Darwin.
		Integer_overflow *bool `android:"arch_variant"`
		// Memory-tagging, only available on arm64
		// requires sanitizer.memtag: true
		// if set, enables sync memory tagging
		Memtag_heap *bool `android:"arch_variant"`
		// List of specific undefined behavior sanitizers to enable in diagnostic mode
		Misc_undefined []string `android:"arch_variant"`
		// List of sanitizers to pass to -fno-sanitize-recover
		// results in only the first detected error for these sanitizers being reported and program then
		// exits with a non-zero exit code.
		No_recover []string `android:"arch_variant"`
	} `android:"arch_variant"`

	// Sanitizers to run with flag configuration specified
	Config struct {
		// Enables CFI support flags for assembly-heavy libraries
		Cfi_assembly_support *bool `android:"arch_variant"`
	} `android:"arch_variant"`

	// List of sanitizers to pass to -fsanitize-recover
	// allows execution to continue for these sanitizers to detect multiple errors rather than only
	// the first one
	Recover []string

	// value to pass to -fsanitize-ignorelist
	Blocklist *string
}

type sanitizeMutatedProperties struct {
	// Whether sanitizers can be enabled on this module
	Never *bool `blueprint:"mutated"`

	// Whether ASan (Address sanitizer) is enabled for this module.
	// Hwaddress sanitizer takes precedence over this sanitizer.
	Address *bool `blueprint:"mutated"`
	// Whether TSan (Thread sanitizer) is enabled for this module
	Thread *bool `blueprint:"mutated"`
	// Whether HWASan (Hardware Address sanitizer) is enabled for this module
	Hwaddress *bool `blueprint:"mutated"`

	// Whether Undefined behavior sanitizer is enabled for this module
	All_undefined *bool `blueprint:"mutated"`
	// Whether undefined behavior sanitizer subset is enabled for this module
	Undefined *bool `blueprint:"mutated"`
	// List of specific undefined behavior sanitizers enabled for this module
	Misc_undefined []string `blueprint:"mutated"`
	// Whether Fuzzeris enabled for this module
	Fuzzer *bool `blueprint:"mutated"`
	// whether safe-stack sanitizer is enabled for this module
	Safestack *bool `blueprint:"mutated"`
	// Whether cfi sanitizer is enabled for this module
	Cfi *bool `blueprint:"mutated"`
	// Whether signed/unsigned integer overflow sanitizer is enabled for this module
	Integer_overflow *bool `blueprint:"mutated"`
	// Whether scudo sanitizer is enabled for this module
	Scudo *bool `blueprint:"mutated"`
	// Whether shadow-call-stack sanitizer is enabled for this module.
	Scs *bool `blueprint:"mutated"`
	// Whether Memory-tagging is enabled for this module
	Memtag_heap *bool `blueprint:"mutated"`
	// Whether Memory-tagging stack instrumentation is enabled for this module
	Memtag_stack *bool `blueprint:"mutated"`
	// Whether Memory-tagging globals instrumentation is enabled for this module
	Memtag_globals *bool `android:"arch_variant"`

	// Whether a modifier for ASAN and HWASAN for write only instrumentation is enabled for this
	// module
	Writeonly *bool `blueprint:"mutated"`

	// Sanitizers to run in the diagnostic mode (as opposed to the release mode).
	Diag struct {
		// Whether Undefined behavior sanitizer, diagnostic mode is enabled for this module
		Undefined *bool `blueprint:"mutated"`
		// Whether cfi sanitizer, diagnostic mode is enabled for this module
		Cfi *bool `blueprint:"mutated"`
		// Whether signed/unsigned integer overflow sanitizer, diagnostic mode is enabled for this
		// module
		Integer_overflow *bool `blueprint:"mutated"`
		// Whether Memory-tagging, diagnostic mode is enabled for this module
		Memtag_heap *bool `blueprint:"mutated"`
		// List of specific undefined behavior sanitizers enabled in diagnostic mode
		Misc_undefined []string `blueprint:"mutated"`
	} `blueprint:"mutated"`
}

type SanitizeProperties struct {
	Sanitize        SanitizeUserProps         `android:"arch_variant"`
	SanitizeMutated sanitizeMutatedProperties `blueprint:"mutated"`

	// ForceDisable is set by the version mutator to disable sanitization of stubs variants
	ForceDisable bool `blueprint:"mutated"`

	// SanitizerEnabled is set by begin() if any of the sanitize boolean properties are set after
	// applying the logic that enables globally enabled sanitizers and disables any unsupported
	// sanitizers.
	// TODO(b/349906293): this has some unintuitive behavior.  It is set in begin() before the sanitize
	//  mutator is run if any of the individual sanitizes  properties are set, and then the individual
	//  sanitize properties are cleared in the non-sanitized variants, but this value is never cleared.
	//  That results in SanitizerEnabled being set in variants that have no sanitizers enabled, causing
	//  some of the sanitizer logic in flags() to be applied to the non-sanitized variant.
	SanitizerEnabled bool `blueprint:"mutated"`

	MinimalRuntimeDep bool     `blueprint:"mutated"`
	UbsanRuntimeDep   bool     `blueprint:"mutated"`
	InSanitizerDir    bool     `blueprint:"mutated"`
	Sanitizers        []string `blueprint:"mutated"`
	DiagSanitizers    []string `blueprint:"mutated"`
}

type sanitize struct {
	Properties SanitizeProperties
}

// Mark this tag with a check to see if apex dependency check should be skipped
func (t libraryDependencyTag) SkipApexAllowedDependenciesCheck() bool {
	return t.skipApexAllowedDependenciesCheck
}

var _ android.SkipApexAllowedDependenciesCheck = (*libraryDependencyTag)(nil)

func init() {
	pctx.StaticVariable("HostOnlySanitizeFlags", strings.Join(hostOnlySanitizeFlags, " "))

	android.RegisterMakeVarsProvider(pctx, cfiMakeVarsProvider)
	android.RegisterMakeVarsProvider(pctx, hwasanMakeVarsProvider)
	android.RegisterMakeVarsProvider(pctx, memtagStackMakeVarsProvider)

	RegisterSanitizerLibrariesTxtType(android.InitRegistrationContext)
}

func (sanitize *sanitize) props() []interface{} {
	return []interface{}{&sanitize.Properties}
}

func (p *sanitizeMutatedProperties) copyUserPropertiesToMutated(userProps *SanitizeUserProps) {
	p.Never = userProps.Never
	p.Address = userProps.Address
	p.All_undefined = userProps.All_undefined
	p.Cfi = userProps.Cfi
	p.Fuzzer = userProps.Fuzzer
	p.Hwaddress = userProps.Hwaddress
	p.Integer_overflow = userProps.Integer_overflow
	p.Memtag_heap = userProps.Memtag_heap
	p.Memtag_stack = userProps.Memtag_stack
	p.Memtag_globals = userProps.Memtag_globals
	p.Safestack = userProps.Safestack
	p.Scs = userProps.Scs
	p.Scudo = userProps.Scudo
	p.Thread = userProps.Thread
	p.Undefined = userProps.Undefined
	p.Writeonly = userProps.Writeonly

	p.Misc_undefined = make([]string, 0, len(userProps.Misc_undefined))
	for _, v := range userProps.Misc_undefined {
		p.Misc_undefined = append(p.Misc_undefined, v)
	}

	p.Diag.Cfi = userProps.Diag.Cfi
	p.Diag.Integer_overflow = userProps.Diag.Integer_overflow
	p.Diag.Memtag_heap = userProps.Diag.Memtag_heap
	p.Diag.Undefined = userProps.Diag.Undefined

	p.Diag.Misc_undefined = make([]string, 0, len(userProps.Diag.Misc_undefined))
	for _, v := range userProps.Diag.Misc_undefined {
		p.Diag.Misc_undefined = append(p.Diag.Misc_undefined, v)
	}
}

func (sanitize *sanitize) begin(ctx BaseModuleContext) {
	s := &sanitize.Properties.SanitizeMutated
	s.copyUserPropertiesToMutated(&sanitize.Properties.Sanitize)

	if sanitize.Properties.ForceDisable {
		return
	}

	// Don't apply sanitizers to NDK code.
	if ctx.useSdk() {
		s.Never = BoolPtr(true)
	}

	// Never always wins.
	if Bool(s.Never) {
		return
	}

	// cc_test targets default to SYNC MemTag unless explicitly set to ASYNC (via diag: {memtag_heap: false}).
	if ctx.testBinary() {
		if s.Memtag_heap == nil {
			s.Memtag_heap = proptools.BoolPtr(true)
		}
		if s.Diag.Memtag_heap == nil {
			s.Diag.Memtag_heap = proptools.BoolPtr(true)
		}
	}

	var globalSanitizers []string
	var globalSanitizersDiag []string

	if ctx.Host() {
		if !ctx.Windows() {
			globalSanitizers = ctx.Config().SanitizeHost()
		}
	} else {
		arches := ctx.Config().SanitizeDeviceArch()
		if len(arches) == 0 || inList(ctx.Arch().ArchType.Name, arches) {
			globalSanitizers = ctx.Config().SanitizeDevice()
			globalSanitizersDiag = ctx.Config().SanitizeDeviceDiag()
		}
	}

	if len(globalSanitizers) > 0 {
		var found bool
		if found, globalSanitizers = removeFromList("undefined", globalSanitizers); found && s.All_undefined == nil {
			s.All_undefined = proptools.BoolPtr(true)
		}

		if found, globalSanitizers = removeFromList("default-ub", globalSanitizers); found && s.Undefined == nil {
			s.Undefined = proptools.BoolPtr(true)
		}

		if found, globalSanitizers = removeFromList("address", globalSanitizers); found && s.Address == nil {
			s.Address = proptools.BoolPtr(true)
		}

		if found, globalSanitizers = removeFromList("thread", globalSanitizers); found && s.Thread == nil {
			s.Thread = proptools.BoolPtr(true)
		}

		if found, globalSanitizers = removeFromList("fuzzer", globalSanitizers); found && s.Fuzzer == nil {
			s.Fuzzer = proptools.BoolPtr(true)
		}

		if found, globalSanitizers = removeFromList("safe-stack", globalSanitizers); found && s.Safestack == nil {
			s.Safestack = proptools.BoolPtr(true)
		}

		if found, globalSanitizers = removeFromList("cfi", globalSanitizers); found && s.Cfi == nil {
			if !ctx.Config().CFIDisabledForPath(ctx.ModuleDir()) {
				s.Cfi = proptools.BoolPtr(true)
			}
		}

		// Global integer_overflow builds do not support static libraries.
		if found, globalSanitizers = removeFromList("integer_overflow", globalSanitizers); found && s.Integer_overflow == nil {
			if !ctx.Config().IntegerOverflowDisabledForPath(ctx.ModuleDir()) && !ctx.static() {
				s.Integer_overflow = proptools.BoolPtr(true)
			}
		}

		if found, globalSanitizers = removeFromList("scudo", globalSanitizers); found && s.Scudo == nil {
			s.Scudo = proptools.BoolPtr(true)
		}

		if found, globalSanitizers = removeFromList("hwaddress", globalSanitizers); found && s.Hwaddress == nil {
			if !ctx.Config().HWASanDisabledForPath(ctx.ModuleDir()) {
				s.Hwaddress = proptools.BoolPtr(true)
			}
		}

		if found, globalSanitizers = removeFromList("writeonly", globalSanitizers); found && s.Writeonly == nil {
			// Hwaddress and Address are set before, so we can check them here
			// If they aren't explicitly set in the blueprint/SANITIZE_(HOST|TARGET), they would be nil instead of false
			if s.Address == nil && s.Hwaddress == nil {
				ctx.ModuleErrorf("writeonly modifier cannot be used without 'address' or 'hwaddress'")
			}
			s.Writeonly = proptools.BoolPtr(true)
		}
		if found, globalSanitizers = removeFromList("memtag_heap", globalSanitizers); found && s.Memtag_heap == nil {
			if !ctx.Config().MemtagHeapDisabledForPath(ctx.ModuleDir()) {
				s.Memtag_heap = proptools.BoolPtr(true)
			}
		}

		if found, globalSanitizers = removeFromList("memtag_stack", globalSanitizers); found && s.Memtag_stack == nil {
			s.Memtag_stack = proptools.BoolPtr(true)
		}

		if found, globalSanitizers = removeFromList("memtag_globals", globalSanitizers); found && s.Memtag_globals == nil {
			s.Memtag_globals = proptools.BoolPtr(true)
		}

		if len(globalSanitizers) > 0 {
			ctx.ModuleErrorf("unknown global sanitizer option %s", globalSanitizers[0])
		}

		// Global integer_overflow builds do not support static library diagnostics.
		if found, globalSanitizersDiag = removeFromList("integer_overflow", globalSanitizersDiag); found &&
			s.Diag.Integer_overflow == nil && Bool(s.Integer_overflow) && !ctx.static() {
			s.Diag.Integer_overflow = proptools.BoolPtr(true)
		}

		if found, globalSanitizersDiag = removeFromList("cfi", globalSanitizersDiag); found &&
			s.Diag.Cfi == nil && Bool(s.Cfi) {
			s.Diag.Cfi = proptools.BoolPtr(true)
		}

		if found, globalSanitizersDiag = removeFromList("memtag_heap", globalSanitizersDiag); found &&
			s.Diag.Memtag_heap == nil && Bool(s.Memtag_heap) {
			s.Diag.Memtag_heap = proptools.BoolPtr(true)
		}

		if len(globalSanitizersDiag) > 0 {
			ctx.ModuleErrorf("unknown global sanitizer diagnostics option %s", globalSanitizersDiag[0])
		}
	}

	// Enable Memtag for all components in the include paths (for Aarch64 only)
	if ctx.Arch().ArchType == android.Arm64 && ctx.toolchain().Bionic() {
		if ctx.Config().MemtagHeapSyncEnabledForPath(ctx.ModuleDir()) {
			if s.Memtag_heap == nil {
				s.Memtag_heap = proptools.BoolPtr(true)
			}
			if s.Diag.Memtag_heap == nil {
				s.Diag.Memtag_heap = proptools.BoolPtr(true)
			}
		} else if ctx.Config().MemtagHeapAsyncEnabledForPath(ctx.ModuleDir()) {
			if s.Memtag_heap == nil {
				s.Memtag_heap = proptools.BoolPtr(true)
			}
		}
	}

	// Enable HWASan for all components in the include paths (for Aarch64 only)
	if s.Hwaddress == nil && ctx.Config().HWASanEnabledForPath(ctx.ModuleDir()) &&
		ctx.Arch().ArchType == android.Arm64 && ctx.toolchain().Bionic() {
		s.Hwaddress = proptools.BoolPtr(true)
	}

	// Enable CFI for non-host components in the include paths
	if s.Cfi == nil && ctx.Config().CFIEnabledForPath(ctx.ModuleDir()) && !ctx.Host() {
		s.Cfi = proptools.BoolPtr(true)
		if inList("cfi", ctx.Config().SanitizeDeviceDiag()) {
			s.Diag.Cfi = proptools.BoolPtr(true)
		}
	}

	// Is CFI actually enabled?
	if !ctx.Config().EnableCFI() {
		s.Cfi = nil
		s.Diag.Cfi = nil
	}

	// HWASan requires AArch64 hardware feature (top-byte-ignore).
	if ctx.Arch().ArchType != android.Arm64 || !ctx.toolchain().Bionic() {
		s.Hwaddress = nil
	}

	// SCS is only implemented on AArch64/riscv64.
	if (ctx.Arch().ArchType != android.Arm64 && ctx.Arch().ArchType != android.Riscv64) || !ctx.toolchain().Bionic() {
		s.Scs = nil
	}

	// Memtag_heap is only implemented on AArch64.
	// Memtag ABI is Android specific for now, so disable for host.
	if ctx.Arch().ArchType != android.Arm64 || !ctx.toolchain().Bionic() || ctx.Host() {
		s.Memtag_heap = nil
		s.Memtag_stack = nil
		s.Memtag_globals = nil
	}

	// Also disable CFI if ASAN is enabled.
	if Bool(s.Address) || Bool(s.Hwaddress) {
		s.Cfi = nil
		s.Diag.Cfi = nil
		// HWASAN and ASAN win against MTE.
		s.Memtag_heap = nil
		s.Memtag_stack = nil
		s.Memtag_globals = nil
	}

	// Disable sanitizers that depend on the UBSan runtime for windows/darwin builds.
	if !ctx.Os().Linux() {
		s.Cfi = nil
		s.Diag.Cfi = nil
		s.Misc_undefined = nil
		s.Undefined = nil
		s.All_undefined = nil
		s.Integer_overflow = nil
	}

	// Disable CFI for musl
	if ctx.toolchain().Musl() {
		s.Cfi = nil
		s.Diag.Cfi = nil
	}

	// TODO(b/280478629): runtimes don't exist for musl arm64 yet.
	if ctx.toolchain().Musl() && ctx.Arch().ArchType == android.Arm64 {
		s.Address = nil
		s.Hwaddress = nil
		s.Thread = nil
		s.Scudo = nil
		s.Fuzzer = nil
		s.Cfi = nil
		s.Diag.Cfi = nil
		s.Misc_undefined = nil
		s.Undefined = nil
		s.All_undefined = nil
		s.Integer_overflow = nil
	}

	if ctx.inRamdisk() || ctx.inVendorRamdisk() || ctx.inRecovery() {
		// HWASan ramdisk (which is built from recovery) goes over some bootloader limit.
		// Keep libc instrumented so that ramdisk / vendor_ramdisk / recovery can run hwasan-instrumented code if necessary.
		if !strings.HasPrefix(ctx.ModuleDir(), "bionic/libc") {
			s.Hwaddress = nil
		}
		// Memtag stack in ramdisk makes pKVM unhappy.
		s.Memtag_stack = nil
	}

	if ctx.staticBinary() {
		s.Address = nil
		s.Fuzzer = nil
		s.Thread = nil
	}

	if Bool(s.All_undefined) {
		s.Undefined = nil
	}

	if !ctx.toolchain().Is64Bit() {
		// TSAN and SafeStack are not supported on 32-bit architectures
		s.Thread = nil
		s.Safestack = nil
		// TODO(ccross): error for compile_multilib = "32"?
	}

	if ctx.Os() != android.Windows && (Bool(s.All_undefined) || Bool(s.Undefined) || Bool(s.Address) || Bool(s.Thread) ||
		Bool(s.Fuzzer) || Bool(s.Safestack) || Bool(s.Cfi) || Bool(s.Integer_overflow) || len(s.Misc_undefined) > 0 ||
		Bool(s.Scudo) || Bool(s.Hwaddress) || Bool(s.Scs) || Bool(s.Memtag_heap) || Bool(s.Memtag_stack) ||
		Bool(s.Memtag_globals)) {
		sanitize.Properties.SanitizerEnabled = true
	}

	// Disable Scudo if ASan or TSan is enabled, or if it's disabled globally.
	if Bool(s.Address) || Bool(s.Thread) || Bool(s.Hwaddress) || ctx.Config().DisableScudo() {
		s.Scudo = nil
	}

	if Bool(s.Hwaddress) {
		s.Address = nil
		s.Thread = nil
	}

	// TODO(b/131771163): CFI transiently depends on LTO, and thus Fuzzer is
	// mutually incompatible.
	if Bool(s.Fuzzer) {
		s.Cfi = nil
	}
}

func toDisableImplicitIntegerChange(flags []string) bool {
	// Returns true if any flag is fsanitize*integer, and there is
	// no explicit flag about sanitize=implicit-integer-sign-change.
	for _, f := range flags {
		if strings.Contains(f, "sanitize=implicit-integer-sign-change") {
			return false
		}
	}
	for _, f := range flags {
		if strings.HasPrefix(f, "-fsanitize") && strings.Contains(f, "integer") {
			return true
		}
	}
	return false
}

func toDisableUnsignedShiftBaseChange(flags []string) bool {
	// Returns true if any flag is fsanitize*integer, and there is
	// no explicit flag about sanitize=unsigned-shift-base.
	for _, f := range flags {
		if strings.Contains(f, "sanitize=unsigned-shift-base") {
			return false
		}
	}
	for _, f := range flags {
		if strings.HasPrefix(f, "-fsanitize") && strings.Contains(f, "integer") {
			return true
		}
	}
	return false
}

func (s *sanitize) flags(ctx ModuleContext, flags Flags) Flags {
	if s.Properties.ForceDisable {
		return flags
	}

	if !s.Properties.SanitizerEnabled && !s.Properties.UbsanRuntimeDep {
		flags.Local.CFlags = append(flags.Local.CFlags, "-fwrapv")
		return flags
	}

	wrapv := true
	for _, san := range s.Properties.Sanitizers {
		if san == "signed-integer-overflow" || san == "integer" || san == "undefined" {
			wrapv = false
			break
		}
	}

	if wrapv {
		flags.Local.CFlags = append(flags.Local.CFlags, "-fwrapv")
	}

	sanProps := &s.Properties.SanitizeMutated

	if Bool(sanProps.Address) {
		if ctx.Arch().ArchType == android.Arm {
			// Frame pointer based unwinder in ASan requires ARM frame setup.
			// TODO: put in flags?
			flags.RequiredInstructionSet = "arm"
		}
		flags.Local.CFlags = append(flags.Local.CFlags, asanCflags...)

		if Bool(sanProps.Writeonly) {
			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", "-asan-instrument-reads=0")
		}

		if ctx.Host() {
			if !ctx.Darwin() { // ld64.lld doesn't know about '--no-as-needed'
				// -nodefaultlibs (provided with libc++) prevents the driver from linking
				// libraries needed with -fsanitize=address. http://b/18650275 (WAI)
				flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--no-as-needed")
			}
		} else {
			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", "-asan-globals=0")
			if ctx.bootstrap() {
				flags.DynamicLinker = "/system/bin/bootstrap/linker_asan"
			} else {
				flags.DynamicLinker = "/system/bin/linker_asan"
			}
			if flags.Toolchain.Is64Bit() {
				flags.DynamicLinker += "64"
			}
		}
	}

	if Bool(sanProps.Hwaddress) {
		flags.Local.CFlags = append(flags.Local.CFlags, hwasanCflags...)

		for _, flag := range hwasanCommonflags {
			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", flag)
		}
		for _, flag := range hwasanCommonflags {
			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,"+flag)
		}

		if Bool(sanProps.Writeonly) {
			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", "-hwasan-instrument-reads=0")
		}
		if !ctx.staticBinary() && !ctx.Host() {
			if ctx.bootstrap() {
				flags.DynamicLinker = "/system/bin/bootstrap/linker_hwasan64"
			} else {
				flags.DynamicLinker = "/system/bin/linker_hwasan64"
			}
		}
	}

	if Bool(sanProps.Fuzzer) {
		flags.Local.CFlags = append(flags.Local.CFlags, "-fsanitize=fuzzer-no-link")

		// TODO(b/131771163): LTO and Fuzzer support is mutually incompatible.
		_, flags.Local.LdFlags = removeFromList("-flto", flags.Local.LdFlags)
		_, flags.Local.CFlags = removeFromList("-flto", flags.Local.CFlags)
		flags.Local.LdFlags = append(flags.Local.LdFlags, "-fno-lto")
		flags.Local.CFlags = append(flags.Local.CFlags, "-fno-lto")

		// TODO(b/142430592): Upstream linker scripts for sanitizer runtime libraries
		// discard the sancov_lowest_stack symbol, because it's emulated TLS (and thus
		// doesn't match the linker script due to the "__emutls_v." prefix).
		flags.Local.LdFlags = append(flags.Local.LdFlags, "-fno-sanitize-coverage=stack-depth")
		flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-coverage=stack-depth")

		// Disable fortify for fuzzing builds. Generally, we'll be building with
		// UBSan or ASan here and the fortify checks pollute the stack traces.
		flags.Local.CFlags = append(flags.Local.CFlags, "-U_FORTIFY_SOURCE")

		// Build fuzzer-sanitized libraries with an $ORIGIN DT_RUNPATH. Android's
		// linker uses DT_RUNPATH, not DT_RPATH. When we deploy cc_fuzz targets and
		// their libraries to /data/fuzz/<arch>/lib, any transient shared library gets
		// the DT_RUNPATH from the shared library above it, and not the executable,
		// meaning that the lookup falls back to the system. Adding the $ORIGIN to the
		// DT_RUNPATH here means that transient shared libraries can be found
		// colocated with their parents.
		flags.Local.LdFlags = append(flags.Local.LdFlags, `-Wl,-rpath,\$$ORIGIN`)
	}

	if Bool(sanProps.Cfi) {
		if ctx.Arch().ArchType == android.Arm {
			// __cfi_check needs to be built as Thumb (see the code in linker_cfi.cpp). LLVM is not set up
			// to do this on a function basis, so force Thumb on the entire module.
			flags.RequiredInstructionSet = "thumb"
		}

		flags.Local.CFlags = append(flags.Local.CFlags, cfiCflags...)
		flags.Local.AsFlags = append(flags.Local.AsFlags, cfiAsflags...)
		flags.CFlagsDeps = append(flags.CFlagsDeps, android.PathForSource(ctx, cfiBlocklistPath+"/"+cfiBlocklistFilename))
		if Bool(s.Properties.Sanitize.Config.Cfi_assembly_support) {
			flags.Local.CFlags = append(flags.Local.CFlags, cfiAssemblySupportFlag)
		}
		// Only append the default visibility flag if -fvisibility has not already been set
		// to hidden.
		if !inList("-fvisibility=hidden", flags.Local.CFlags) {
			flags.Local.CFlags = append(flags.Local.CFlags, "-fvisibility=default")
		}
		flags.Local.LdFlags = append(flags.Local.LdFlags, cfiLdflags...)

		if ctx.staticBinary() {
			_, flags.Local.CFlags = removeFromList("-fsanitize-cfi-cross-dso", flags.Local.CFlags)
			_, flags.Local.LdFlags = removeFromList("-fsanitize-cfi-cross-dso", flags.Local.LdFlags)
		}
	}

	if Bool(sanProps.Memtag_stack) {
		flags.Local.CFlags = append(flags.Local.CFlags, memtagStackCommonFlags...)
		flags.Local.AsFlags = append(flags.Local.AsFlags, memtagStackCommonFlags...)
		flags.Local.LdFlags = append(flags.Local.LdFlags, memtagStackCommonFlags...)

		for _, flag := range memtagStackLlvmFlags {
			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", flag)
		}
		for _, flag := range memtagStackLlvmFlags {
			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,"+flag)
		}
	}

	if (Bool(sanProps.Memtag_heap) || Bool(sanProps.Memtag_stack) || Bool(sanProps.Memtag_globals)) && ctx.binary() {
		if Bool(sanProps.Diag.Memtag_heap) {
			flags.Local.LdFlags = append(flags.Local.LdFlags, "-fsanitize-memtag-mode=sync")
		} else {
			flags.Local.LdFlags = append(flags.Local.LdFlags, "-fsanitize-memtag-mode=async")
		}
	}

	if Bool(sanProps.Integer_overflow) {
		flags.Local.CFlags = append(flags.Local.CFlags, intOverflowCflags...)
	}

	if len(s.Properties.Sanitizers) > 0 {
		sanitizeArg := "-fsanitize=" + strings.Join(s.Properties.Sanitizers, ",")
		flags.Local.CFlags = append(flags.Local.CFlags, sanitizeArg)
		flags.Local.AsFlags = append(flags.Local.AsFlags, sanitizeArg)
		flags.Local.LdFlags = append(flags.Local.LdFlags, sanitizeArg)

		if ctx.toolchain().Bionic() || ctx.toolchain().Musl() {
			// Bionic and musl sanitizer runtimes have already been added as dependencies so that
			// the right variant of the runtime will be used (with the "-android" or "-musl"
			// suffixes), so don't let clang the runtime library.
			flags.Local.LdFlags = append(flags.Local.LdFlags, noSanitizeLinkRuntimeFlag)
		} else {
			// Host sanitizers only link symbols in the final executable, so
			// there will always be undefined symbols in intermediate libraries.
			_, flags.Global.LdFlags = removeFromList("-Wl,--no-undefined", flags.Global.LdFlags)
		}

		if !ctx.toolchain().Bionic() {
			// non-Bionic toolchain prebuilts are missing UBSan's vptr and function san.
			// Musl toolchain prebuilts have vptr and function sanitizers, but enabling them
			// implicitly enables RTTI which causes RTTI mismatch issues with dependencies.

			flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize=vptr,function")
		}

		if Bool(sanProps.Fuzzer) {
			// When fuzzing, we wish to crash with diagnostics on any bug.
			flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-trap=all", "-fno-sanitize-recover=all")
		} else if ctx.Host() {
			flags.Local.CFlags = append(flags.Local.CFlags, hostOnlySanitizeFlags...)
		} else {
			flags.Local.CFlags = append(flags.Local.CFlags, deviceOnlySanitizeFlags...)
		}

		if enableMinimalRuntime(s) {
			flags.Local.CFlags = append(flags.Local.CFlags, strings.Join(minimalRuntimeFlags, " "))
		}

		// http://b/119329758, Android core does not boot up with this sanitizer yet.
		if toDisableImplicitIntegerChange(flags.Local.CFlags) {
			flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize=implicit-integer-sign-change")
		}
		// http://b/171275751, Android doesn't build with this sanitizer yet.
		if toDisableUnsignedShiftBaseChange(flags.Local.CFlags) {
			flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize=unsigned-shift-base")
		}
	}

	if len(s.Properties.DiagSanitizers) > 0 {
		flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-trap="+strings.Join(s.Properties.DiagSanitizers, ","))
	}
	// FIXME: enable RTTI if diag + (cfi or vptr)

	if s.Properties.Sanitize.Recover != nil {
		flags.Local.CFlags = append(flags.Local.CFlags, "-fsanitize-recover="+
			strings.Join(s.Properties.Sanitize.Recover, ","))
	}

	if s.Properties.Sanitize.Diag.No_recover != nil {
		flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-recover="+
			strings.Join(s.Properties.Sanitize.Diag.No_recover, ","))
	}

	blocklist := android.OptionalPathForModuleSrc(ctx, s.Properties.Sanitize.Blocklist)
	if blocklist.Valid() {
		flags.Local.CFlags = append(flags.Local.CFlags, sanitizeIgnorelistPrefix+blocklist.String())
		flags.CFlagsDeps = append(flags.CFlagsDeps, blocklist.Path())
	}

	return flags
}

func (s *sanitize) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
	// Add a suffix for cfi/hwasan/scs-enabled static/header libraries to allow surfacing
	// both the sanitized and non-sanitized variants to make without a name conflict.
	if entries.Class == "STATIC_LIBRARIES" || entries.Class == "HEADER_LIBRARIES" {
		if Bool(s.Properties.SanitizeMutated.Cfi) {
			entries.SubName += ".cfi"
		}
		if Bool(s.Properties.SanitizeMutated.Hwaddress) {
			entries.SubName += ".hwasan"
		}
		if Bool(s.Properties.SanitizeMutated.Scs) {
			entries.SubName += ".scs"
		}
	}
}

func (s *sanitize) inSanitizerDir() bool {
	return s.Properties.InSanitizerDir
}

// getSanitizerBoolPtr returns the SanitizerTypes associated bool pointer from SanitizeProperties.
func (s *sanitize) getSanitizerBoolPtr(t SanitizerType) *bool {
	switch t {
	case Asan:
		return s.Properties.SanitizeMutated.Address
	case Hwasan:
		return s.Properties.SanitizeMutated.Hwaddress
	case tsan:
		return s.Properties.SanitizeMutated.Thread
	case intOverflow:
		return s.Properties.SanitizeMutated.Integer_overflow
	case cfi:
		return s.Properties.SanitizeMutated.Cfi
	case scs:
		return s.Properties.SanitizeMutated.Scs
	case Memtag_heap:
		return s.Properties.SanitizeMutated.Memtag_heap
	case Memtag_stack:
		return s.Properties.SanitizeMutated.Memtag_stack
	case Memtag_globals:
		return s.Properties.SanitizeMutated.Memtag_globals
	case Fuzzer:
		return s.Properties.SanitizeMutated.Fuzzer
	default:
		panic(fmt.Errorf("unknown SanitizerType %d", t))
	}
}

// isUnsanitizedVariant returns true if no sanitizers are enabled.
func (sanitize *sanitize) isUnsanitizedVariant() bool {
	return !sanitize.isSanitizerEnabled(Asan) &&
		!sanitize.isSanitizerEnabled(Hwasan) &&
		!sanitize.isSanitizerEnabled(tsan) &&
		!sanitize.isSanitizerEnabled(cfi) &&
		!sanitize.isSanitizerEnabled(scs) &&
		!sanitize.isSanitizerEnabled(Memtag_heap) &&
		!sanitize.isSanitizerEnabled(Memtag_stack) &&
		!sanitize.isSanitizerEnabled(Memtag_globals) &&
		!sanitize.isSanitizerEnabled(Fuzzer)
}

// isVariantOnProductionDevice returns true if variant is for production devices (no non-production sanitizers enabled).
func (sanitize *sanitize) isVariantOnProductionDevice() bool {
	return !sanitize.isSanitizerEnabled(Asan) &&
		!sanitize.isSanitizerEnabled(Hwasan) &&
		!sanitize.isSanitizerEnabled(tsan) &&
		!sanitize.isSanitizerEnabled(Fuzzer)
}

func (sanitize *sanitize) SetSanitizer(t SanitizerType, b bool) {
	bPtr := proptools.BoolPtr(b)
	if !b {
		bPtr = nil
	}
	switch t {
	case Asan:
		sanitize.Properties.SanitizeMutated.Address = bPtr
		// For ASAN variant, we need to disable Memtag_stack
		sanitize.Properties.SanitizeMutated.Memtag_stack = nil
		sanitize.Properties.SanitizeMutated.Memtag_globals = nil
	case Hwasan:
		sanitize.Properties.SanitizeMutated.Hwaddress = bPtr
		// For HWAsan variant, we need to disable Memtag_stack
		sanitize.Properties.SanitizeMutated.Memtag_stack = nil
		sanitize.Properties.SanitizeMutated.Memtag_globals = nil
	case tsan:
		sanitize.Properties.SanitizeMutated.Thread = bPtr
	case intOverflow:
		sanitize.Properties.SanitizeMutated.Integer_overflow = bPtr
	case cfi:
		sanitize.Properties.SanitizeMutated.Cfi = bPtr
	case scs:
		sanitize.Properties.SanitizeMutated.Scs = bPtr
	case Memtag_heap:
		sanitize.Properties.SanitizeMutated.Memtag_heap = bPtr
	case Memtag_stack:
		sanitize.Properties.SanitizeMutated.Memtag_stack = bPtr
		// We do not need to disable ASAN or HWASan here, as there is no Memtag_stack variant.
	case Memtag_globals:
		sanitize.Properties.Sanitize.Memtag_globals = bPtr
	case Fuzzer:
		sanitize.Properties.SanitizeMutated.Fuzzer = bPtr
	default:
		panic(fmt.Errorf("unknown SanitizerType %d", t))
	}
	if b {
		sanitize.Properties.SanitizerEnabled = true
	}
}

// Check if the sanitizer is explicitly disabled (as opposed to nil by
// virtue of not being set).
func (sanitize *sanitize) isSanitizerExplicitlyDisabled(t SanitizerType) bool {
	if sanitize == nil {
		return false
	}

	sanitizerVal := sanitize.getSanitizerBoolPtr(t)
	return sanitizerVal != nil && *sanitizerVal == false
}

// There isn't an analog of the method above (ie:isSanitizerExplicitlyEnabled)
// because enabling a sanitizer either directly (via the blueprint) or
// indirectly (via a mutator) sets the bool ptr to true, and you can't
// distinguish between the cases. It isn't needed though - both cases can be
// treated identically.
func (s *sanitize) isSanitizerEnabled(t SanitizerType) bool {
	if s == nil {
		return false
	}
	if s.Properties.ForceDisable || proptools.Bool(s.Properties.SanitizeMutated.Never) {
		return false
	}

	sanitizerVal := s.getSanitizerBoolPtr(t)
	return sanitizerVal != nil && *sanitizerVal == true
}

// IsSanitizableDependencyTag returns true if the dependency tag is sanitizable.
func IsSanitizableDependencyTag(tag blueprint.DependencyTag) bool {
	switch t := tag.(type) {
	case dependencyTag:
		return t == reuseObjTag || t == objDepTag
	case libraryDependencyTag:
		return true
	default:
		return false
	}
}

func (m *Module) SanitizableDepTagChecker() SantizableDependencyTagChecker {
	return IsSanitizableDependencyTag
}

// @auto-generate: gob
type PlatformSanitizeableInfo struct {
	// IsSanitizerEnabled is a list of enabled flags for each sanitizer in the same order as Sanitizers.
	// If it is non-nil it will be the same length as Sanitizers.
	IsSanitizerEnabled []bool
}

var PlatformSanitizeableInfoProvider = blueprint.NewMutatorProvider[PlatformSanitizeableInfo]("sanitize_markapexes")

// If an APEX is sanitized or not depends on whether it contains at least one
// sanitized module. Transition mutators cannot propagate information up the
// dependency graph this way, so we need an auxiliary mutator to do so.
func markSanitizableApexesMutator(ctx android.BottomUpMutatorContext) {
	if sanitizeable, ok := ctx.Module().(Sanitizeable); ok {
		enabled := make([]bool, len(Sanitizers))
		for i, sanitizer := range Sanitizers {
			enabled[i] = sanitizeable.IsSanitizerEnabled(ctx.Config(), sanitizer.name())
		}

		ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
			if info, ok := android.OtherModuleProvider(ctx, dep, PlatformSanitizeableInfoProvider); ok &&
				len(info.IsSanitizerEnabled) > 0 {
				for i := range Sanitizers {
					enabled[i] = enabled[i] || info.IsSanitizerEnabled[i]
				}
			}
		})

		for i, sanitizer := range Sanitizers {
			if enabled[i] {
				sanitizeable.EnableSanitizer(sanitizer.name())
			}
		}
	} else if sanitizeable, ok := ctx.Module().(PlatformSanitizeable); ok {
		var isSanitizerEnabled []bool
		for i, sanitizer := range Sanitizers {
			if sanitizeable.IsSanitizerEnabled(sanitizer) {
				if isSanitizerEnabled == nil {
					isSanitizerEnabled = make([]bool, len(Sanitizers))
				}
				isSanitizerEnabled[i] = true
			}
		}
		android.SetProvider(ctx, PlatformSanitizeableInfoProvider, PlatformSanitizeableInfo{
			IsSanitizerEnabled: isSanitizerEnabled,
		})
	}
}

type sanitizerSplitMutator struct {
	sanitizer SanitizerType
}

func (s *sanitizerSplitMutator) Split(ctx android.BaseModuleContext) []string {
	if c, ok := ctx.Module().(PlatformSanitizeable); ok && c.SanitizePropDefined() {
		// If the given sanitizer is not requested in the .bp file for a module, it
		// won't automatically build the sanitized variation.
		if !c.IsSanitizerEnabled(s.sanitizer) {
			return []string{""}
		}

		if c.Binary() {
			// If a sanitizer is enabled for a binary, we do not build the version
			// without the sanitizer
			return []string{s.sanitizer.variationName()}
		} else if c.StaticallyLinked() || c.Header() {
			// For static libraries, we build both versions. Some Make modules
			// apparently depend on this behavior.
			return []string{"", s.sanitizer.variationName()}
		} else {
			// We only build the requested variation of dynamic libraries
			return []string{s.sanitizer.variationName()}
		}
	}

	if _, ok := ctx.Module().(JniSanitizeable); ok {
		// TODO: this should call into JniSanitizable.IsSanitizerEnabledForJni but
		// that is short-circuited for now
		return []string{""}
	}

	// If an APEX has a sanitized dependency, we build the APEX in the sanitized
	// variation. This is useful because such APEXes require extra dependencies.
	if sanitizeable, ok := ctx.Module().(Sanitizeable); ok {
		enabled := sanitizeable.IsSanitizerEnabled(ctx.Config(), s.sanitizer.name())
		if enabled {
			return []string{s.sanitizer.variationName()}
		} else {
			return []string{""}
		}
	}

	return []string{""}
}

func (s *sanitizerSplitMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
	if c, ok := ctx.Module().(PlatformSanitizeable); ok {
		if !c.SanitizableDepTagChecker()(ctx.DepTag()) {
			// If the dependency is through a non-sanitizable tag, use the
			// non-sanitized variation
			return ""
		}

		return sourceVariation
	} else if _, ok := ctx.Module().(JniSanitizeable); ok {
		// TODO: this should call into JniSanitizable.IsSanitizerEnabledForJni but
		// that is short-circuited for now
		return ""
	} else {
		// Otherwise, do not rock the boat.
		return sourceVariation
	}
}

func (s *sanitizerSplitMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
	if d, ok := ctx.Module().(PlatformSanitizeable); ok {
		if !d.SanitizePropDefined() ||
			d.SanitizeNever() ||
			d.IsSanitizerExplicitlyDisabled(s.sanitizer) ||
			!d.SanitizerSupported(s.sanitizer) {
			// If a module opts out of a sanitizer, use its non-sanitized variation
			return ""
		}

		// Binaries are always built in the variation they requested.
		if d.Binary() {
			if d.IsSanitizerEnabled(s.sanitizer) {
				return s.sanitizer.variationName()
			} else {
				return ""
			}
		}

		// If a shared library requests to be sanitized, it will be built for that
		// sanitizer. Otherwise, some sanitizers propagate through shared library
		// dependency edges, some do not.
		if !d.StaticallyLinked() && !d.Header() {
			if d.IsSanitizerEnabled(s.sanitizer) {
				return s.sanitizer.variationName()
			}

			// Some sanitizers do not propagate to shared dependencies
			if !s.sanitizer.shouldPropagateToSharedLibraryDeps() {
				return ""
			}
		}

		// Static and header libraries inherit whether they are sanitized from the
		// module they are linked into
		return incomingVariation
	} else if d, ok := ctx.Module().(Sanitizeable); ok {
		// If an APEX contains a sanitized module, it will be built in the variation
		// corresponding to that sanitizer.
		enabled := d.IsSanitizerEnabled(ctx.Config(), s.sanitizer.name())
		if enabled {
			return s.sanitizer.variationName()
		}

		return incomingVariation
	}

	return ""
}

func (s *sanitizerSplitMutator) Mutate(mctx android.BottomUpMutatorContext, variationName string) {
	sanitizerVariation := variationName == s.sanitizer.variationName()

	if c, ok := mctx.Module().(PlatformSanitizeable); ok && c.SanitizePropDefined() {
		sanitizerEnabled := c.IsSanitizerEnabled(s.sanitizer)

		oneMakeVariation := false
		if c.StaticallyLinked() || c.Header() {
			if s.sanitizer != cfi && s.sanitizer != scs && s.sanitizer != Hwasan {
				// These sanitizers export only one variation to Make. For the rest,
				// Make targets can depend on both the sanitized and non-sanitized
				// versions.
				oneMakeVariation = true
			}
		} else if !c.Binary() {
			// Shared library. These are the sanitizers that do propagate through shared
			// library dependencies and therefore can cause multiple variations of a
			// shared library to be built.
			if s.sanitizer != cfi && s.sanitizer != Hwasan && s.sanitizer != scs && s.sanitizer != Asan {
				oneMakeVariation = true
			}
		}

		if oneMakeVariation {
			if sanitizerEnabled != sanitizerVariation {
				c.SetPreventInstall()
				c.SetHideFromMake()
			}
		}

		if sanitizerVariation {
			c.SetSanitizer(s.sanitizer, true)

			// CFI is incompatible with ASAN so disable it in ASAN variations
			if s.sanitizer.incompatibleWithCfi() {
				cfiSupported := mctx.Module().(PlatformSanitizeable).SanitizerSupported(cfi)
				if mctx.Device() && cfiSupported {
					c.SetSanitizer(cfi, false)
				}
			}

			// locate the asan libraries under /data/asan
			if !c.Binary() && !c.StaticallyLinked() && !c.Header() && mctx.Device() && s.sanitizer == Asan && sanitizerEnabled {
				c.SetInSanitizerDir()
			}

			if c.StaticallyLinked() && c.ExportedToMake() {
				if s.sanitizer == Hwasan {
					hwasanStaticLibs(mctx.Config()).add(c, c.Module().Name())
				} else if s.sanitizer == cfi {
					cfiStaticLibs(mctx.Config()).add(c, c.Module().Name())
				} else if s.sanitizer == Memtag_stack {
					memtagStackStaticLibs(mctx.Config()).add(c, c.Module().Name())
				}
			}
		} else if c.IsSanitizerEnabled(s.sanitizer) {
			// Disable the sanitizer for the non-sanitized variation
			c.SetSanitizer(s.sanitizer, false)
		}
	} else if sanitizeable, ok := mctx.Module().(Sanitizeable); ok {
		// If an APEX has sanitized dependencies, it gets a few more dependencies
		if sanitizerVariation {
			sanitizeable.AddSanitizerDependencies(mctx, s.sanitizer.name())
		}
	}
}

func (c *Module) SanitizeNever() bool {
	return c.sanitize.Properties.ForceDisable || Bool(c.sanitize.Properties.SanitizeMutated.Never)
}

func (c *Module) IsSanitizerExplicitlyDisabled(t SanitizerType) bool {
	return c.sanitize.isSanitizerExplicitlyDisabled(t)
}

// SanitizerRuntimeDepInfo is exported from static libraries that have sanitizers enabled that require a runtime.
// @auto-generate: gob
type SanitizerRuntimeDepInfo struct {
	MinimalRuntimeNeeded bool
	UbsanRuntimeNeeded   bool
}

var SanitizerRuntimeDepInfoProvider = blueprint.NewMutatorProvider[SanitizerRuntimeDepInfo]("sanitize_runtime_deps")

// Propagate the ubsan minimal runtime dependency when there are integer overflow sanitized static dependencies.
func sanitizerRuntimeDepsMutator(mctx android.BottomUpMutatorContext) {
	// Change this to PlatformSanitizable when/if non-cc modules support ubsan sanitizers.
	if c, ok := mctx.Module().(*Module); ok && c.sanitize != nil {
		if c.sanitize.Properties.ForceDisable {
			return
		}

		var info SanitizerRuntimeDepInfo
		info.MinimalRuntimeNeeded = enableMinimalRuntime(c.sanitize)
		info.UbsanRuntimeNeeded = enableUbsanRuntime(c.sanitize)

		// Visit dependencies to determine if any require the runtime to be linked in.
		isSanitizableDependencyTag := c.SanitizableDepTagChecker()
		mctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
			if !isSanitizableDependencyTag(mctx.OtherModuleDependencyTag(dep)) {
				return
			}

			if depInfo, ok := android.OtherModuleProvider(mctx, dep, SanitizerRuntimeDepInfoProvider); ok {
				info.MinimalRuntimeNeeded = info.MinimalRuntimeNeeded || depInfo.MinimalRuntimeNeeded
				info.UbsanRuntimeNeeded = info.UbsanRuntimeNeeded || depInfo.UbsanRuntimeNeeded
			}
		})

		if info.MinimalRuntimeNeeded {
			// If a static dependency is built with the minimal runtime,
			// make sure we include the ubsan minimal runtime.
			c.sanitize.Properties.MinimalRuntimeDep = true
		}
		if info.UbsanRuntimeNeeded {
			// If a static dependency runs with full ubsan diagnostics,
			// make sure we include the ubsan runtime.
			c.sanitize.Properties.UbsanRuntimeDep = true
		}

		// If this is a static library with a runtime requirement then re-export the requirement.
		if c.static() && (info != SanitizerRuntimeDepInfo{}) {
			android.SetProvider(mctx, SanitizerRuntimeDepInfoProvider, info)
		}
	}
}

// Add the dependency to the runtime library for each of the sanitizer variants
func sanitizerRuntimeMutator(mctx android.BottomUpMutatorContext) {
	if c, ok := mctx.Module().(*Module); ok && c.sanitize != nil {
		if !c.Enabled(mctx) {
			return
		}
		if c.sanitize.Properties.ForceDisable {
			return
		}

		var sanitizers []string
		var diagSanitizers []string

		sanProps := &c.sanitize.Properties.SanitizeMutated

		if Bool(sanProps.All_undefined) {
			sanitizers = append(sanitizers, "undefined")
		} else {
			if Bool(sanProps.Undefined) {
				sanitizers = append(sanitizers,
					"bool",
					"integer-divide-by-zero",
					"object-size",
					"return",
					"returns-nonnull-attribute",
					"shift-exponent",
					"unreachable",
					"vla-bound",
					// TODO(danalbert): The following checks currently have compiler performance issues.
					//"alignment",
					//"bounds",
					//"enum",
					//"float-cast-overflow",
					//"float-divide-by-zero",
					//"nonnull-attribute",
					//"null",
					//"shift-base",
					//"signed-integer-overflow",
				)
			}
			sanitizers = append(sanitizers, sanProps.Misc_undefined...)
		}

		if Bool(sanProps.Diag.Undefined) {
			diagSanitizers = append(diagSanitizers, "undefined")
		}

		diagSanitizers = append(diagSanitizers, sanProps.Diag.Misc_undefined...)

		if Bool(sanProps.Address) {
			sanitizers = append(sanitizers, "address")
			diagSanitizers = append(diagSanitizers, "address")
		}

		if Bool(sanProps.Hwaddress) {
			sanitizers = append(sanitizers, "hwaddress")
		}

		if Bool(sanProps.Thread) {
			sanitizers = append(sanitizers, "thread")
		}

		if Bool(sanProps.Safestack) {
			sanitizers = append(sanitizers, "safe-stack")
		}

		if Bool(sanProps.Cfi) {
			sanitizers = append(sanitizers, "cfi")

			if Bool(sanProps.Diag.Cfi) {
				diagSanitizers = append(diagSanitizers, "cfi")
			}
		}

		if Bool(sanProps.Integer_overflow) {
			sanitizers = append(sanitizers, "unsigned-integer-overflow")
			sanitizers = append(sanitizers, "signed-integer-overflow")
			if Bool(sanProps.Diag.Integer_overflow) {
				diagSanitizers = append(diagSanitizers, "unsigned-integer-overflow")
				diagSanitizers = append(diagSanitizers, "signed-integer-overflow")
			}
		}

		if Bool(sanProps.Scudo) {
			sanitizers = append(sanitizers, "scudo")
		}

		if Bool(sanProps.Scs) {
			sanitizers = append(sanitizers, "shadow-call-stack")
		}

		if Bool(sanProps.Memtag_heap) && c.Binary() {
			sanitizers = append(sanitizers, "memtag-heap")
		}

		if Bool(sanProps.Memtag_stack) {
			sanitizers = append(sanitizers, "memtag-stack")
		}

		if Bool(sanProps.Memtag_globals) {
			sanitizers = append(sanitizers, "memtag-globals")
		}

		if Bool(sanProps.Fuzzer) {
			sanitizers = append(sanitizers, "fuzzer-no-link")
		}

		// Save the list of sanitizers. These will be used again when generating
		// the build rules (for Cflags, etc.)
		c.sanitize.Properties.Sanitizers = sanitizers
		c.sanitize.Properties.DiagSanitizers = diagSanitizers

		// TODO(b/150822854) Hosts have a different default behavior and assume the runtime library is used.
		if c.Host() {
			diagSanitizers = sanitizers
		}

		addStaticDeps := func(dep string, hideSymbols bool) {
			// static executable gets static runtime libs
			depTag := libraryDependencyTag{Kind: staticLibraryDependency, unexportedSymbols: hideSymbols}
			variations := append(mctx.Target().Variations(),
				blueprint.Variation{Mutator: "link", Variation: "static"})
			if c.Device() {
				variations = append(variations, c.ImageVariation())
			}
			if c.UseSdk() {
				variations = append(variations,
					blueprint.Variation{Mutator: "sdk", Variation: "sdk"})
			}
			mctx.AddFarVariationDependencies(variations, depTag, dep)
		}

		// Determine the runtime library required
		runtimeSharedLibrary := ""
		toolchain := c.toolchain(mctx)
		if Bool(sanProps.Address) {
			if toolchain.Musl() || (c.staticBinary() && toolchain.Bionic()) {
				// Use a static runtime for musl to match what clang does for glibc.
				addStaticDeps(config.AddressSanitizerStaticRuntimeLibrary(), false)
				addStaticDeps(config.AddressSanitizerCXXStaticRuntimeLibrary(), false)
			} else {
				runtimeSharedLibrary = config.AddressSanitizerRuntimeLibrary()
			}
		} else if Bool(sanProps.Hwaddress) {
			if c.staticBinary() {
				addStaticDeps(config.HWAddressSanitizerStaticLibrary(), true)
				addStaticDeps("libdl", false)
			} else {
				runtimeSharedLibrary = config.HWAddressSanitizerRuntimeLibrary()
			}
		} else if Bool(sanProps.Thread) {
			runtimeSharedLibrary = config.ThreadSanitizerRuntimeLibrary()
		} else if Bool(sanProps.Scudo) {
			if len(diagSanitizers) == 0 && !c.sanitize.Properties.UbsanRuntimeDep {
				runtimeSharedLibrary = config.ScudoMinimalRuntimeLibrary()
			} else {
				runtimeSharedLibrary = config.ScudoRuntimeLibrary()
			}
		} else if len(diagSanitizers) > 0 || c.sanitize.Properties.UbsanRuntimeDep ||
			Bool(sanProps.Fuzzer) ||
			Bool(sanProps.Undefined) ||
			Bool(sanProps.All_undefined) {
			if toolchain.Musl() || c.staticBinary() {
				// Use a static runtime for static binaries.  For sanitized glibc binaries the runtime is
				// added automatically by clang, but for static glibc binaries that are not sanitized but
				// have a sanitized dependency the runtime needs to be added manually.
				// Also manually add a static runtime for musl to match what clang does for glibc.
				// Otherwise dlopening libraries that depend on libclang_rt.ubsan_standalone.so fails with:
				// Error relocating ...: initial-exec TLS resolves to dynamic definition
				addStaticDeps(config.UndefinedBehaviorSanitizerRuntimeLibrary()+".static", true)
			} else {
				runtimeSharedLibrary = config.UndefinedBehaviorSanitizerRuntimeLibrary()
			}
		}

		if enableMinimalRuntime(c.sanitize) || c.sanitize.Properties.MinimalRuntimeDep {
			addStaticDeps(config.UndefinedBehaviorSanitizerMinimalRuntimeLibrary(), true)
		}

		if runtimeSharedLibrary != "" && (toolchain.Bionic() || toolchain.Musl()) {
			// UBSan is supported on non-bionic linux host builds as well

			// Adding dependency to the runtime library. We are using *FarVariation*
			// because the runtime libraries themselves are not mutated by sanitizer
			// mutators and thus don't have sanitizer variants whereas this module
			// has been already mutated.
			//
			// Note that by adding dependency with {static|shared}DepTag, the lib is
			// added to libFlags and LOCAL_SHARED_LIBRARIES by cc.Module
			if c.staticBinary() {
				// Most sanitizers are either disabled for static binaries or have already
				// handled the static binary case above through a direct call to addStaticDeps.
				// If not, treat the runtime shared library as a static library and hope for
				// the best.
				addStaticDeps(runtimeSharedLibrary, true)
			} else if !c.static() && !c.Header() {
				// Skip apex dependency check for sharedLibraryDependency
				// when sanitizer diags are enabled. Skipping the check will allow
				// building with diag libraries without having to list the
				// dependency in Apex's allowed_deps file.
				diagEnabled := len(diagSanitizers) > 0
				// dynamic executable and shared libs get shared runtime libs
				depTag := libraryDependencyTag{
					Kind:  sharedLibraryDependency,
					Order: earlyLibraryDependency,

					skipApexAllowedDependenciesCheck: diagEnabled,
				}
				variations := append(mctx.Target().Variations(),
					blueprint.Variation{Mutator: "link", Variation: "shared"})
				if c.Device() {
					variations = append(variations, c.ImageVariation())
				}
				if c.UseSdk() {
					variations = append(variations,
						blueprint.Variation{Mutator: "sdk", Variation: "sdk"})
				}
				AddSharedLibDependenciesWithVersions(mctx, c, variations, depTag, runtimeSharedLibrary, "", true)
			}
			// static lib does not have dependency to the runtime library. The
			// dependency will be added to the executables or shared libs using
			// the static lib.
		}
	}
}

type Sanitizeable interface {
	android.Module
	IsSanitizerEnabled(config android.Config, sanitizerName string) bool
	EnableSanitizer(sanitizerName string)
	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
}

func (c *Module) UbsanRuntimeDep() bool {
	return c.sanitize.Properties.UbsanRuntimeDep
}

func (c *Module) SanitizePropDefined() bool {
	return c.sanitize != nil
}

func (c *Module) IsSanitizerEnabled(t SanitizerType) bool {
	return c.sanitize.isSanitizerEnabled(t)
}

func (c *Module) StaticallyLinked() bool {
	return c.static()
}

func (c *Module) SetInSanitizerDir() {
	if c.sanitize != nil {
		c.sanitize.Properties.InSanitizerDir = true
	}
}

func (c *Module) SetSanitizer(t SanitizerType, b bool) {
	if c.sanitize != nil {
		c.sanitize.SetSanitizer(t, b)
	}
}

var _ PlatformSanitizeable = (*Module)(nil)

type sanitizerStaticLibsMap struct {
	// libsMap contains one list of modules per each image and each arch.
	// e.g. libs[vendor]["arm"] contains arm modules installed to vendor
	libsMap       map[ImageVariantType]map[string][]string
	libsMapLock   sync.Mutex
	sanitizerType SanitizerType
}

func newSanitizerStaticLibsMap(t SanitizerType) *sanitizerStaticLibsMap {
	return &sanitizerStaticLibsMap{
		sanitizerType: t,
		libsMap:       make(map[ImageVariantType]map[string][]string),
	}
}

// Add the current module to sanitizer static libs maps
// Each module should pass its exported name as names of Make and Soong can differ.
func (s *sanitizerStaticLibsMap) add(c LinkableInterface, name string) {
	image := GetImageVariantType(c)
	arch := c.Module().Target().Arch.ArchType.String()

	s.libsMapLock.Lock()
	defer s.libsMapLock.Unlock()

	if _, ok := s.libsMap[image]; !ok {
		s.libsMap[image] = make(map[string][]string)
	}

	s.libsMap[image][arch] = append(s.libsMap[image][arch], name)
}

// Exports makefile variables in the following format:
// SOONG_{sanitizer}_{image}_{arch}_STATIC_LIBRARIES
// e.g. SOONG_cfi_core_x86_STATIC_LIBRARIES
// These are to be used by use_soong_sanitized_static_libraries.
// See build/make/core/binary.mk for more details.
func (s *sanitizerStaticLibsMap) exportToMake(ctx android.MakeVarsContext) {
	for _, image := range android.SortedKeys(s.libsMap) {
		archMap := s.libsMap[ImageVariantType(image)]
		for _, arch := range android.SortedKeys(archMap) {
			libs := archMap[arch]
			sort.Strings(libs)

			key := fmt.Sprintf(
				"SOONG_%s_%s_%s_STATIC_LIBRARIES",
				s.sanitizerType.variationName(),
				image, // already upper
				arch)

			ctx.Strict(key, strings.Join(libs, " "))
		}
	}
}

var cfiStaticLibsKey = android.NewOnceKey("cfiStaticLibs")

func cfiStaticLibs(config android.Config) *sanitizerStaticLibsMap {
	return config.Once(cfiStaticLibsKey, func() interface{} {
		return newSanitizerStaticLibsMap(cfi)
	}).(*sanitizerStaticLibsMap)
}

var hwasanStaticLibsKey = android.NewOnceKey("hwasanStaticLibs")

func hwasanStaticLibs(config android.Config) *sanitizerStaticLibsMap {
	return config.Once(hwasanStaticLibsKey, func() interface{} {
		return newSanitizerStaticLibsMap(Hwasan)
	}).(*sanitizerStaticLibsMap)
}

var memtagStackStaticLibsKey = android.NewOnceKey("memtagStackStaticLibs")

func memtagStackStaticLibs(config android.Config) *sanitizerStaticLibsMap {
	return config.Once(memtagStackStaticLibsKey, func() interface{} {
		return newSanitizerStaticLibsMap(Memtag_stack)
	}).(*sanitizerStaticLibsMap)
}

func enableMinimalRuntime(sanitize *sanitize) bool {
	if sanitize.isSanitizerEnabled(Asan) {
		return false
	} else if sanitize.isSanitizerEnabled(Hwasan) {
		return false
	} else if sanitize.isSanitizerEnabled(Fuzzer) {
		return false
	}

	if enableUbsanRuntime(sanitize) {
		return false
	}

	sanitizeProps := &sanitize.Properties.SanitizeMutated
	if Bool(sanitizeProps.Diag.Cfi) {
		return false
	}

	return Bool(sanitizeProps.Integer_overflow) ||
		len(sanitizeProps.Misc_undefined) > 0 ||
		Bool(sanitizeProps.Undefined) ||
		Bool(sanitizeProps.All_undefined)
}

func (m *Module) UbsanRuntimeNeeded() bool {
	return enableUbsanRuntime(m.sanitize)
}

func (m *Module) MinimalRuntimeNeeded() bool {
	return enableMinimalRuntime(m.sanitize)
}

func enableUbsanRuntime(sanitize *sanitize) bool {
	sanitizeProps := &sanitize.Properties.SanitizeMutated
	return Bool(sanitizeProps.Diag.Integer_overflow) ||
		Bool(sanitizeProps.Diag.Undefined) ||
		len(sanitizeProps.Diag.Misc_undefined) > 0
}

func cfiMakeVarsProvider(ctx android.MakeVarsContext) {
	cfiStaticLibs(ctx.Config()).exportToMake(ctx)
}

func hwasanMakeVarsProvider(ctx android.MakeVarsContext) {
	hwasanStaticLibs(ctx.Config()).exportToMake(ctx)
}

func memtagStackMakeVarsProvider(ctx android.MakeVarsContext) {
	memtagStackStaticLibs(ctx.Config()).exportToMake(ctx)
}

type sanitizerLibrariesTxtModule struct {
	android.ModuleBase

	outputFile android.Path
}

var _ etc.PrebuiltEtcModule = (*sanitizerLibrariesTxtModule)(nil)

func RegisterSanitizerLibrariesTxtType(ctx android.RegistrationContext) {
	ctx.RegisterModuleType("sanitizer_libraries_txt", sanitizerLibrariesTxtFactory)
}

func sanitizerLibrariesTxtFactory() android.Module {
	m := &sanitizerLibrariesTxtModule{}
	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
	return m
}

type sanitizerLibraryDependencyTag struct {
	blueprint.BaseDependencyTag
	android.AlwaysAllowDisabledModuleDependencyTag
}

var _ android.AllowDisabledModuleDependency = (*sanitizerLibraryDependencyTag)(nil)

func (txt *sanitizerLibrariesTxtModule) DepsMutator(actx android.BottomUpMutatorContext) {
	targets := actx.Config().Targets[android.Android]
	depTag := sanitizerLibraryDependencyTag{}

	for _, target := range targets {
		variation := append(target.Variations(),
			blueprint.Variation{Mutator: "image", Variation: ""},
			blueprint.Variation{Mutator: "sdk", Variation: ""},
			blueprint.Variation{Mutator: "link", Variation: "shared"},
		)
		for _, lib := range android.SortedStringValues(sanitizerVariables) {
			if actx.OtherModuleFarDependencyVariantExists(variation, lib) {
				actx.AddFarVariationDependencies(variation, depTag, lib)
			}

			prebuiltLibName := "prebuilt_" + lib
			if actx.OtherModuleFarDependencyVariantExists(variation, prebuiltLibName) {
				actx.AddFarVariationDependencies(variation, depTag, prebuiltLibName)
			}
		}
	}

}

func (txt *sanitizerLibrariesTxtModule) getSanitizerLibs(ctx android.ModuleContext) string {
	var sanitizerLibStems []string

	ctx.VisitDirectDepsProxy(func(m android.ModuleProxy) {
		info := android.OtherModuleProviderOrDefault(ctx, m, android.CommonModuleInfoProvider)
		if !info.Enabled {
			return
		}

		if _, ok := android.OtherModuleProvider(ctx, m, SharedLibraryInfoProvider); !ok {
			return
		}

		targets := ctx.Config().Targets[android.Android]

		var targetMatches bool
		for _, target := range targets {
			if info.Target.Os == target.Os && info.Target.Arch.ArchType == target.Arch.ArchType {
				targetMatches = true
			}
		}

		if !targetMatches {
			return
		}

		outputFiles := android.OutputFilesForModule(ctx, m, "")
		if len(outputFiles) == 1 {
			sanitizerLibStems = append(sanitizerLibStems, outputFiles[0].Base())
		} else if len(outputFiles) > 1 {
			panic(fmt.Errorf("multiple output files for %s: %s", m, outputFiles.Strings()))
		}
	})

	sanitizerLibStems = android.SortedUniqueStrings(sanitizerLibStems)
	return strings.Join(sanitizerLibStems, "\n")
}

func (txt *sanitizerLibrariesTxtModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	filename := txt.Name()

	outputFile := android.PathForModuleOut(ctx, filename)
	android.WriteFileRule(ctx, outputFile, txt.getSanitizerLibs(ctx))

	installPath := android.PathForModuleInstall(ctx, "etc")
	ctx.InstallFile(installPath, filename, outputFile)

	ctx.SetOutputFiles(android.Paths{outputFile}, "")
	txt.outputFile = outputFile

	etc.SetCommonPrebuiltEtcInfo(ctx, txt)
}

func (txt *sanitizerLibrariesTxtModule) PrepareAndroidMKProviderInfo(config android.Config) *android.AndroidMkProviderInfo {
	return &android.AndroidMkProviderInfo{
		PrimaryInfo: android.AndroidMkInfo{
			Class:      "ETC",
			OutputFile: android.OptionalPathForPath(txt.outputFile),
		},
	}
}

// PrebuiltEtcModule interface
func (txt *sanitizerLibrariesTxtModule) BaseDir() string {
	return "etc"
}

// PrebuiltEtcModule interface
func (txt *sanitizerLibrariesTxtModule) SubDir() string {
	return ""
}
