Merge "Add product_variables.uml"
diff --git a/cc/linker.go b/cc/linker.go
index a0f3bc2..678b992 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -48,8 +48,8 @@
 	No_default_compiler_flags *bool
 
 	// list of system libraries that will be dynamically linked to
-	// shared library and executable modules.  If unset, generally defaults to libc
-	// and libm.  Set to [] to prevent linking against libc and libm.
+	// shared library and executable modules.  If unset, generally defaults to libc,
+	// libm, and libdl.  Set to [] to prevent linking against the defaults.
 	System_shared_libs []string
 
 	// allow the module to contain undefined symbols.  By default,
@@ -153,34 +153,32 @@
 		}
 
 		if !ctx.static() {
-			// libdl should always appear after libc in dt_needed list - see below
-			// the only exception is when libc is not in linker.Properties.System_shared_libs
-			// such as for libc module itself
-			if inList("libc", linker.Properties.System_shared_libs) {
-				_, deps.SharedLibs = removeFromList("libdl", deps.SharedLibs)
+			systemSharedLibs := linker.Properties.System_shared_libs
+			if systemSharedLibs == nil {
+				systemSharedLibs = []string{"libc", "libm", "libdl"}
 			}
 
-			if linker.Properties.System_shared_libs != nil {
-				if !inList("libdl", linker.Properties.System_shared_libs) &&
-					inList("libc", linker.Properties.System_shared_libs) {
-					linker.Properties.System_shared_libs = append(linker.Properties.System_shared_libs,
-						"libdl")
+			if inList("libdl", deps.SharedLibs) {
+				// If system_shared_libs has libc but not libdl, make sure shared_libs does not
+				// have libdl to avoid loading libdl before libc.
+				if inList("libc", systemSharedLibs) {
+					if !inList("libdl", systemSharedLibs) {
+						ctx.PropertyErrorf("shared_libs",
+							"libdl must be in system_shared_libs, not shared_libs")
+					}
+					_, deps.SharedLibs = removeFromList("libdl", deps.SharedLibs)
 				}
-				deps.LateSharedLibs = append(deps.LateSharedLibs,
-					linker.Properties.System_shared_libs...)
-			} else if !ctx.sdk() && !ctx.vndk() {
-				deps.LateSharedLibs = append(deps.LateSharedLibs, "libc", "libm", "libdl")
 			}
-		}
 
-		if ctx.sdk() {
-			deps.SharedLibs = append(deps.SharedLibs,
-				"libc",
-				"libm",
-				"libdl",
-			)
-		}
-		if ctx.vndk() {
+			// If libc and libdl are both in system_shared_libs make sure libd comes after libc
+			// to avoid loading libdl before libc.
+			if inList("libdl", systemSharedLibs) && inList("libc", systemSharedLibs) &&
+				indexList("libdl", systemSharedLibs) < indexList("libc", systemSharedLibs) {
+				ctx.PropertyErrorf("system_shared_libs", "libdl must be after libc")
+			}
+
+			deps.LateSharedLibs = append(deps.LateSharedLibs, systemSharedLibs...)
+		} else if ctx.sdk() || ctx.vndk() {
 			deps.LateSharedLibs = append(deps.LateSharedLibs, "libc", "libm", "libdl")
 		}
 	}
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 4ea4d26..0409674 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -264,9 +264,6 @@
 		if Bool(sanitize.Properties.Sanitize.Address) {
 			deps.StaticLibs = append(deps.StaticLibs, asanLibs...)
 		}
-		if Bool(sanitize.Properties.Sanitize.Address) || Bool(sanitize.Properties.Sanitize.Thread) {
-			deps.SharedLibs = append(deps.SharedLibs, "libdl")
-		}
 	}
 
 	return deps
diff --git a/cc/stl.go b/cc/stl.go
index 65d7e40..a123d77 100644
--- a/cc/stl.go
+++ b/cc/stl.go
@@ -107,8 +107,6 @@
 			}
 			if ctx.staticBinary() {
 				deps.StaticLibs = append(deps.StaticLibs, "libm", "libc", "libdl")
-			} else {
-				deps.SharedLibs = append(deps.SharedLibs, "libdl")
 			}
 		}
 	case "":
@@ -118,15 +116,9 @@
 		// The system STL doesn't have a prebuilt (it uses the system's libstdc++), but it does have
 		// its own includes. The includes are handled in CCBase.Flags().
 		deps.SharedLibs = append([]string{"libstdc++"}, deps.SharedLibs...)
