Add support for sanitizer property

Add a new feature to cc modules that adds the cflags and libraries
necessary to implement AddressSanitizer, ThreadSanitizer, and
UndefinedBehaviorSanitizer.

Change-Id: Ibe5ffadc7ece56080a2521f2c7c00da9ef712584
diff --git a/Android.bp b/Android.bp
index b4928f8..831b703 100644
--- a/Android.bp
+++ b/Android.bp
@@ -124,6 +124,7 @@
         "cc/cc.go",
         "cc/clang.go",
         "cc/gen.go",
+        "cc/sanitize.go",
         "cc/stl.go",
         "cc/toolchain.go",
         "cc/util.go",
diff --git a/cc/arm64_device.go b/cc/arm64_device.go
index 1a0408c..9194c6d 100644
--- a/cc/arm64_device.go
+++ b/cc/arm64_device.go
@@ -190,6 +190,10 @@
 	return t.toolchainClangCflags
 }
 
+func (toolchainArm64) AddressSanitizerRuntimeLibrary() string {
+	return "libclang_rt.asan-aarch64-android.so"
+}
+
 func arm64ToolchainFactory(arch common.Arch) Toolchain {
 	if arch.ArchVariant != "armv8-a" {
 		panic(fmt.Sprintf("Unknown ARM architecture version: %q", arch.ArchVariant))
diff --git a/cc/arm_device.go b/cc/arm_device.go
index ea7db43..764aaa9 100644
--- a/cc/arm_device.go
+++ b/cc/arm_device.go
@@ -337,6 +337,10 @@
 	}
 }
 
+func (toolchainArm) AddressSanitizerRuntimeLibrary() string {
+	return "libclang_rt.asan-arm-android.so"
+}
+
 func armToolchainFactory(arch common.Arch) Toolchain {
 	var fixCortexA8 string
 	toolchainCflags := make([]string, 2, 3)
diff --git a/cc/builder.go b/cc/builder.go
index 38bb141..f67f9bd3 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -133,6 +133,7 @@
 	conlyFlags  string
 	cppFlags    string
 	ldFlags     string
+	libFlags    string
 	yaccFlags   string
 	nocrt       bool
 	toolchain   Toolchain
@@ -190,7 +191,7 @@
 				panic("unrecoginzied ccCmd")
 			}
 
-			ccCmd = "${clangPath}/" + ccCmd
+			ccCmd = "${clangBin}/" + ccCmd
 		} else {
 			ccCmd = gccCmd(flags.toolchain, ccCmd)
 		}
@@ -288,7 +289,7 @@
 
 	var ldCmd string
 	if flags.clang {
-		ldCmd = "${clangPath}/clang++"
+		ldCmd = "${clangBin}/clang++"
 	} else {
 		ldCmd = gccCmd(flags.toolchain, "g++")
 	}
@@ -296,6 +297,10 @@
 	var ldDirs []string
 	var libFlagsList []string
 
+	if len(flags.libFlags) > 0 {
+		libFlagsList = append(libFlagsList, flags.libFlags)
+	}
+
 	if len(wholeStaticLibs) > 0 {
 		if ctx.Host() && ctx.Darwin() {
 			libFlagsList = append(libFlagsList, common.JoinWithPrefix(wholeStaticLibs.Strings(), "-force_load "))
@@ -359,7 +364,7 @@
 
 	var ldCmd string
 	if flags.clang {
-		ldCmd = "${clangPath}clang++"
+		ldCmd = "${clangBin}clang++"
 	} else {
 		ldCmd = gccCmd(flags.toolchain, "g++")
 	}
diff --git a/cc/cc.go b/cc/cc.go
index 18dd09c..851e4c3 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -59,6 +59,12 @@
 	common.RegisterBottomUpMutator("link", linkageMutator)
 	common.RegisterBottomUpMutator("test_per_src", testPerSrcMutator)
 	common.RegisterBottomUpMutator("deps", depsMutator)
+
+	common.RegisterTopDownMutator("asan_deps", sanitizerDepsMutator(asan))
+	common.RegisterBottomUpMutator("asan", sanitizerMutator(asan))
+
+	common.RegisterTopDownMutator("tsan_deps", sanitizerDepsMutator(tsan))
+	common.RegisterBottomUpMutator("tsan", sanitizerMutator(tsan))
 }
 
 var (
@@ -168,7 +174,8 @@
 		}
 		return "clang-2690385", nil
 	})
