Merge "rust: Support new rust_stdlib_prebuilt_host type"
diff --git a/android/bazel.go b/android/bazel.go
index 3bc104d..26d70e4 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -188,6 +188,7 @@
 		"packages/apps/QuickSearchBox":/* recursive = */ true,
 		"packages/apps/WallpaperPicker":/* recursive = */ false,
 
+		"prebuilts/gcc":/* recursive = */ true,
 		"prebuilts/sdk":/* recursive = */ false,
 		"prebuilts/sdk/current/extras/app-toolkit":/* recursive = */ false,
 		"prebuilts/sdk/current/support":/* recursive = */ false,
@@ -250,6 +251,8 @@
 
 	// Per-module denylist to always opt modules out of both bp2build and mixed builds.
 	bp2buildModuleDoNotConvertList = []string{
+		"libprotobuf-cpp-full", "libprotobuf-cpp-lite", // Unsupported product&vendor suffix. b/204811222 and b/204810610.
+
 		"libc_malloc_debug", // depends on libunwindstack, which depends on unsupported module art_cc_library_statics
 
 		"libbase_ndk", // http://b/186826477, fails to link libctscamera2_jni for device (required for CtsCameraTestCases)
@@ -270,14 +273,8 @@
 		"platform_tools_properties",
 		"build_tools_source_properties",
 
-		// //external/libcap/...
-		"cap_names.h", // http://b/196105070 host toolchain misconfigurations for mixed builds
-		"libcap",      // http://b/196105070 host toolchain misconfigurations for mixed builds
-
-		"libminijail", // depends on unconverted modules: libcap
-		"getcap",      // depends on unconverted modules: libcap
-		"setcap",      // depends on unconverted modules: libcap
-		"minijail0",   // depends on unconverted modules: libcap, libminijail
+		"libminijail", // b/202491296: Uses unsupported c_std property.
+		"minijail0",   // depends on unconverted modules: libminijail
 		"drop_privs",  // depends on unconverted modules: libminijail
 
 		// Tests. Handle later.
@@ -381,7 +378,11 @@
 
 // MixedBuildsEnabled checks that a module is ready to be replaced by a
 // converted or handcrafted Bazel target.
-func (b *BazelModuleBase) MixedBuildsEnabled(ctx BazelConversionPathContext) bool {
+func (b *BazelModuleBase) MixedBuildsEnabled(ctx ModuleContext) bool {
+	if ctx.Os() == Windows {
+		// Windows toolchains are not currently supported.
+		return false
+	}
 	if !ctx.Config().BazelContext.BazelEnabled() {
 		return false
 	}
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 3c6212e..0052551 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -602,8 +602,6 @@
   platform_name = build_options(target)["//command_line_option:platforms"][0].name
   if platform_name == "host":
     return "HOST"
-  elif platform_name.startswith("linux_glibc_"):
-    return platform_name[len("linux_glibc_"):] + "|" + platform_name[:len("linux_glibc_")-1]
   elif platform_name.startswith("android_"):
     return platform_name[len("android_"):] + "|" + platform_name[:len("android_")-1]
   elif platform_name.startswith("linux_"):
@@ -865,7 +863,7 @@
 		arch = "x86_64"
 	}
 	os := key.configKey.osType.Name
-	if len(os) == 0 || os == "common_os" {
+	if len(os) == 0 || os == "common_os" || os == "linux_glibc" {
 		// Use host OS, which is currently hardcoded to be linux.
 		os = "linux"
 	}
diff --git a/android/makevars.go b/android/makevars.go
index 8825d1a..665d576 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -422,6 +422,7 @@
 	fmt.Fprintln(buf)
 
 	for _, dist := range dists {
+		fmt.Fprintf(buf, ".PHONY: %s\n", strings.Join(dist.goals, " "))
 		fmt.Fprintf(buf, "$(call dist-for-goals,%s,%s)\n",
 			strings.Join(dist.goals, " "), strings.Join(dist.paths, " "))
 	}
diff --git a/cc/cc.go b/cc/cc.go
index 8915d6f..113620c 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -1717,7 +1717,7 @@
 	bazelActionsUsed := false
 	// Mixed builds mode is disabled for modules outside of device OS.
 	// TODO(b/200841190): Support non-device OS in mixed builds.
-	if c.MixedBuildsEnabled(actx) && c.bazelHandler != nil && actx.Os().Class == android.Device {
+	if c.MixedBuildsEnabled(actx) && c.bazelHandler != nil {
 		bazelActionsUsed = c.bazelHandler.GenerateBazelBuildActions(actx, bazelModuleLabel)
 	}
 	return bazelActionsUsed
diff --git a/cc/sanitize.go b/cc/sanitize.go
index c7e8411..93d4b4c 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -88,7 +88,7 @@
 	intOverflow
 	scs
 	Fuzzer
-	memtag_heap
+	Memtag_heap
 	cfi // cfi is last to prevent it running before incompatible mutators
 )
 
@@ -99,7 +99,7 @@
 	intOverflow,
 	scs,
 	Fuzzer,
-	memtag_heap,
+	Memtag_heap,
 	cfi, // cfi is last to prevent it running before incompatible mutators
 }
 
@@ -118,7 +118,7 @@
 		return "cfi"
 	case scs:
 		return "scs"
-	case memtag_heap:
+	case Memtag_heap:
 		return "memtag_heap"
 	case Fuzzer:
 		return "fuzzer"
@@ -134,7 +134,7 @@
 		return "address"
 	case Hwasan:
 		return "hwaddress"
-	case memtag_heap:
+	case Memtag_heap:
 		return "memtag_heap"
 	case tsan:
 		return "thread"
@@ -156,7 +156,7 @@
 	case Asan, Hwasan, Fuzzer, scs, tsan, cfi:
 		ctx.TopDown(t.variationName()+"_deps", sanitizerDepsMutator(t))
 		ctx.BottomUp(t.variationName(), sanitizerMutator(t))
-	case memtag_heap, intOverflow:
+	case Memtag_heap, intOverflow:
 		// do nothing
 	default:
 		panic(fmt.Errorf("unknown SanitizerType %d", t))
@@ -179,6 +179,8 @@
 		return true
 	case Fuzzer:
 		return true
+	case Memtag_heap:
+		return true
 	default:
 		return false
 	}
@@ -467,7 +469,7 @@
 		s.Scs = nil
 	}
 
-	// memtag_heap is only implemented on AArch64.
+	// Memtag_heap is only implemented on AArch64.
 	if ctx.Arch().ArchType != android.Arm64 {
 		s.Memtag_heap = nil
 	}
@@ -813,7 +815,7 @@
 		return sanitize.Properties.Sanitize.Cfi
 	case scs:
 		return sanitize.Properties.Sanitize.Scs