-	case "ndk_libc++_shared":
-		deps.SharedLibs = append(deps.SharedLibs, stl.Properties.SelectedStl,
-			"libdl")
-	case "ndk_libc++_static":
-		deps.StaticLibs = append(deps.StaticLibs, stl.Properties.SelectedStl)
-		deps.SharedLibs = append(deps.SharedLibs, "libdl")
-	case "ndk_libstlport_shared":
+	case "ndk_libc++_shared", "ndk_libstlport_shared":
 		deps.SharedLibs = append(deps.SharedLibs, stl.Properties.SelectedStl)
-	case "ndk_libstlport_static", "ndk_libgnustl_static":
+	case "ndk_libc++_static", "ndk_libstlport_static", "ndk_libgnustl_static":
 		deps.StaticLibs = append(deps.StaticLibs, stl.Properties.SelectedStl)
 	default:
 		panic(fmt.Errorf("Unknown stl: %q", stl.Properties.SelectedStl))
diff --git a/finder/finder.go b/finder/finder.go
index 8f9496d..ffda155 100644
--- a/finder/finder.go
+++ b/finder/finder.go
@@ -18,6 +18,7 @@
 	"bufio"
 	"bytes"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -30,7 +31,6 @@
 	"time"
 
 	"android/soong/fs"
-	"errors"
 )
 
 // This file provides a Finder struct that can quickly search for files satisfying
@@ -159,11 +159,17 @@
 	nodes        pathMap
 }
 
+var defaultNumThreads = runtime.NumCPU() * 2
+
 // New creates a new Finder for use
 func New(cacheParams CacheParams, filesystem fs.FileSystem,
 	logger Logger, dbPath string) (f *Finder, err error) {
+	return newImpl(cacheParams, filesystem, logger, dbPath, defaultNumThreads)
+}
 
-	numThreads := runtime.NumCPU() * 2
+// newImpl is like New but accepts more params
+func newImpl(cacheParams CacheParams, filesystem fs.FileSystem,
+	logger Logger, dbPath string, numThreads int) (f *Finder, err error) {
 	numDbLoadingThreads := numThreads
 	numSearchingThreads := numThreads
 
@@ -661,6 +667,9 @@
 		for _, info := range infos {
 			for !strings.HasPrefix(info.P+"/", prefix+"/") {
 				prefix = filepath.Dir(prefix)
+				if prefix == "/" {
+					break
+				}
 			}
 		}
 		// remove common prefix
diff --git a/finder/finder_test.go b/finder/finder_test.go
index 8d1bbd7..1522c68 100644
--- a/finder/finder_test.go
+++ b/finder/finder_test.go
@@ -35,14 +35,18 @@
 }
 
 func newFinder(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams) *Finder {
-	f, err := newFinderAndErr(t, filesystem, cacheParams)
+	return newFinderWithNumThreads(t, filesystem, cacheParams, 2)
+}
+
+func newFinderWithNumThreads(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams, numThreads int) *Finder {
+	f, err := newFinderAndErr(t, filesystem, cacheParams, numThreads)
 	if err != nil {
 		fatal(t, err.Error())
 	}
 	return f
 }
 
-func newFinderAndErr(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams) (*Finder, error) {
+func newFinderAndErr(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams, numThreads int) (*Finder, error) {
 	cachePath := "/finder/finder-db"
 	cacheDir := filepath.Dir(cachePath)
 	filesystem.MkDirs(cacheDir)
@@ -51,7 +55,7 @@
 	}
 
 	logger := log.New(ioutil.Discard, "", 0)
-	f, err := New(cacheParams, filesystem, logger, cachePath)
+	f, err := newImpl(cacheParams, filesystem, logger, cachePath, numThreads)
 	return f, err
 }
 
@@ -64,11 +68,13 @@
 }
 
 func finderAndErrorWithSameParams(t *testing.T, original *Finder) (*Finder, error) {
-	f, err := New(
+	f, err := newImpl(
 		original.cacheMetadata.Config.CacheParams,
 		original.filesystem,
 		original.logger,
-		original.DbPath)
+		original.DbPath,
+		original.numDbLoadingThreads,
+	)
 	return f, err
 }
 
@@ -234,6 +240,21 @@
 	assertSameResponse(t, foundPaths, absoluteMatches)
 }
 