-	pctx.StaticVariable("clangPath", "${clangBase}/${HostPrebuiltTag}/${clangVersion}/bin")
+	pctx.StaticVariable("clangPath", "${clangBase}/${HostPrebuiltTag}/${clangVersion}")
+	pctx.StaticVariable("clangBin", "${clangPath}/bin")
 }
 
 type Deps struct {
@@ -208,12 +215,16 @@
 	CppFlags    []string // Flags that apply to C++ source files
 	YaccFlags   []string // Flags that apply to Yacc source files
 	LdFlags     []string // Flags that apply to linker command lines
+	libFlags    []string // Flags to add libraries early to the link order
 
 	Nocrt     bool
 	Toolchain Toolchain
 	Clang     bool
 
 	RequiredInstructionSet string
+	DynamicLinker          string
+
+	CFlagsDeps common.Paths // Files depended on by compiler flags
 }
 
 type BaseCompilerProperties struct {
@@ -371,6 +382,8 @@
 	// don't link in crt_begin and crt_end.  This flag should only be necessary for
 	// compiling crt or libc.
 	Nocrt *bool `android:"arch_variant"`
+
+	VariantName string `blueprint:"mutated"`
 }
 
 type BinaryLinkerProperties struct {
@@ -426,16 +439,6 @@
 	Required        []string
 	Strip           string
 	Tags            []string
-	Sanitize        struct {
-		Never          bool     `android:"arch_variant"`
-		Address        bool     `android:"arch_variant"`
-		Thread         bool     `android:"arch_variant"`
-		Undefined      bool     `android:"arch_variant"`
-		All_undefined  bool     `android:"arch_variant"`
-		Misc_undefined []string `android:"arch_variant"`
-		Coverage       bool     `android:"arch_variant"`
-		Recover        []string
-	} `android:"arch_variant"`
 }
 
 type ModuleContextIntf interface {
@@ -529,6 +532,9 @@
 	linker     linker
 	installer  installer
 	stl        *stl
+	sanitize   *sanitize
+
+	androidMkSharedLibDeps []string
 
 	outputFile common.OptionalPath
 
@@ -552,6 +558,9 @@
 	if c.stl != nil {
 		props = append(props, c.stl.props()...)
 	}
+	if c.sanitize != nil {
+		props = append(props, c.sanitize.props()...)
+	}
 	for _, feature := range c.features {
 		props = append(props, feature.props()...)
 	}
@@ -632,6 +641,7 @@
 func newModule(hod common.HostOrDeviceSupported, multilib common.Multilib) *Module {
 	module := newBaseModule(hod, multilib)
 	module.stl = &stl{}
+	module.sanitize = &sanitize{}
 	return module
 }
 
@@ -648,7 +658,6 @@
 		Toolchain: c.toolchain(ctx),
 		Clang:     c.clang(ctx),
 	}
-
 	if c.compiler != nil {
 		flags = c.compiler.flags(ctx, flags)
 	}
@@ -658,6 +667,9 @@
 	if c.stl != nil {
 		flags = c.stl.flags(ctx, flags)
 	}
+	if c.sanitize != nil {
+		flags = c.sanitize.flags(ctx, flags)
+	}
 	for _, feature := range c.features {
 		flags = feature.flags(ctx, flags)
 	}
@@ -734,6 +746,9 @@
 	if c.stl != nil {
 		c.stl.begin(ctx)
 	}
+	if c.sanitize != nil {
+		c.sanitize.begin(ctx)
+	}
 	for _, feature := range c.features {
 		feature.begin(ctx)
 	}
@@ -751,6 +766,9 @@
 	if c.stl != nil {
 		deps = c.stl.deps(ctx, deps)
 	}
+	if c.sanitize != nil {
+		deps = c.sanitize.deps(ctx, deps)
+	}
 	for _, feature := range c.features {
 		deps = feature.deps(ctx, deps)
 	}