-	case memtag_heap:
+	case Memtag_heap:
 		return sanitize.Properties.Sanitize.Memtag_heap
 	case Fuzzer:
 		return sanitize.Properties.Sanitize.Fuzzer
@@ -829,7 +831,7 @@
 		!sanitize.isSanitizerEnabled(tsan) &&
 		!sanitize.isSanitizerEnabled(cfi) &&
 		!sanitize.isSanitizerEnabled(scs) &&
-		!sanitize.isSanitizerEnabled(memtag_heap) &&
+		!sanitize.isSanitizerEnabled(Memtag_heap) &&
 		!sanitize.isSanitizerEnabled(Fuzzer)
 }
 
@@ -859,7 +861,7 @@
 		sanitize.Properties.Sanitize.Cfi = bPtr
 	case scs:
 		sanitize.Properties.Sanitize.Scs = bPtr
-	case memtag_heap:
+	case Memtag_heap:
 		sanitize.Properties.Sanitize.Memtag_heap = bPtr
 	case Fuzzer:
 		sanitize.Properties.Sanitize.Fuzzer = bPtr
@@ -1148,7 +1150,7 @@
 			if lib, ok := snapshot.StaticLibs[noteDep]; ok {
 				noteDep = lib
 			}
-			depTag := libraryDependencyTag{Kind: staticLibraryDependency, wholeStatic: true}
+			depTag := StaticDepTag(true)
 			variations := append(mctx.Target().Variations(),
 				blueprint.Variation{Mutator: "link", Variation: "static"})
 			if c.Device() {
@@ -1318,6 +1320,10 @@
 func sanitizerMutator(t SanitizerType) func(android.BottomUpMutatorContext) {
 	return func(mctx android.BottomUpMutatorContext) {
 		if c, ok := mctx.Module().(PlatformSanitizeable); ok && c.SanitizePropDefined() {
+
+			// Make sure we're not setting CFI to any value if it's not supported.
+			cfiSupported := mctx.Module().(PlatformSanitizeable).SanitizerSupported(cfi)
+
 			if c.Binary() && c.IsSanitizerEnabled(t) {
 				modules := mctx.CreateVariations(t.variationName())
 				modules[0].(PlatformSanitizeable).SetSanitizer(t, true)
@@ -1338,7 +1344,6 @@
 					// is redirected to the sanitized variant of the dependent module.
 					defaultVariation := t.variationName()
 					// Not all PlatformSanitizeable modules support the CFI sanitizer
-					cfiSupported := mctx.Module().(PlatformSanitizeable).SanitizerSupported(cfi)
 					mctx.SetDefaultDependencyVariation(&defaultVariation)
 
 					modules := mctx.CreateVariations("", t.variationName())
@@ -1385,7 +1390,7 @@
 						modules[0].(PlatformSanitizeable).SetInSanitizerDir()
 					}
 
-					if mctx.Device() && t.incompatibleWithCfi() {
+					if mctx.Device() && t.incompatibleWithCfi() && cfiSupported {
 						// TODO: Make sure that cfi mutator runs "after" any of the sanitizers that
 						// are incompatible with cfi
 						modules[0].(PlatformSanitizeable).SetSanitizer(cfi, false)
diff --git a/mk2rbc/cmd/mk2rbc.go b/mk2rbc/cmd/mk2rbc.go
index 0f030d3..7bb0246 100644
--- a/mk2rbc/cmd/mk2rbc.go
+++ b/mk2rbc/cmd/mk2rbc.go
@@ -81,7 +81,7 @@
 
 var backupSuffix string
 var tracedVariables []string
-var errorLogger = errorsByType{data: make(map[string]datum)}
+var errorLogger = errorSink{data: make(map[string]datum)}
 var makefileFinder = &LinuxMakefileFinder{}
 var versionDefaultsMk = filepath.Join("build", "make", "core", "version_defaults.mk")
 
@@ -339,9 +339,7 @@
 		WarnPartialSuccess: !*noWarn,
 		SourceFS:           os.DirFS(*rootDir),
 		MakefileFinder:     makefileFinder,
-	}
-	if *errstat {
-		mk2starRequest.ErrorLogger = errorLogger
+		ErrorLogger:        errorLogger,
 	}
 	ss, err := mk2rbc.Convert(mk2starRequest)
 	if err != nil {
@@ -456,10 +454,8 @@
 			}
 		}
 	}
-	if *verbose {
-		fmt.Fprintf(os.Stderr, "%-16s%5d\n", "Succeeded:", nOk)
-		fmt.Fprintf(os.Stderr, "%-16s%5d\n", "Partial:", nPartial)
-		fmt.Fprintf(os.Stderr, "%-16s%5d\n", "Failed:", nFailed)
+	if *verbose && (nPartial > 0 || nFailed > 0) {
+		fmt.Fprintln(os.Stderr, "Succeeded: ", nOk, " Partial: ", nPartial, " Failed: ", nFailed)
 	}
 }
 
@@ -468,11 +464,18 @@
 	formattingArgs []string
 }
 
-type errorsByType struct {
+type errorSink struct {
 	data map[string]datum
 }
 
-func (ebt errorsByType) NewError(message string, node parser.Node, args ...interface{}) {
+func (ebt errorSink) NewError(sourceFile string, sourceLine int, node parser.Node, message string, args ...interface{}) {
+	fmt.Fprintf(os.Stderr, "%s:%d ", sourceFile, sourceLine)
+	fmt.Fprintf(os.Stderr, message, args...)
+	fmt.Fprintln(os.Stderr)
+	if !*errstat {
+		return
+	}
+
 	v, exists := ebt.data[message]
 	if exists {
 		v.count++
@@ -497,7 +500,7 @@
 	ebt.data[message] = v
 }
 
-func (ebt errorsByType) printStatistics() {
+func (ebt errorSink) printStatistics() {
 	if len(ebt.data) > 0 {
 		fmt.Fprintln(os.Stderr, "Error counts:")
 	}
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index 0e39c32..7ce1834 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -161,7 +161,7 @@
 	RootDir            string    // root directory path used to resolve included files
 	OutputSuffix       string    // generated Starlark files suffix
 	OutputDir          string    // if set, root of the output hierarchy
-	ErrorLogger        ErrorMonitorCB
+	ErrorLogger        ErrorLogger
 	TracedVariables    []string // trace assignment to these variables
 	TraceCalls         bool
 	WarnPartialSuccess bool
@@ -169,10 +169,10 @@
 	MakefileFinder     MakefileFinder
 }
 
-// An error sink allowing to gather error statistics.
-// NewError is called on every error encountered during processing.
-type ErrorMonitorCB interface {
-	NewError(s string, node mkparser.Node, args ...interface{})
+// ErrorLogger prints errors and gathers error statistics.
+// Its NewError function is called on every error encountered during the conversion.
+type ErrorLogger interface {
+	NewError(sourceFile string, sourceLine int, node mkparser.Node, text string, args ...interface{})
 }
 
 // Derives module name for a given file. It is base name
@@ -381,6 +381,7 @@
 	warnPartialSuccess bool
 	sourceFS           fs.FS
 	makefileFinder     MakefileFinder
+	nodeLocator        func(pos mkparser.Pos) int
 }
 
 func (ss *StarlarkScript) newNode(node starlarkNode) {
@@ -406,7 +407,7 @@
 	fatalError       error
 	builtinMakeVars  map[string]starlarkExpr
 	outputSuffix     string
-	errorLogger      ErrorMonitorCB
+	errorLogger      ErrorLogger
 	tracedVariables  map[string]bool // variables to be traced in the generated script
 	variables        map[string]variable
 	varAssignments   *varAssignmentScope
@@ -973,25 +974,16 @@
 	ctx.pushReceiver(&block)
 	for ctx.hasNodes() {
 		node := ctx.getNode()
-		if ctx.handleSimpleStatement(node) {
-			continue
-		}
-		switch d := node.(type) {
-		case *mkparser.Directive:
+		if d, ok := node.(*mkparser.Directive); ok {
 			switch d.Name {
 			case "else", "elifdef", "elifndef", "elifeq", "elifneq", "endif":
 				ctx.popReceiver()
 				ctx.receiver.newNode(&block)
 				ctx.backNode()
 				return
-			case "ifdef", "ifndef", "ifeq", "ifneq":
-				ctx.handleIfBlock(d)
-			default:
-				ctx.errorf(d, "unexpected directive %s", d.Name)
 			}
-		default:
-			ctx.errorf(node, "unexpected statement")
 		}
+		ctx.handleSimpleStatement(node)
 	}
 	ctx.fatalError = fmt.Errorf("no matching endif for %s", check.Dump())
 	ctx.popReceiver()
@@ -1031,7 +1023,7 @@
 func (ctx *parseContext) newBadExpr(node mkparser.Node, text string, args ...interface{}) starlarkExpr {
 	message := fmt.Sprintf(text, args...)
 	if ctx.errorLogger != nil {
-		ctx.errorLogger.NewError(text, node, args)
+		ctx.errorLogger.NewError(ctx.script.mkFile, ctx.script.nodeLocator(node.Pos()), node, text, args...)
 	}
 	ctx.script.hasErrors = true
 	return &badExpr{node, message}
@@ -1493,9 +1485,7 @@
 // Handles the statements whose treatment is the same in all contexts: comment,
 // assignment, variable (which is a macro call in reality) and all constructs that
 // do not handle in any context ('define directive and any unrecognized stuff).
-// Return true if we handled it.
-func (ctx *parseContext) handleSimpleStatement(node mkparser.Node) bool {
-	handled := true
+func (ctx *parseContext) handleSimpleStatement(node mkparser.Node) {
 	switch x := node.(type) {
 	case *mkparser.Comment:
 		ctx.maybeHandleAnnotation(x)
@@ -1510,13 +1500,14 @@
 			ctx.handleDefine(x)
 		case "include", "-include":
 			ctx.handleInclude(node, ctx.parseMakeString(node, x.Args), x.Name[0] != '-')
+		case "ifeq", "ifneq", "ifdef", "ifndef":
+			ctx.handleIfBlock(x)
 		default:
-			handled = false
+			ctx.errorf(x, "unexpected directive %s", x.Name)
 		}
 	default:
 		ctx.errorf(x, "unsupported line %s", strings.ReplaceAll(x.Dump(), "\n", "\n#"))
 	}
-	return handled
 }
 
 // Processes annotation. An annotation is a comment that starts with #RBC# and provides
@@ -1554,11 +1545,12 @@
 // records that the given node failed to be converted and includes an explanatory message
 func (ctx *parseContext) errorf(failedNode mkparser.Node, message string, args ...interface{}) {
 	if ctx.errorLogger != nil {
-		ctx.errorLogger.NewError(message, failedNode, args...)
+		ctx.errorLogger.NewError(ctx.script.mkFile, ctx.script.nodeLocator(failedNode.Pos()), failedNode, message, args...)
 	}
 	message = fmt.Sprintf(message, args...)
 	ctx.insertComment(fmt.Sprintf("# MK2RBC TRANSLATION ERROR: %s", message))
 	ctx.carryAsComment(failedNode)
+
 	ctx.script.hasErrors = true
 }
 
@@ -1675,6 +1667,7 @@
 		warnPartialSuccess: req.WarnPartialSuccess,
 		sourceFS:           req.SourceFS,
 		makefileFinder:     req.MakefileFinder,
+		nodeLocator:        func(pos mkparser.Pos) int { return parser.Unpack(pos).Line },
 	}
 	ctx := newParseContext(starScript, nodes)
 	ctx.outputSuffix = req.OutputSuffix
@@ -1688,21 +1681,7 @@
 	}
 	ctx.pushReceiver(starScript)
 	for ctx.hasNodes() && ctx.fatalError == nil {
-		node := ctx.getNode()
-		if ctx.handleSimpleStatement(node) {
-			continue
-		}
-		switch x := node.(type) {
-		case *mkparser.Directive:
-			switch x.Name {
-			case "ifeq", "ifneq", "ifdef", "ifndef":
-				ctx.handleIfBlock(x)
-			default:
-				ctx.errorf(x, "unexpected directive %s", x.Name)
-			}
-		default:
-			ctx.errorf(x, "unsupported line")
-		}
+		ctx.handleSimpleStatement(ctx.getNode())
 	}
 	if ctx.fatalError != nil {
 		return nil, ctx.fatalError
diff --git a/rust/Android.bp b/rust/Android.bp
index 0ee673d..cda2dbc 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -54,6 +54,7 @@
         "project_json_test.go",
         "protobuf_test.go",
         "rust_test.go",
+        "sanitize_test.go",
         "source_provider_test.go",
         "test_test.go",
         "vendor_snapshot_test.go",
diff --git a/rust/binary.go b/rust/binary.go
index cba2f72..db91ccb 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -34,6 +34,7 @@
 type binaryInterface interface {
 	binary() bool
 	staticallyLinked() bool
+	testBinary() bool
 }
 
 type binaryDecorator struct {
@@ -172,3 +173,7 @@
 func (binary *binaryDecorator) staticallyLinked() bool {
 	return Bool(binary.Properties.Static_executable)
 }
+
+func (binary *binaryDecorator) testBinary() bool {
+	return false
+}
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index d66db14..9b88b6d 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -17,6 +17,7 @@
 		"external/vm_tools/p9",
 		"frameworks/native/libs/binder/rust",
 		"frameworks/proto_logging/stats",
+		"hardware/interfaces/security",
 		"packages/modules/Bluetooth",
 		"packages/modules/DnsResolver",
 		"packages/modules/Uwb",
diff --git a/rust/fuzz.go b/rust/fuzz.go
index c52f5f9..55921ba 100644
--- a/rust/fuzz.go
+++ b/rust/fuzz.go
@@ -36,7 +36,7 @@
 	fuzzPackagedModule fuzz.FuzzPackagedModule
 }
 
-var _ compiler = (*binaryDecorator)(nil)
+var _ compiler = (*fuzzDecorator)(nil)
 
 // rust_binary produces a binary that is runnable on a device.
 func RustFuzzFactory() android.Module {
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 9b518c8..b99b1e6 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -442,3 +442,10 @@
 	m.Output("unstripped/libwaldo.dylib.so.bloaty.csv")
 	m.Output("libwaldo.dylib.so.bloaty.csv")
 }
+
+func assertString(t *testing.T, got, expected string) {
+	t.Helper()
+	if got != expected {
+		t.Errorf("expected %q got %q", expected, got)
+	}
+}
diff --git a/rust/sanitize.go b/rust/sanitize.go
index baa383d..fdb342d 100644
--- a/rust/sanitize.go
+++ b/rust/sanitize.go
@@ -15,20 +15,39 @@
 package rust
 
 import (
+	"fmt"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/rust/config"
-	"fmt"
-	"github.com/google/blueprint"
 )
 
+// TODO: When Rust has sanitizer-parity with CC, deduplicate this struct
 type SanitizeProperties struct {
 	// enable AddressSanitizer, HWAddressSanitizer, and others.
 	Sanitize struct {
 		Address   *bool `android:"arch_variant"`
 		Hwaddress *bool `android:"arch_variant"`
-		Fuzzer    *bool `android:"arch_variant"`
-		Never     *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"`
+		Fuzzer      *bool `android:"arch_variant"`
+		Never       *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 {
+			// Memory-tagging, only available on arm64
+			// requires sanitizer.memtag: true
+			// if set, enables sync memory tagging
+			Memtag_heap *bool `android:"arch_variant"`
+		}
 	}
 	SanitizerEnabled bool `blueprint:"mutated"`
 	SanitizeDep      bool `blueprint:"mutated"`
@@ -59,9 +78,18 @@
 	"-Z sanitizer=address",
 }
 
+// See cc/sanitize.go's hwasanGlobalOptions for global hwasan options.
 var hwasanFlags = []string{
 	"-Z sanitizer=hwaddress",
 	"-C target-feature=+tagged-globals",
+
+	// Flags from cc/sanitize.go hwasanFlags
+	"-C llvm-args=--aarch64-enable-global-isel-at-O=-1",
+	"-C llvm-args=-fast-isel=false",
+	"-C llvm-args=-instcombine-lower-dbg-declare=0",
+
+	// Additional flags for HWASAN-ified Rust/C interop
+	"-C llvm-args=--hwasan-with-ifunc",
 }
 
 func boolPtr(v bool) *bool {
@@ -79,7 +107,85 @@
 }
 
 func (sanitize *sanitize) begin(ctx BaseModuleContext) {
-	s := sanitize.Properties.Sanitize
+	s := &sanitize.Properties.Sanitize
+
+	// Never always wins.
+	if Bool(s.Never) {
+		return
+	}
+
+	// rust_test targets default to SYNC MemTag unless explicitly set to ASYNC (via diag: {Memtag_heap}).
+	if binary, ok := ctx.RustModule().compiler.(binaryInterface); ok && binary.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 || android.InList(ctx.Arch().ArchType.Name, arches) {
+			globalSanitizers = ctx.Config().SanitizeDevice()
+			globalSanitizersDiag = ctx.Config().SanitizeDeviceDiag()
+		}
+	}
+
+	if len(globalSanitizers) > 0 {
+		var found bool
+
+		// Global Sanitizers
+		if found, globalSanitizers = android.RemoveFromList("hwaddress", globalSanitizers); found && s.Hwaddress == nil {
+			// TODO(b/180495975): HWASan for static Rust binaries isn't supported yet.
+			if !ctx.RustModule().StaticExecutable() {
+				s.Hwaddress = proptools.BoolPtr(true)
+			}
+		}
+
+		if found, globalSanitizers = android.RemoveFromList("memtag_heap", globalSanitizers); found && s.Memtag_heap == nil {
+			if !ctx.Config().MemtagHeapDisabledForPath(ctx.ModuleDir()) {
+				s.Memtag_heap = proptools.BoolPtr(true)
+			}
+		}
+
+		if found, globalSanitizers = android.RemoveFromList("address", globalSanitizers); found && s.Address == nil {
+			s.Address = proptools.BoolPtr(true)
+		}
+
+		if found, globalSanitizers = android.RemoveFromList("fuzzer", globalSanitizers); found && s.Fuzzer == nil {
+			s.Fuzzer = proptools.BoolPtr(true)
+		}
+
+		// Global Diag Sanitizers
+		if found, globalSanitizersDiag = android.RemoveFromList("memtag_heap", globalSanitizersDiag); found &&
+			s.Diag.Memtag_heap == nil && Bool(s.Memtag_heap) {
+			s.Diag.Memtag_heap = proptools.BoolPtr(true)
+		}
+	}
+
+	// Enable Memtag for all components in the include paths (for Aarch64 only)
+	if ctx.Arch().ArchType == android.Arm64 {
+		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)
+			}
+		}
+	}
 
 	// TODO:(b/178369775)
 	// For now sanitizing is only supported on devices
@@ -96,7 +202,22 @@
 		s.Hwaddress = nil
 	}
 
-	if ctx.Os() == android.Android && Bool(s.Hwaddress) {
+	// 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 (ctx.RustModule().InRamdisk() || ctx.RustModule().InVendorRamdisk() || ctx.RustModule().InRecovery()) && !strings.HasPrefix(ctx.ModuleDir(), "bionic/libc") {
+		s.Hwaddress = nil
+	}
+
+	if Bool(s.Hwaddress) {
+		s.Address = nil
+	}
+
+	// Memtag_heap is only implemented on AArch64.
+	if ctx.Arch().ArchType != android.Arm64 {
+		s.Memtag_heap = nil
+	}
+
+	if ctx.Os() == android.Android && (Bool(s.Hwaddress) || Bool(s.Address) || Bool(s.Memtag_heap)) {
 		sanitize.Properties.SanitizerEnabled = true
 	}
 }
@@ -136,6 +257,26 @@
 			return
 		}
 
+		if Bool(mod.sanitize.Properties.Sanitize.Memtag_heap) && mod.Binary() {
+			noteDep := "note_memtag_heap_async"
+			if Bool(mod.sanitize.Properties.Sanitize.Diag.Memtag_heap) {
+				noteDep = "note_memtag_heap_sync"
+			}
+			// If we're using snapshots, redirect to snapshot whenever possible
+			// TODO(b/178470649): clean manual snapshot redirections
+			snapshot := mctx.Provider(cc.SnapshotInfoProvider).(cc.SnapshotInfo)
+			if lib, ok := snapshot.StaticLibs[noteDep]; ok {
+				noteDep = lib
+			}
+			depTag := cc.StaticDepTag(true)
+			variations := append(mctx.Target().Variations(),
+				blueprint.Variation{Mutator: "link", Variation: "static"})
+			if mod.Device() {
+				variations = append(variations, mod.ImageVariation())
+			}
+			mctx.AddFarVariationDependencies(variations, depTag, noteDep)
+		}
+
 		variations := mctx.Target().Variations()
 		var depTag blueprint.DependencyTag
 		var deps []string
@@ -149,26 +290,23 @@
 		} else if mod.IsSanitizerEnabled(cc.Hwasan) ||
 			(mod.IsSanitizerEnabled(cc.Fuzzer) && mctx.Arch().ArchType == android.Arm64) {
 			// TODO(b/180495975): HWASan for static Rust binaries isn't supported yet.
-			if binary, ok := mod.compiler.(*binaryDecorator); ok {
-				if Bool(binary.Properties.Static_executable) {
+			if binary, ok := mod.compiler.(binaryInterface); ok {
+				if binary.staticallyLinked() {
 					mctx.ModuleErrorf("HWASan is not supported for static Rust executables yet.")
 				}
 			}
 
-			if mod.StaticallyLinked() {
-				variations = append(variations,
-					blueprint.Variation{Mutator: "link", Variation: "static"})
-				depTag = cc.StaticDepTag(false)
-				deps = []string{config.LibclangRuntimeLibrary(mod.toolchain(mctx), "hwasan_static")}
-			} else {
-				variations = append(variations,
-					blueprint.Variation{Mutator: "link", Variation: "shared"})
-				depTag = cc.SharedDepTag()
-				deps = []string{config.LibclangRuntimeLibrary(mod.toolchain(mctx), "hwasan")}
-			}
+			// Always link against the shared library -- static binaries will pull in the static
+			// library during final link if necessary
+			variations = append(variations,
+				blueprint.Variation{Mutator: "link", Variation: "shared"})
+			depTag = cc.SharedDepTag()
+			deps = []string{config.LibclangRuntimeLibrary(mod.toolchain(mctx), "hwasan")}
 		}
 
-		mctx.AddFarVariationDependencies(variations, depTag, deps...)
+		if len(deps) > 0 {
+			mctx.AddFarVariationDependencies(variations, depTag, deps...)
+		}
 	}
 }
 
@@ -184,6 +322,9 @@
 	case cc.Hwasan:
 		sanitize.Properties.Sanitize.Hwaddress = boolPtr(b)
 		sanitizerSet = true
+	case cc.Memtag_heap:
+		sanitize.Properties.Sanitize.Memtag_heap = boolPtr(b)
+		sanitizerSet = true
 	default:
 		panic(fmt.Errorf("setting unsupported sanitizerType %d", t))
 	}