+// testAgainstSeveralThreadcounts runs the given test for each threadcount that we care to test
+func testAgainstSeveralThreadcounts(t *testing.T, tester func(t *testing.T, numThreads int)) {
+	// test singlethreaded, multithreaded, and also using the same number of threads as
+	// will be used on the current system
+	threadCounts := []int{1, 2, defaultNumThreads}
+	for _, numThreads := range threadCounts {
+		testName := fmt.Sprintf("%v threads", numThreads)
+		// store numThreads in a new variable to prevent numThreads from changing in each loop
+		localNumThreads := numThreads
+		t.Run(testName, func(t *testing.T) {
+			tester(t, localNumThreads)
+		})
+	}
+}
+
 // end of utils, start of individual tests
 
 func TestSingleFile(t *testing.T) {
@@ -285,24 +306,30 @@
 }
 
 func TestFilesystemRoot(t *testing.T) {
-	filesystem := newFs()
-	root := "/"
-	createdPath := "/findme.txt"
-	create(t, createdPath, filesystem)
 
-	finder := newFinder(
-		t,
-		filesystem,
-		CacheParams{
-			RootDirs:     []string{root},
-			IncludeFiles: []string{"findme.txt", "skipme.txt"},
-		},
-	)
-	defer finder.Shutdown()
+	testWithNumThreads := func(t *testing.T, numThreads int) {
+		filesystem := newFs()
+		root := "/"
+		createdPath := "/findme.txt"
+		create(t, createdPath, filesystem)
 
-	foundPaths := finder.FindNamedAt(root, "findme.txt")
+		finder := newFinderWithNumThreads(
+			t,
+			filesystem,
+			CacheParams{
+				RootDirs:     []string{root},
+				IncludeFiles: []string{"findme.txt", "skipme.txt"},
+			},
+			numThreads,
+		)
+		defer finder.Shutdown()
 
-	assertSameResponse(t, foundPaths, []string{createdPath})
+		foundPaths := finder.FindNamedAt(root, "findme.txt")
+
+		assertSameResponse(t, foundPaths, []string{createdPath})
+	}
+
+	testAgainstSeveralThreadcounts(t, testWithNumThreads)
 }
 
 func TestNonexistentDir(t *testing.T) {
@@ -316,6 +343,7 @@
 			RootDirs:     []string{"/tmp/IDontExist"},
 			IncludeFiles: []string{"findme.txt", "skipme.txt"},
 		},
+		1,
 	)
 	if err == nil {
 		fatal(t, "Did not fail when given a nonexistent root directory")
@@ -380,6 +408,8 @@
 			"/tmp/include/findme.txt"})
 }
 