@@ -960,6 +978,20 @@
 	return c.installer.inData()
 }
 
+type appendVariantName interface {
+	appendVariantName(string)
+}
+
+func (c *Module) appendVariantName(name string) {
+	if c.linker == nil {
+		return
+	}
+
+	if l, ok := c.linker.(appendVariantName); ok {
+		l.appendVariantName(name)
+	}
+}
+
 // Compiler
 
 type baseCompiler struct {
@@ -1152,6 +1184,7 @@
 	srcPaths, gendeps := genSources(ctx, inputFiles, buildFlags)
 
 	deps = append(deps, gendeps...)
+	deps = append(deps, flags.CFlagsDeps...)
 
 	return TransformSourceToObj(ctx, subdir, srcPaths, buildFlags, deps)
 }
@@ -1269,6 +1302,10 @@
 	linker.dynamicProperties.VariantIsStatic = static
 }
 
+func (linker *baseLinker) isDependencyRoot() bool {
+	return false
+}
+
 type baseLinkerInterface interface {
 	// Returns true if the build options for the module have selected a static or shared build
 	buildStatic() bool
@@ -1282,6 +1319,10 @@
 
 	// Returns whether a module is a static binary
 	staticBinary() bool
+
+	// Returns true for dependency roots (binaries)
+	// TODO(ccross): also handle dlopenable libraries
+	isDependencyRoot() bool
 }
 
 type baseInstaller struct {
@@ -1417,6 +1458,7 @@
 }
 
 var _ linker = (*libraryLinker)(nil)