@@ -243,6 +384,8 @@
 		return sanitize.Properties.Sanitize.Address
 	case cc.Hwasan:
 		return sanitize.Properties.Sanitize.Hwaddress
+	case cc.Memtag_heap:
+		return sanitize.Properties.Sanitize.Memtag_heap
 	default:
 		return nil
 	}
@@ -268,6 +411,12 @@
 	case cc.Asan:
 		return true
 	case cc.Hwasan:
+		// TODO(b/180495975): HWASan for static Rust binaries isn't supported yet.
+		if mod.StaticExecutable() {
+			return false
+		}
+		return true
+	case cc.Memtag_heap:
 		return true
 	default:
 		return false
diff --git a/rust/sanitize_test.go b/rust/sanitize_test.go
new file mode 100644
index 0000000..d6a14b2
--- /dev/null
+++ b/rust/sanitize_test.go
@@ -0,0 +1,365 @@
+package rust
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+)
+
+type MemtagNoteType int
+
+const (
+	None MemtagNoteType = iota + 1
+	Sync
+	Async
+)
+
+func (t MemtagNoteType) str() string {
+	switch t {
+	case None:
+		return "none"
+	case Sync:
+		return "sync"
+	case Async:
+		return "async"
+	default:
+		panic("type_note_invalid")
+	}
+}
+
+func checkHasMemtagNote(t *testing.T, m android.TestingModule, expected MemtagNoteType) {
+	t.Helper()
+	note_async := "note_memtag_heap_async"
+	note_sync := "note_memtag_heap_sync"
+
+	found := None
+	implicits := m.Rule("rustc").Implicits
+	for _, lib := range implicits {
+		if strings.Contains(lib.Rel(), note_async) {
+			found = Async
+			break
+		} else if strings.Contains(lib.Rel(), note_sync) {
+			found = Sync
+			break
+		}
+	}
+
+	if found != expected {
+		t.Errorf("Wrong Memtag note in target %q: found %q, expected %q", m.Module().(*Module).Name(), found.str(), expected.str())
+	}
+}
+
+var prepareForTestWithMemtagHeap = android.GroupFixturePreparers(
+	android.FixtureModifyMockFS(func(fs android.MockFS) {
+		templateBp := `
+		rust_test {
+			name: "unset_test_%[1]s",
+			srcs: ["foo.rs"],
+		}
+
+		rust_test {
+			name: "no_memtag_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: false },
+		}
+
+		rust_test {
+			name: "set_memtag_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true },
+		}
+
+		rust_test {
+			name: "set_memtag_set_async_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true, diag: { memtag_heap: false }  },
+		}
+
+		rust_test {
+			name: "set_memtag_set_sync_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true, diag: { memtag_heap: true }  },
+		}
+
+		rust_test {
+			name: "unset_memtag_set_sync_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { diag: { memtag_heap: true }  },
+		}
+
+		rust_binary {
+			name: "unset_binary_%[1]s",
+			srcs: ["foo.rs"],
+		}
+
+		rust_binary {
+			name: "no_memtag_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: false },
+		}
+
+		rust_binary {
+			name: "set_memtag_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true },
+		}
+
+		rust_binary {
+			name: "set_memtag_set_async_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true, diag: { memtag_heap: false }  },
+		}
+
+		rust_binary {
+			name: "set_memtag_set_sync_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true, diag: { memtag_heap: true }  },
+		}
+
+		rust_binary {
+			name: "unset_memtag_set_sync_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { diag: { memtag_heap: true }  },
+		}
+		`
+		subdirNoOverrideBp := fmt.Sprintf(templateBp, "no_override")
+		subdirOverrideDefaultDisableBp := fmt.Sprintf(templateBp, "override_default_disable")
+		subdirSyncBp := fmt.Sprintf(templateBp, "override_default_sync")
+		subdirAsyncBp := fmt.Sprintf(templateBp, "override_default_async")
+
+		fs.Merge(android.MockFS{
+			"subdir_no_override/Android.bp":              []byte(subdirNoOverrideBp),
+			"subdir_override_default_disable/Android.bp": []byte(subdirOverrideDefaultDisableBp),
+			"subdir_sync/Android.bp":                     []byte(subdirSyncBp),
+			"subdir_async/Android.bp":                    []byte(subdirAsyncBp),
+		})
+	}),
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.MemtagHeapExcludePaths = []string{"subdir_override_default_disable"}
+		// "subdir_override_default_disable" is covered by both include and override_default_disable paths. override_default_disable wins.
+		variables.MemtagHeapSyncIncludePaths = []string{"subdir_sync", "subdir_override_default_disable"}
+		variables.MemtagHeapAsyncIncludePaths = []string{"subdir_async", "subdir_override_default_disable"}
+	}),
+)
+
+func TestSanitizeMemtagHeap(t *testing.T) {
+	variant := "android_arm64_armv8-a"
+
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		prepareForTestWithMemtagHeap,
+	).RunTest(t)
+	ctx := result.TestContext
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+}
+
+func TestSanitizeMemtagHeapWithSanitizeDevice(t *testing.T) {
+	variant := "android_arm64_armv8-a"
+
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		prepareForTestWithMemtagHeap,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.SanitizeDevice = []string{"memtag_heap"}
+		}),
+	).RunTest(t)
+	ctx := result.TestContext
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+}
+
+func TestSanitizeMemtagHeapWithSanitizeDeviceDiag(t *testing.T) {
+	variant := "android_arm64_armv8-a"
+
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		prepareForTestWithMemtagHeap,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.SanitizeDevice = []string{"memtag_heap"}
+			variables.SanitizeDeviceDiag = []string{"memtag_heap"}
+		}),
+	).RunTest(t)
+	ctx := result.TestContext
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+}
diff --git a/rust/test.go b/rust/test.go
index 56da509..bb877a9 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -196,3 +196,7 @@
 
 	return deps
 }