+// TestRootDir tests that the value of RootDirs is used
+// tests of the filesystem root are in TestFilesystemRoot
 func TestRootDir(t *testing.T) {
 	filesystem := newFs()
 	create(t, "/tmp/a/findme.txt", filesystem)
@@ -548,48 +578,54 @@
 }
 
 func TestConcurrentFindSameDirectory(t *testing.T) {
-	filesystem := newFs()
 
-	// create a bunch of files and directories
-	paths := []string{}
-	for i := 0; i < 10; i++ {
-		parentDir := fmt.Sprintf("/tmp/%v", i)
-		for j := 0; j < 10; j++ {
-			filePath := filepath.Join(parentDir, fmt.Sprintf("%v/findme.txt", j))
-			paths = append(paths, filePath)
+	testWithNumThreads := func(t *testing.T, numThreads int) {
+		filesystem := newFs()
+
+		// create a bunch of files and directories
+		paths := []string{}
+		for i := 0; i < 10; i++ {
+			parentDir := fmt.Sprintf("/tmp/%v", i)
+			for j := 0; j < 10; j++ {
+				filePath := filepath.Join(parentDir, fmt.Sprintf("%v/findme.txt", j))
+				paths = append(paths, filePath)
+			}
+		}
+		sort.Strings(paths)
+		for _, path := range paths {
+			create(t, path, filesystem)
+		}
+
+		// set up a finder
+		finder := newFinderWithNumThreads(
+			t,
+			filesystem,
+			CacheParams{
+				RootDirs:     []string{"/tmp"},
+				IncludeFiles: []string{"findme.txt"},
+			},
+			numThreads,
+		)
+		defer finder.Shutdown()
+
+		numTests := 20
+		results := make(chan []string, numTests)
+		// make several parallel calls to the finder
+		for i := 0; i < numTests; i++ {
+			go func() {
+				foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
+				results <- foundPaths
+			}()
+		}
+
+		// check that each response was correct
+		for i := 0; i < numTests; i++ {
+			foundPaths := <-results
+			assertSameResponse(t, foundPaths, paths)
 		}
 	}
-	sort.Strings(paths)
-	for _, path := range paths {
-		create(t, path, filesystem)
-	}
 
-	// set up a finder
-	finder := newFinder(
-		t,
-		filesystem,
-		CacheParams{
-			RootDirs:     []string{"/tmp"},
-			IncludeFiles: []string{"findme.txt"},
-		},
-	)
-	defer finder.Shutdown()
-
-	numTests := 20
-	results := make(chan []string, numTests)
-	// make several parallel calls to the finder
-	for i := 0; i < numTests; i++ {
-		go func() {
-			foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
-			results <- foundPaths
-		}()
-	}
-
-	// check that each response was correct
-	for i := 0; i < numTests; i++ {
-		foundPaths := <-results
-		assertSameResponse(t, foundPaths, paths)
-	}
+	testAgainstSeveralThreadcounts(t, testWithNumThreads)
 }
 
 func TestConcurrentFindDifferentDirectories(t *testing.T) {
diff --git a/ui/build/kati.go b/ui/build/kati.go
index 48c38d4..cc02c76 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -59,11 +59,13 @@
 }
 
 func runKati(ctx Context, config Config) {
+	genKatiSuffix(ctx, config)
+
+	runKatiCleanSpec(ctx, config)
+
 	ctx.BeginTrace("kati")
 	defer ctx.EndTrace()
 
-	genKatiSuffix(ctx, config)
-
 	executable := config.PrebuiltBuildTool("ckati")
 	args := []string{
 		"--ninja",
@@ -101,9 +103,6 @@
 	}
 	cmd.Stderr = cmd.Stdout
 
-	// Kati leaks memory, so ensure leak detection is turned off
-	cmd.Environment.Set("ASAN_OPTIONS", "detect_leaks=0")
-
 	cmd.StartOrFatal()
 	katiRewriteOutput(ctx, pipe)
 	cmd.WaitOrFatal()
@@ -162,3 +161,33 @@
 		fmt.Fprintln(ctx.Stdout())
 	}
 }
+
+func runKatiCleanSpec(ctx Context, config Config) {
+	ctx.BeginTrace("kati cleanspec")
+	defer ctx.EndTrace()
+
+	executable := config.PrebuiltBuildTool("ckati")
+	args := []string{
+		"--ninja",
+		"--ninja_dir=" + config.OutDir(),
+		"--ninja_suffix=" + config.KatiSuffix() + "-cleanspec",
+		"--regen",
+		"--detect_android_echo",
+		"--color_warnings",
+		"--gen_all_targets",
+		"--werror_find_emulator",
+		"--use_find_emulator",
+		"-f", "build/make/core/cleanbuild.mk",
+		"BUILDING_WITH_NINJA=true",
+		"SOONG_MAKEVARS_MK=" + config.SoongMakeVarsMk(),
+	}
+
+	cmd := Command(ctx, config, "ckati", executable, args...)
+	cmd.Sandbox = katiCleanSpecSandbox
+	cmd.Stdout = ctx.Stdout()
+	cmd.Stderr = ctx.Stderr()
+
+	// Kati leaks memory, so ensure leak detection is turned off
+	cmd.Environment.Set("ASAN_OPTIONS", "detect_leaks=0")
+	cmd.RunOrFatal()
+}
diff --git a/ui/build/sandbox_darwin.go b/ui/build/sandbox_darwin.go
index 54c145c..60407d4 100644
--- a/ui/build/sandbox_darwin.go
+++ b/ui/build/sandbox_darwin.go
@@ -22,11 +22,12 @@
 type Sandbox string
 
 const (
-	noSandbox     = ""
-	globalSandbox = "build/soong/ui/build/sandbox/darwin/global.sb"
-	makeSandbox   = globalSandbox
-	soongSandbox  = globalSandbox
-	katiSandbox   = globalSandbox
+	noSandbox            = ""
+	globalSandbox        = "build/soong/ui/build/sandbox/darwin/global.sb"
+	makeSandbox          = globalSandbox
+	soongSandbox         = globalSandbox
+	katiSandbox          = globalSandbox
+	katiCleanSpecSandbox = globalSandbox
 )
 
 var sandboxExecPath string
diff --git a/ui/build/sandbox_linux.go b/ui/build/sandbox_linux.go
index fb48b9c..6615d37 100644
--- a/ui/build/sandbox_linux.go
+++ b/ui/build/sandbox_linux.go
@@ -17,11 +17,12 @@
 type Sandbox bool
 
 const (
-	noSandbox     = false
-	globalSandbox = false
-	makeSandbox   = false
-	soongSandbox  = false
-	katiSandbox   = false
+	noSandbox            = false
+	globalSandbox        = false
+	makeSandbox          = false
+	soongSandbox         = false
+	katiSandbox          = false
+	katiCleanSpecSandbox = false
 )
 
 func (c *Cmd) sandboxSupported() bool {