+var _ appendVariantName = (*libraryLinker)(nil)
 
 func (library *libraryLinker) props() []interface{} {
 	props := library.baseLinker.props()
@@ -1493,7 +1535,8 @@
 	objFiles = append(objFiles, deps.WholeStaticLibObjFiles...)
 	library.objFiles = objFiles
 
-	outputFile := common.PathForModuleOut(ctx, ctx.ModuleName()+staticLibraryExtension)
+	outputFile := common.PathForModuleOut(ctx,
+		ctx.ModuleName()+library.Properties.VariantName+staticLibraryExtension)
 
 	if ctx.Darwin() {
 		TransformDarwinObjToStaticLib(ctx, objFiles, flagsToBuilderFlags(flags), outputFile)
@@ -1511,7 +1554,8 @@
 func (library *libraryLinker) linkShared(ctx ModuleContext,
 	flags Flags, deps PathDeps, objFiles common.Paths) common.Path {
 
-	outputFile := common.PathForModuleOut(ctx, ctx.ModuleName()+flags.Toolchain.ShlibSuffix())
+	outputFile := common.PathForModuleOut(ctx,
+		ctx.ModuleName()+library.Properties.VariantName+flags.Toolchain.ShlibSuffix())
 
 	var linkerDeps common.Paths
 
@@ -1595,6 +1639,10 @@
 	return !library.static()
 }
 
+func (library *libraryLinker) appendVariantName(variant string) {
+	library.Properties.VariantName += variant
+}
+
 type libraryInstaller struct {
 	baseInstaller
 
@@ -1771,6 +1819,10 @@
 	return true
 }
 
+func (binary *binaryLinker) isDependencyRoot() bool {
+	return true
+}
+
 func NewBinary(hod common.HostOrDeviceSupported) *Module {
 	module := newModule(hod, common.MultilibFirst)
 	module.compiler = &baseCompiler{}
@@ -1829,16 +1881,17 @@
 			)
 
 		} else {
-			linker := "/system/bin/linker"
-			if flags.Toolchain.Is64Bit() {
-				linker += "64"
+			if flags.DynamicLinker == "" {
+				flags.DynamicLinker = "/system/bin/linker"
+				if flags.Toolchain.Is64Bit() {
+					flags.DynamicLinker += "64"
+				}
 			}
 
 			flags.LdFlags = append(flags.LdFlags,
 				"-pie",
 				"-nostdlib",
 				"-Bdynamic",
-				fmt.Sprintf("-Wl,-dynamic-linker,%s", linker),
 				"-Wl,--gc-sections",
 				"-Wl,-z,nocopyreloc",
 			)
@@ -1871,6 +1924,10 @@
 	sharedLibs := deps.SharedLibs
 	sharedLibs = append(sharedLibs, deps.LateSharedLibs...)
 
+	if flags.DynamicLinker != "" {
+		flags.LdFlags = append(flags.LdFlags, " -Wl,-dynamic-linker,"+flags.DynamicLinker)
+	}
+
 	TransformObjToDynamicBinary(ctx, objFiles, sharedLibs, deps.StaticLibs,
 		deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, true,
 		flagsToBuilderFlags(flags), outputFile)
@@ -2101,6 +2158,7 @@
 		&TestLinkerProperties{},
 		&UnusedProperties{},
 		&StlProperties{},
+		&SanitizeProperties{},
 	}
 
 	_, propertyStructs = common.InitAndroidArchModule(module, common.HostAndDeviceDefault,
diff --git a/cc/sanitize.go b/cc/sanitize.go
new file mode 100644
index 0000000..676d5b3
--- /dev/null
+++ b/cc/sanitize.go
@@ -0,0 +1,329 @@
+// 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"
+	"strings"
+
+	"github.com/google/blueprint"
+
+	"android/soong/common"
+)
+
+type sanitizerType int
+
+func init() {
+	pctx.StaticVariable("clangAsanLibDir", "${clangPath}/lib64/clang/3.8/lib/linux")
+}
+
+const (
+	asan sanitizerType = iota + 1
+	tsan
+)
+
+func (t sanitizerType) String() string {
+	switch t {
+	case asan:
+		return "asan"
+	case tsan:
+		return "tsan"
+	default:
+		panic(fmt.Errorf("unknown sanitizerType %d", t))
+	}
+}
+
+type SanitizeProperties struct {
+	// enable AddressSanitizer, ThreadSanitizer, or UndefinedBehaviorSanitizer
+	Sanitize struct {
+		Never bool `android:"arch_variant"`
+
+		// main sanitizers
+		Address bool `android:"arch_variant"`
+		Thread  bool `android:"arch_variant"`
+
+		// local sanitizers
+		Undefined      bool     `android:"arch_variant"`
+		All_undefined  bool     `android:"arch_variant"`
+		Misc_undefined []string `android:"arch_variant"`
+		Coverage       bool     `android:"arch_variant"`
+
+		// value to pass to -fsantitize-recover=
+		Recover []string
+
+		// value to pass to -fsanitize-blacklist
+		Blacklist *string
+	} `android:"arch_variant"`
+
+	SanitizerEnabled bool `blueprint:"mutated"`
+	SanitizeDep      bool `blueprint:"mutated"`
+}
+
+type sanitize struct {
+	Properties SanitizeProperties
+}
+
+func (sanitize *sanitize) props() []interface{} {
+	return []interface{}{&sanitize.Properties}
+}
+
+func (sanitize *sanitize) begin(ctx BaseModuleContext) {
+	// Don't apply sanitizers to NDK code.
+	if ctx.sdk() {
+		sanitize.Properties.Sanitize.Never = true
+	}
+
+	// Never always wins.
+	if sanitize.Properties.Sanitize.Never {
+		return
+	}
+
+	if ctx.ContainsProperty("sanitize") {
+		sanitize.Properties.SanitizerEnabled = true
+	}
+
+	var globalSanitizers []string
+	if ctx.clang() {
+		if ctx.Host() {
+			globalSanitizers = ctx.AConfig().SanitizeHost()
+		} else {
+			globalSanitizers = ctx.AConfig().SanitizeDevice()
+		}
+	}
+
+	// The sanitizer specified by the environment wins over the module.
+	if len(globalSanitizers) > 0 {
+		// wipe the enabled sanitizers
+		sanitize.Properties = SanitizeProperties{}
+		var found bool
+		if found, globalSanitizers = removeFromList("undefined", globalSanitizers); found {
+			sanitize.Properties.Sanitize.All_undefined = true
+		} else if found, globalSanitizers = removeFromList("default-ub", globalSanitizers); found {
+			sanitize.Properties.Sanitize.Undefined = true
+		}
+
+		if found, globalSanitizers = removeFromList("address", globalSanitizers); found {
+			sanitize.Properties.Sanitize.Address = true
+		}
+
+		if found, globalSanitizers = removeFromList("thread", globalSanitizers); found {
+			sanitize.Properties.Sanitize.Thread = true
+		}
+
+		if found, globalSanitizers = removeFromList("coverage", globalSanitizers); found {
+			sanitize.Properties.Sanitize.Coverage = true
+		}
+
+		if len(globalSanitizers) > 0 {
+			ctx.ModuleErrorf("unknown global sanitizer option %s", globalSanitizers[0])
+		}
+		sanitize.Properties.SanitizerEnabled = true
+	}
+
+	if !ctx.toolchain().Is64Bit() && sanitize.Properties.Sanitize.Thread {
+		// TSAN is not supported on 32-bit architectures
+		sanitize.Properties.Sanitize.Thread = false
+		// TODO(ccross): error for compile_multilib = "32"?
+	}
+
+	if sanitize.Properties.Sanitize.Coverage {
+		if !sanitize.Properties.Sanitize.Address {
+			ctx.ModuleErrorf(`Use of "coverage" also requires "address"`)
+		}
+	}
+}
+
+func (sanitize *sanitize) deps(ctx BaseModuleContext, deps Deps) Deps {
+	if !sanitize.Properties.SanitizerEnabled { // || c.static() {
+		return deps
+	}
+
+	if ctx.Device() {
+		deps.SharedLibs = append(deps.SharedLibs, "libdl")
+		if sanitize.Properties.Sanitize.Address {
+			deps.StaticLibs = append(deps.StaticLibs, "libasan")
+		}
+	}
+
+	return deps
+}
+
+func (sanitize *sanitize) flags(ctx ModuleContext, flags Flags) Flags {
+	if !sanitize.Properties.SanitizerEnabled {
+		return flags
+	}
+
+	if !ctx.clang() {
+		ctx.ModuleErrorf("Use of sanitizers requires clang")
+	}
+
+	var sanitizers []string
+
+	if sanitize.Properties.Sanitize.All_undefined {
+		sanitizers = append(sanitizers, "undefined")
+		if ctx.Device() {
+			ctx.ModuleErrorf("ubsan is not yet supported on the device")
+		}
+	} else {
+		if sanitize.Properties.Sanitize.Undefined {
+			sanitizers = append(sanitizers,
+				"bool",
+				"integer-divide-by-zero",
+				"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",
+				// TODO(danalbert): Fix UB in libc++'s __tree so we can turn this on.
+				// https://llvm.org/PR19302
+				// http://reviews.llvm.org/D6974
+				// "object-size",
+			)
+		}
+		sanitizers = append(sanitizers, sanitize.Properties.Sanitize.Misc_undefined...)
+	}
+
+	if sanitize.Properties.Sanitize.Address {
+		if ctx.Arch().ArchType == common.Arm {
+			// Frame pointer based unwinder in ASan requires ARM frame setup.
+			// TODO: put in flags?
+			flags.RequiredInstructionSet = "arm"
+		}
+		flags.CFlags = append(flags.CFlags, "-fno-omit-frame-pointer")
+		flags.LdFlags = append(flags.LdFlags, "-Wl,-u,__asan_preinit")
+
+		// ASan runtime library must be the first in the link order.
+		runtimeLibrary := ctx.toolchain().AddressSanitizerRuntimeLibrary()
+		if runtimeLibrary != "" {
+			flags.libFlags = append([]string{"${clangAsanLibDir}/" + runtimeLibrary}, flags.libFlags...)
+		}
+		if ctx.Host() {
+			// -nodefaultlibs (provided with libc++) prevents the driver from linking
+			// libraries needed with -fsanitize=address. http://b/18650275 (WAI)
+			flags.LdFlags = append(flags.LdFlags, "-lm", "-lpthread")
+			flags.LdFlags = append(flags.LdFlags, "-Wl,--no-as-needed")
+		} else {
+			flags.CFlags = append(flags.CFlags, "-mllvm", "-asan-globals=0")
+			flags.DynamicLinker = "/system/bin/linker_asan"
+			if flags.Toolchain.Is64Bit() {
+				flags.DynamicLinker += "64"
+			}
+		}
+		sanitizers = append(sanitizers, "address")
+	}
+
+	if sanitize.Properties.Sanitize.Coverage {
+		flags.CFlags = append(flags.CFlags, "-fsanitize-coverage=edge,indirect-calls,8bit-counters,trace-cmp")
+	}
+
+	if sanitize.Properties.Sanitize.Recover != nil {
+		flags.CFlags = append(flags.CFlags, "-fsanitize-recover="+
+			strings.Join(sanitize.Properties.Sanitize.Recover, ","))
+	}
+
+	if len(sanitizers) > 0 {
+		sanitizeArg := "-fsanitize=" + strings.Join(sanitizers, ",")
+		flags.CFlags = append(flags.CFlags, sanitizeArg)
+		if ctx.Host() {
+			flags.CFlags = append(flags.CFlags, "-fno-sanitize-recover=all")
+			flags.LdFlags = append(flags.LdFlags, sanitizeArg)
+			flags.LdFlags = append(flags.LdFlags, "-lrt", "-ldl")
+		} else {
+			if !sanitize.Properties.Sanitize.Address {
+				flags.CFlags = append(flags.CFlags, "-fsanitize-trap=all", "-ftrap-function=abort")
+			}
+		}
+	}
+
+	blacklist := common.OptionalPathForModuleSrc(ctx, sanitize.Properties.Sanitize.Blacklist)
+	if blacklist.Valid() {
+		flags.CFlags = append(flags.CFlags, "-fsanitize-blacklist="+blacklist.String())
+		flags.CFlagsDeps = append(flags.CFlagsDeps, blacklist.Path())
+	}
+
+	return flags
+}
+
+func (sanitize *sanitize) Sanitizer(t sanitizerType) bool {
+	if sanitize == nil {
+		return false
+	}
+
+	switch t {
+	case asan:
+		return sanitize.Properties.Sanitize.Address
+	case tsan:
+		return sanitize.Properties.Sanitize.Thread
+	default:
+		panic(fmt.Errorf("unknown sanitizerType %d", t))
+	}
+}
+
+func (sanitize *sanitize) SetSanitizer(t sanitizerType, b bool) {
+	switch t {
+	case asan:
+		sanitize.Properties.Sanitize.Address = b
+	case tsan:
+		sanitize.Properties.Sanitize.Thread = b
+	default:
+		panic(fmt.Errorf("unknown sanitizerType %d", t))
+	}
+	if b {
+		sanitize.Properties.SanitizerEnabled = true
+	}
+}
+
+// Propagate asan requirements down from binaries
+func sanitizerDepsMutator(t sanitizerType) func(common.AndroidTopDownMutatorContext) {
+	return func(mctx common.AndroidTopDownMutatorContext) {
+		if c, ok := mctx.Module().(*Module); ok && c.sanitize.Sanitizer(t) {
+			mctx.VisitDepsDepthFirst(func(module blueprint.Module) {
+				if d, ok := mctx.Module().(*Module); ok && c.sanitize != nil &&
+					!c.sanitize.Properties.Sanitize.Never {
+					d.sanitize.Properties.SanitizeDep = true
+				}
+			})
+		}
+	}
+}
+
+// Create asan variants for modules that need them
+func sanitizerMutator(t sanitizerType) func(common.AndroidBottomUpMutatorContext) {
+	return func(mctx common.AndroidBottomUpMutatorContext) {
+		if c, ok := mctx.Module().(*Module); ok && c.sanitize != nil {
+			if d, ok := c.linker.(baseLinkerInterface); ok && d.isDependencyRoot() && c.sanitize.Sanitizer(t) {
+				mctx.CreateVariations(t.String())
+			} else if c.sanitize.Properties.SanitizeDep {
+				modules := mctx.CreateVariations("", t.String())
+				modules[0].(*Module).sanitize.SetSanitizer(t, false)
+				modules[1].(*Module).sanitize.SetSanitizer(t, true)
+				modules[1].(*Module).appendVariantName("_" + t.String())
+				modules[0].(*Module).sanitize.Properties.SanitizeDep = false
+				modules[1].(*Module).sanitize.Properties.SanitizeDep = false
+			}
+			c.sanitize.Properties.SanitizeDep = false
+		}
+	}
+}
diff --git a/cc/toolchain.go b/cc/toolchain.go
index 397ec09..09dcff2 100644
--- a/cc/toolchain.go
+++ b/cc/toolchain.go
@@ -74,6 +74,8 @@
 
 	SystemCppCppflags() string
 	SystemCppLdflags() string
+
+	AddressSanitizerRuntimeLibrary() string
 }
 
 type toolchainBase struct {
@@ -133,6 +135,10 @@
 	return ""
 }
 
+func (toolchainBase) AddressSanitizerRuntimeLibrary() string {
+	return ""
+}
+
 type toolchain64Bit struct {
 	toolchainBase
 }
diff --git a/cc/util.go b/cc/util.go
index 1d27f73..70433b1 100644
--- a/cc/util.go
+++ b/cc/util.go
@@ -40,14 +40,18 @@
 	return common.JoinWithPrefix(names, "-l")
 }
 