+
+func (test *testDecorator) testBinary() bool {
+	return true
+}
diff --git a/rust/testing.go b/rust/testing.go
index dac04d6..9f8ed54 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -91,7 +91,12 @@
 			no_libcrt: true,
 			nocrt: true,
 			system_shared_libs: [],
-			export_include_dirs: ["libprotobuf-cpp-full-includes"],
+		}
+		cc_library {
+			name: "libclang_rt.hwasan_static-aarch64-android",
+			no_libcrt: true,
+			nocrt: true,
+			system_shared_libs: [],
 		}
 		rust_library {
 			name: "libstd",
@@ -186,5 +191,8 @@
 		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
 	})
 	ctx.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
+	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
+	})
 	registerRustSnapshotModules(ctx)
 }
diff --git a/rust/vendor_snapshot_test.go b/rust/vendor_snapshot_test.go
index 60ddb65..bfa6f36 100644
--- a/rust/vendor_snapshot_test.go
+++ b/rust/vendor_snapshot_test.go
@@ -562,6 +562,7 @@
 					"libvendor",
 					"libvndk",
 					"libclang_rt.builtins-aarch64-android",
+					"note_memtag_heap_sync",
 				],
 				shared_libs: [
 					"libvendor_available",
@@ -853,6 +854,20 @@
 		},
 	}
 
+	// Test sanitizers use the snapshot libraries
+	rust_binary {
+		name: "memtag_binary",
+		srcs: ["vendor/bin.rs"],
+		vendor: true,
+		compile_multilib: "64",
+		sanitize: {
+			memtag_heap: true,
+			diag: {
+				memtag_heap: true,
+			}
+		},
+	}
+
 	// old snapshot module which has to be ignored
 	vendor_snapshot_binary {
 		name: "bin",
@@ -880,11 +895,25 @@
 			},
 		},
 	}
+
+	vendor_snapshot_static {
+		name: "note_memtag_heap_sync",
+		vendor: true,
+		target_arch: "arm64",
+		version: "30",
+		arch: {
+			arm64: {
+				src: "note_memtag_heap_sync.a",
+			},
+		},
+	}
+
 `
 
 	mockFS := android.MockFS{
 		"framework/Android.bp":                          []byte(frameworkBp),
 		"framework/bin.rs":                              nil,
+		"note_memtag_heap_sync.a":                       nil,
 		"vendor/Android.bp":                             []byte(vendorProprietaryBp),
 		"vendor/bin":                                    nil,
 		"vendor/bin32":                                  nil,
@@ -993,4 +1022,9 @@
 	if android.InList(binaryVariant, binVariants) {
 		t.Errorf("bin must not have variant %#v, but it does", sharedVariant)
 	}
+
+	memtagStaticLibs := ctx.ModuleForTests("memtag_binary", "android_vendor.30_arm64_armv8-a").Module().(*Module).Properties.AndroidMkStaticLibs
+	if g, w := memtagStaticLibs, []string{"libclang_rt.builtins-aarch64-android.vendor", "note_memtag_heap_sync.vendor"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted memtag_binary AndroidMkStaticLibs %q, got %q", w, g)
+	}
 }
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 635be10..730d756 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -1,5 +1,6 @@
 package {
     default_applicable_licenses: ["Android-Apache-2.0"],
+    default_visibility: ["//build/soong:__subpackages__"],
 }
 
 python_binary_host {
@@ -8,14 +9,6 @@
     srcs: [
         "check_boot_jars/check_boot_jars.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
 }
 
 python_binary_host {
@@ -24,14 +17,6 @@
     srcs: [
         "manifest_fixer.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -45,11 +30,8 @@
         "manifest_fixer.py",
     ],
     version: {
-        py2: {
-            enabled: true,
-        },
         py3: {
-            enabled: false,
+            embedded_launcher: true,
         },
     },
     libs: [
@@ -67,12 +49,14 @@
     ],
     version: {
         py2: {
+            // TODO(b/203436762) Remove when system/apex/apexer/apexer.py is converted
             enabled: true,
         },
         py3: {
-            enabled: false,
+            enabled: true,
         },
     },
+    visibility: ["//system/apex/apexer:__pkg__"],
 }
 
 python_binary_host {
@@ -81,14 +65,6 @@
     srcs: [
         "manifest_check.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -101,14 +77,6 @@
         "manifest_check_test.py",
         "manifest_check.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -123,14 +91,6 @@
     srcs: [
         "jsonmodify.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
 }
 
 python_binary_host {
@@ -139,14 +99,6 @@
     srcs: [
         "test_config_fixer.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -159,14 +111,6 @@
         "test_config_fixer_test.py",
         "test_config_fixer.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -179,14 +123,6 @@
     srcs: [
         "construct_context.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -199,14 +135,6 @@
         "construct_context_test.py",
         "construct_context.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -253,11 +181,7 @@
         "conv_linker_config.py",
     ],
     version: {
-        py2: {
-            enabled: false,
-        },
         py3: {
-            enabled: true,
             embedded_launcher: true,
         },
     },
@@ -272,12 +196,4 @@
     srcs: [
         "get_clang_version.py",
     ],
-    version: {
-        py2: {
-            enabled: false,
-        },
-        py3: {
-            enabled: true,
-        },
-    },
 }
diff --git a/scripts/manifest.py b/scripts/manifest.py
index 04f7405..81f9c61 100755
--- a/scripts/manifest.py
+++ b/scripts/manifest.py
@@ -123,4 +123,4 @@
 def write_xml(f, doc):
   f.write('<?xml version="1.0" encoding="utf-8"?>\n')
   for node in doc.childNodes:
-    f.write(node.toxml(encoding='utf-8') + '\n')
+    f.write(node.toxml() + '\n')
diff --git a/scripts/manifest_check.py b/scripts/manifest_check.py
index 8bed52a..c8d4f76 100755
--- a/scripts/manifest_check.py
+++ b/scripts/manifest_check.py
@@ -335,7 +335,7 @@
         if is_apk:
             aapt = args.aapt if args.aapt is not None else 'aapt'
             manifest = subprocess.check_output(
-                [aapt, 'dump', 'badging', args.input])
+                [aapt, 'dump', 'badging', args.input]).decode('utf-8')
         else:
             manifest = minidom.parse(args.input)
 
@@ -381,7 +381,7 @@
             if is_apk:
                 raise RuntimeError('cannot save APK manifest as XML')
 
-            with open(args.output, 'wb') as f:
+            with open(args.output, 'w') as f:
                 write_xml(f, manifest)
 
     # pylint: disable=broad-except
diff --git a/scripts/manifest_fixer.py b/scripts/manifest_fixer.py
index 55d0fd1..d80a617 100755
--- a/scripts/manifest_fixer.py
+++ b/scripts/manifest_fixer.py
@@ -352,7 +352,7 @@
     if args.extract_native_libs is not None:
       add_extract_native_libs(doc, args.extract_native_libs)
 
-    with open(args.output, 'wb') as f:
+    with open(args.output, 'w') as f:
       write_xml(f, doc)
 
   # pylint: disable=broad-except
diff --git a/scripts/manifest_fixer_test.py b/scripts/manifest_fixer_test.py
index 3a0a25d..f6fcaaf 100755
--- a/scripts/manifest_fixer_test.py
+++ b/scripts/manifest_fixer_test.py
@@ -16,16 +16,16 @@
 #
 """Unit tests for manifest_fixer.py."""
 
-import StringIO
+import io
 import sys
 import unittest
 from xml.dom import minidom
+import xml.etree.ElementTree as ET
 
 import manifest_fixer
 
 sys.dont_write_bytecode = True
 
-
 class CompareVersionGtTest(unittest.TestCase):
   """Unit tests for compare_version_gt function."""
 
@@ -59,7 +59,7 @@
     doc = minidom.parseString(input_manifest)
     manifest_fixer.raise_min_sdk_version(doc, min_sdk_version,
                                          target_sdk_version, library)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -80,13 +80,16 @@
       attrs += ' ' + extra
     return '    <uses-sdk%s/>\n' % (attrs)
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def test_no_uses_sdk(self):
     """Tests inserting a uses-sdk element into a manifest."""
 
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_min(self):
     """Tests inserting a minSdkVersion attribute into a uses-sdk element."""
@@ -95,7 +98,7 @@
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28',
                                                   extra='extra="foo"')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_raise_min(self):
     """Tests inserting a minSdkVersion attribute into a uses-sdk element."""
@@ -103,7 +106,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_raise(self):
     """Tests raising a minSdkVersion attribute."""
@@ -111,7 +114,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_raise_min(self):
     """Tests a minSdkVersion that doesn't need raising."""
@@ -119,7 +122,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='28')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
     output = self.raise_min_sdk_version_test(manifest_input, '27', '27', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_raise_codename(self):
     """Tests raising a minSdkVersion attribute to a codename."""
@@ -127,7 +130,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='28')
     expected = self.manifest_tmpl % self.uses_sdk(min='P', target='P')
     output = self.raise_min_sdk_version_test(manifest_input, 'P', 'P', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_raise_codename(self):
     """Tests a minSdkVersion codename that doesn't need raising."""
@@ -135,7 +138,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='P')
     expected = self.manifest_tmpl % self.uses_sdk(min='P', target='28')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_target(self):
     """Tests an existing targetSdkVersion is preserved."""
@@ -143,7 +146,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='26', target='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_target(self):
     """Tests inserting targetSdkVersion when minSdkVersion exists."""
@@ -151,7 +154,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='29')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_target_no_min(self):
     """"Tests inserting targetSdkVersion when minSdkVersion exists."""
@@ -159,7 +162,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(target='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_target_no_min(self):
     """Tests inserting targetSdkVersion when minSdkVersion does not exist."""
@@ -167,7 +170,7 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='29')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_library_no_target(self):
     """Tests inserting targetSdkVersion when minSdkVersion exists."""
@@ -175,7 +178,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='16')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_library_target_no_min(self):
     """Tests inserting targetSdkVersion when minSdkVersion exists."""
@@ -183,7 +186,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(target='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_library_no_target_no_min(self):
     """Tests inserting targetSdkVersion when minSdkVersion does not exist."""
@@ -191,7 +194,7 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='16')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_extra(self):
     """Tests that extra attributes and elements are maintained."""
@@ -204,12 +207,12 @@
     # pylint: disable=line-too-long
     expected = self.manifest_tmpl % (
         '    <!-- comment -->\n'
-        '    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29" extra="foo"/>\n'
+        '    <uses-sdk android:minSdkVersion="28" extra="foo" android:targetSdkVersion="29"/>\n'
         '    <application/>\n')
 
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
 
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_indent(self):
     """Tests that an inserted element copies the existing indentation."""
@@ -223,17 +226,20 @@
 
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
 
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
 
 class AddLoggingParentTest(unittest.TestCase):
   """Unit tests for add_logging_parent function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def add_logging_parent_test(self, input_manifest, logging_parent=None):
     doc = minidom.parseString(input_manifest)
     if logging_parent:
       manifest_fixer.add_logging_parent(doc, logging_parent)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -257,23 +263,26 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_logging_parent()
     output = self.add_logging_parent_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_logging_parent(self):
     """Tests manifest_fixer with no logging_parent."""
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_logging_parent('FOO')
     output = self.add_logging_parent_test(manifest_input, 'FOO')
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
 
 class AddUsesLibrariesTest(unittest.TestCase):
   """Unit tests for add_uses_libraries function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest, new_uses_libraries):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.add_uses_libraries(doc, new_uses_libraries, True)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -301,7 +310,7 @@
         ('bar', 'false')])
     expected = manifest_input
     output = self.run_test(manifest_input, [])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_not_overwrite(self):
     """new_uses_libraries must not overwrite existing tags."""
@@ -310,7 +319,7 @@
         ('bar', 'false')])
     expected = manifest_input
     output = self.run_test(manifest_input, ['foo', 'bar'])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_add(self):
     """New names are added with 'required:true'."""
@@ -323,7 +332,7 @@
         ('baz', 'true'),
         ('qux', 'true')])
     output = self.run_test(manifest_input, ['bar', 'baz', 'qux'])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_application(self):
     """When there is no <application> tag, the tag is added."""
@@ -336,7 +345,7 @@
         ('foo', 'true'),
         ('bar', 'true')])
     output = self.run_test(manifest_input, ['foo', 'bar'])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_empty_application(self):
     """Even when here is an empty <application/> tag, the libs are added."""
@@ -350,16 +359,19 @@
         ('foo', 'true'),
         ('bar', 'true')])
     output = self.run_test(manifest_input, ['foo', 'bar'])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
 
 class AddUsesNonSdkApiTest(unittest.TestCase):
   """Unit tests for add_uses_libraries function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.add_uses_non_sdk_api(doc)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -377,23 +389,26 @@
     manifest_input = self.manifest_tmpl % self.uses_non_sdk_api(False)
     expected = self.manifest_tmpl % self.uses_non_sdk_api(True)
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_already_set(self):
     """new_uses_libraries must not overwrite existing tags."""
     manifest_input = self.manifest_tmpl % self.uses_non_sdk_api(True)
     expected = manifest_input
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
 
 class UseEmbeddedDexTest(unittest.TestCase):
   """Unit tests for add_use_embedded_dex function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.add_use_embedded_dex(doc)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -410,13 +425,13 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.use_embedded_dex('true')
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_manifest_with_use_embedded_dex(self):
     manifest_input = self.manifest_tmpl % self.use_embedded_dex('true')
     expected = manifest_input
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_manifest_with_not_use_embedded_dex(self):
     manifest_input = self.manifest_tmpl % self.use_embedded_dex('false')
@@ -426,10 +441,13 @@
 class AddExtractNativeLibsTest(unittest.TestCase):
   """Unit tests for add_extract_native_libs function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest, value):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.add_extract_native_libs(doc, value)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -446,19 +464,19 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.extract_native_libs('true')
     output = self.run_test(manifest_input, True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_set_false(self):
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.extract_native_libs('false')
     output = self.run_test(manifest_input, False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_match(self):
     manifest_input = self.manifest_tmpl % self.extract_native_libs('true')
     expected = manifest_input
     output = self.run_test(manifest_input, True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_conflict(self):
     manifest_input = self.manifest_tmpl % self.extract_native_libs('true')
@@ -468,10 +486,13 @@
 class AddNoCodeApplicationTest(unittest.TestCase):
   """Unit tests for set_has_code_to_false function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.set_has_code_to_false(doc)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -485,26 +506,26 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % '    <application android:hasCode="false"/>\n'
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_has_application_no_has_code(self):
     manifest_input = self.manifest_tmpl % '    <application/>\n'
     expected = self.manifest_tmpl % '    <application android:hasCode="false"/>\n'
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_has_application_has_code_false(self):
     """ Do nothing if there's already an application elemeent. """
     manifest_input = self.manifest_tmpl % '    <application android:hasCode="false"/>\n'
     output = self.run_test(manifest_input)
-    self.assertEqual(output, manifest_input)
+    self.assert_xml_equal(output, manifest_input)
 
   def test_has_application_has_code_true(self):
     """ Do nothing if there's already an application elemeent even if its
      hasCode attribute is true. """
     manifest_input = self.manifest_tmpl % '    <application android:hasCode="true"/>\n'
     output = self.run_test(manifest_input)
-    self.assertEqual(output, manifest_input)
+    self.assert_xml_equal(output, manifest_input)
 
 
 if __name__ == '__main__':
diff --git a/scripts/test_config_fixer.py b/scripts/test_config_fixer.py
index 32d5b17..c150e8c 100644
--- a/scripts/test_config_fixer.py
+++ b/scripts/test_config_fixer.py
@@ -86,7 +86,7 @@
     if args.test_file_name:
       overwrite_test_file_name(doc, args.test_file_name)
 
-    with open(args.output, 'wb') as f:
+    with open(args.output, 'w') as f:
       write_xml(f, doc)
 
   # pylint: disable=broad-except
diff --git a/scripts/test_config_fixer_test.py b/scripts/test_config_fixer_test.py
index 1272c6b..d00a593 100644
--- a/scripts/test_config_fixer_test.py
+++ b/scripts/test_config_fixer_test.py
@@ -16,7 +16,7 @@
 #
 """Unit tests for test_config_fixer.py."""
 
-import StringIO
+import io
 import sys
 import unittest
 from xml.dom import minidom
@@ -59,7 +59,7 @@
     manifest = minidom.parseString(self.manifest)
 
     test_config_fixer.overwrite_package_name(doc, manifest, "com.soong.foo")
-    output = StringIO.StringIO()
+    output = io.StringIO()
     test_config_fixer.write_xml(output, doc)
 
     # Only the matching package name in a test node should be updated.
@@ -86,7 +86,7 @@
     doc = minidom.parseString(self.test_config % ("foo.apk"))
 
     test_config_fixer.overwrite_test_file_name(doc, "bar.apk")
-    output = StringIO.StringIO()
+    output = io.StringIO()
     test_config_fixer.write_xml(output, doc)
 
     # Only the matching package name in a test node should be updated.