-func inList(s string, list []string) bool {
-	for _, l := range list {
+func indexList(s string, list []string) int {
+	for i, l := range list {
 		if l == s {
-			return true
+			return i
 		}
 	}
 
-	return false
+	return -1
+}
+
+func inList(s string, list []string) bool {
+	return indexList(s, list) != -1
 }
 
 func filterList(list []string, filter []string) (remainder []string, filtered []string) {
@@ -62,6 +66,15 @@
 	return
 }
 
+func removeFromList(s string, list []string) (bool, []string) {
+	i := indexList(s, list)
+	if i != -1 {
+		return true, append(list[:i], list[i+1:]...)
+	} else {
+		return false, list
+	}
+}
+
 var libNameRegexp = regexp.MustCompile(`^lib(.*)$`)
 
 func moduleToLibName(module string) (string, error) {
@@ -81,6 +94,7 @@
 		cppFlags:    strings.Join(in.CppFlags, " "),
 		yaccFlags:   strings.Join(in.YaccFlags, " "),
 		ldFlags:     strings.Join(in.LdFlags, " "),
+		libFlags:    strings.Join(in.libFlags, " "),
 		nocrt:       in.Nocrt,
 		toolchain:   in.Toolchain,
 		clang:       in.Clang,
diff --git a/cc/x86_device.go b/cc/x86_device.go
index 92cae92..6b90f17 100644
--- a/cc/x86_device.go
+++ b/cc/x86_device.go
@@ -236,6 +236,10 @@
 	return "${x86Ldflags}"
 }
 
+func (toolchainX86) AddressSanitizerRuntimeLibrary() string {
+	return "libclang_rt.asan-i686-android.so"
+}
+
 func x86ToolchainFactory(arch common.Arch) Toolchain {
 	toolchainCflags := []string{
 		"${x86ToolchainCflags}",
diff --git a/common/config.go b/common/config.go
index 0812903..5e2f715 100644
--- a/common/config.go
+++ b/common/config.go
@@ -307,3 +307,17 @@
 func (c *config) SkipDeviceInstall() bool {
 	return c.EmbeddedInMake() || Bool(c.Mega_device)
 }
+
+func (c *config) SanitizeHost() []string {
+	if c.ProductVariables.SanitizeHost == nil {
+		return nil
+	}
+	return *c.ProductVariables.SanitizeHost
+}
+
+func (c *config) SanitizeDevice() []string {
+	if c.ProductVariables.SanitizeDevice == nil {
+		return nil
+	}
+	return *c.ProductVariables.SanitizeDevice
+}
diff --git a/common/variable.go b/common/variable.go
index de31324..ae72761 100644
--- a/common/variable.go
+++ b/common/variable.go
@@ -77,6 +77,9 @@
 	Unbundled_build            *bool `json:",omitempty"`
 	Brillo                     *bool `json:",omitempty"`
 	Malloc_not_svelte          *bool `json:",omitempty"`
+
+	SanitizeHost   *[]string `json:",omitempty"`
+	SanitizeDevice *[]string `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {