Add build-time checks of AIDL API hashes

All frozen AIDL APIs are required to have a hash, and these are checked
for accuracy at build time. This ensures that these interfaces aren't
changed, since we are relying on their stability over time.

Bug: 150154330
Test: atest CtsNdkBinderTestCases, freeze a new interface and verify
Change-Id: Iff9ce5b8022ebd587ee451eb45d070e500da430a
diff --git a/aidl.cpp b/aidl.cpp
index 083d520..2c9bd2e 100644
--- a/aidl.cpp
+++ b/aidl.cpp
@@ -840,7 +840,7 @@
         unique_ptr<CodeWriter> writer =
             io_delegate.GetCodeWriter(GetApiDumpPathFor(*type, options));
         if (!type->GetPackage().empty()) {
-          (*writer) << "package " << type->GetPackage() << ";\n";
+          (*writer) << kPreamble << "package " << type->GetPackage() << ";\n";
         }
         type->Dump(writer.get());
       }
diff --git a/aidl.h b/aidl.h
index 94eaf54..298a6cc 100644
--- a/aidl.h
+++ b/aidl.h
@@ -50,6 +50,26 @@
 bool dump_api(const Options& options, const IoDelegate& io_delegate);
 bool dump_mappings(const Options& options, const IoDelegate& io_delegate);
 
+const char kPreamble[] =
+    R"(///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+)";
+
 const string kGetInterfaceVersion("getInterfaceVersion");
 const string kGetInterfaceHash("getInterfaceHash");
 
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index 9e22453..21ba79c 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -928,7 +928,7 @@
   ASSERT_TRUE(result);
   string actual;
   EXPECT_TRUE(io_delegate_.GetWrittenContents("dump/foo/bar/IFoo.aidl", &actual));
-  EXPECT_EQ(actual, R"(package foo.bar;
+  EXPECT_EQ(actual, string(kPreamble).append(R"(package foo.bar;
 /* @hide */
 interface IFoo {
   /* @hide */
@@ -940,10 +940,10 @@
   const int A = 1;
   const String STR = "Hello";
 }
-)");
+)"));
 
   EXPECT_TRUE(io_delegate_.GetWrittenContents("dump/foo/bar/Data.aidl", &actual));
-  EXPECT_EQ(actual, R"(package foo.bar;
+  EXPECT_EQ(actual, string(kPreamble).append(R"(package foo.bar;
 /* @hide */
 parcelable Data {
   /* @hide */
@@ -956,7 +956,7 @@
   /* @hide */
   @nullable String[] c;
 }
-)");
+)"));
 }
 
 TEST_F(AidlTest, ApiDumpWithManualIds) {
@@ -975,13 +975,13 @@
   ASSERT_TRUE(result);
   string actual;
   EXPECT_TRUE(io_delegate_.GetWrittenContents("dump/foo/bar/IFoo.aidl", &actual));
-  EXPECT_EQ(actual, R"(package foo.bar;
+  EXPECT_EQ(actual, string(kPreamble).append(R"(package foo.bar;
 interface IFoo {
   int foo() = 1;
   int bar() = 2;
   int baz() = 10;
 }
-)");
+)"));
 }
 
 TEST_F(AidlTest, ApiDumpWithManualIdsOnlyOnSomeMethods) {
diff --git a/build/aidl_api/test-piece-1/1/.hash b/build/aidl_api/test-piece-1/1/.hash
index 3185626..79228df 100644
--- a/build/aidl_api/test-piece-1/1/.hash
+++ b/build/aidl_api/test-piece-1/1/.hash
@@ -1 +1 @@
-db5ac8f6ff15c327e0da41396ba7af9ec21fc48c  -
+13e24b2fac6a979971819fba2ab0d6d7c4182122
diff --git a/build/aidl_api/test-piece-1/2/.hash b/build/aidl_api/test-piece-1/2/.hash
index 5bcc485..2ad0e01 100644
--- a/build/aidl_api/test-piece-1/2/.hash
+++ b/build/aidl_api/test-piece-1/2/.hash
@@ -1 +1 @@
-dc2a9292847e43b4360bb183f7491f0e9895eaa9  -
+dc2a9292847e43b4360bb183f7491f0e9895eaa9
diff --git a/build/aidl_api/test-piece-1/3/.hash b/build/aidl_api/test-piece-1/3/.hash
index 6a6f341..58a9b34 100644
--- a/build/aidl_api/test-piece-1/3/.hash
+++ b/build/aidl_api/test-piece-1/3/.hash
@@ -1 +1 @@
-54f935920ab0934c242145cf00f9852ae3f5a63e  -
+54f935920ab0934c242145cf00f9852ae3f5a63e
diff --git a/build/aidl_api/test-piece-2/1/.hash b/build/aidl_api/test-piece-2/1/.hash
index b1723ae..705fc31 100644
--- a/build/aidl_api/test-piece-2/1/.hash
+++ b/build/aidl_api/test-piece-2/1/.hash
@@ -1 +1 @@
-cf529419c2c68a8d00fa58b29de46ebdbcabcc10  -
+fcd36db451cdbeeb049833fd7f499a987acf3930
diff --git a/build/aidl_api/test-piece-3/1/.hash b/build/aidl_api/test-piece-3/1/.hash
index d14b650..f85682c 100644
--- a/build/aidl_api/test-piece-3/1/.hash
+++ b/build/aidl_api/test-piece-3/1/.hash
@@ -1 +1 @@
-90488eb4c5ea0ae9de6f414383fdf007780b58b9  -
+f630cb120fa36f9f2507d730513a03b0cd4fbc0e
diff --git a/build/aidl_api/test-piece-4/1/.hash b/build/aidl_api/test-piece-4/1/.hash
index f9e9ffc..2982016 100644
--- a/build/aidl_api/test-piece-4/1/.hash
+++ b/build/aidl_api/test-piece-4/1/.hash
@@ -1 +1 @@
-b23b9616db460f76a75d265c329e097c508ca741  -
+29d4015ee2a4171396aecd925156aa2e47a42013
diff --git a/build/aidl_api/test-root-package/1/.hash b/build/aidl_api/test-root-package/1/.hash
index 5795162..8cf35a7 100644
--- a/build/aidl_api/test-root-package/1/.hash
+++ b/build/aidl_api/test-root-package/1/.hash
@@ -1 +1 @@
-3a4db987a854936c551a3fb3f889f8bf561ab9ab  -
+1f59d8099e05dcc6ac47a9a12a1f2660dd60cfef
diff --git a/build/aidl_api/test-root-package/2/.hash b/build/aidl_api/test-root-package/2/.hash
index 9f6e7dc..5f9d7d9 100644
--- a/build/aidl_api/test-root-package/2/.hash
+++ b/build/aidl_api/test-root-package/2/.hash
@@ -1 +1 @@
-7025642541e73177f105c6d5b62a980fa5b4ace3  -
+7025642541e73177f105c6d5b62a980fa5b4ace3
diff --git a/build/aidl_interface.go b/build/aidl_interface.go
index f47cac5..cf7055e 100644
--- a/build/aidl_interface.go
+++ b/build/aidl_interface.go
@@ -77,7 +77,7 @@
 	aidlDumpApiRule = pctx.StaticRule("aidlDumpApiRule", blueprint.RuleParams{
 		Command: `rm -rf "${outDir}" && mkdir -p "${outDir}" && ` +
 			`${aidlCmd} --dumpapi --structured ${imports} ${optionalFlags} --out ${outDir} ${in} && ` +
-			`(cd ${outDir} && find ./ -name "*.aidl" -print0 | LC_ALL=C sort -z | xargs -0 sha1sum && echo ${latestVersion}) | sha1sum > ${hashFile} `,
+			`(cd ${outDir} && find ./ -name "*.aidl" -print0 | LC_ALL=C sort -z | xargs -0 sha1sum && echo ${latestVersion}) | sha1sum | cut -d " " -f 1 > ${hashFile} `,
 		CommandDeps: []string{"${aidlCmd}"},
 	}, "optionalFlags", "imports", "outDir", "hashFile", "latestVersion")
 
@@ -108,10 +108,16 @@
 	}, "optionalFlags", "old", "new", "messageFile")
 
 	aidlDiffApiRule = pctx.StaticRule("aidlDiffApiRule", blueprint.RuleParams{
-		Command: `(diff -N --line-format="" ${oldHashFile} ${newHashFile} && diff -r -B -I '//.*' ${old} ${new} && touch ${out}) || ` +
-			`(cat ${messageFile} && exit 1)`,
+		Command: `if diff -r -B -I '//.*' '${old}'/* '${new}'/*; then touch '${out}'; else ` +
+			`cat '${messageFile}' && exit 1; fi`,
 		Description: "Check equality of ${new} and ${old}",
-	}, "old", "new", "messageFile", "oldHashFile", "newHashFile")
+	}, "old", "new", "messageFile")
+
+	aidlVerifyHashRule = pctx.StaticRule("aidlVerifyHashRule", blueprint.RuleParams{
+		Command: `if [ $$(cd '${apiDir}' && { find ./ -name "*.aidl" -print0 | LC_ALL=C sort -z | xargs -0 sha1sum && echo ${version}; } | sha1sum | cut -d " " -f 1) = $$(read -r <'${hashFile}' hash extra; printf %s $$hash) ]; then ` +
+			`touch ${out}; else cat '${messageFile}' && exit 1; fi`,
+		Description: "Verify ${apiDir} files have not been modified",
+	}, "apiDir", "version", "messageFile", "hashFile")
 
 	joinJsonObjectsToArrayRule = pctx.StaticRule("joinJsonObjectsToArrayRule", blueprint.RuleParams{
 		Rspfile:        "$out.rsp",
@@ -237,6 +243,9 @@
 			for _, path := range api.checkApiTimestamps {
 				g.implicitInputs = append(g.implicitInputs, path)
 			}
+			for _, path := range api.checkHashTimestamps {
+				g.implicitInputs = append(g.implicitInputs, path)
+			}
 		}
 	})
 	g.importFlags = strings.Join(wrap("-I", importPaths, ""), " ")
@@ -424,6 +433,9 @@
 	// for triggering updating current API
 	updateApiTimestamp android.WritablePath
 
+	// for triggering check that files have not been modified
+	checkHashTimestamps android.WritablePaths
+
 	// for triggering freezing API as the new version
 	freezeApiTimestamp android.WritablePath
 }
@@ -512,24 +524,23 @@
 	timestampFile := android.PathForModuleOut(ctx, "updateapi_"+version+".timestamp")
 
 	modulePath := android.PathForModuleSrc(ctx).String()
-	apiPreamble := android.PathForSource(ctx, "system/tools/aidl/build/api_preamble.txt")
 
 	targetDir := filepath.Join(modulePath, m.apiDir(), version)
 	rb := android.NewRuleBuilder()
 	// Wipe the target directory and then copy the API dump into the directory
 	rb.Command().Text("mkdir -p " + targetDir)
 	rb.Command().Text("rm -rf " + targetDir + "/*")
-	rb.Command().Text("cp -rf " + dump.dir.String() + "/. " + targetDir).Implicits(dump.files)
 	if version != currentVersion {
+		rb.Command().Text("cp -rf " + dump.dir.String() + "/. " + targetDir).Implicits(dump.files)
 		// If this is making a new frozen (i.e. non-current) version of the interface,
-		// Place a preamble to prevent people from accidentally modifying the dumps.
-		// And also modify Android.bp file to add the new version to the 'versions' property.
-		rb.Command().Text("find " + targetDir + " -type f -name \"*.aidl\"").
-			Text("| xargs -n 1 bash -c 'cat ").Input(apiPreamble).Text(" $0 > $0.temp && mv $0.temp $0'")
+		// modify Android.bp file to add the new version to the 'versions' property.
 		rb.Command().BuiltTool(ctx, "bpmodify").
 			Text("-w -m " + m.properties.BaseName).
 			Text("-parameter versions -a " + version).
 			Text(android.PathForModuleSrc(ctx, "Android.bp").String())
+	} else {
+		// In this case (unfrozen interface), don't copy .hash
+		rb.Command().Text("cp -rf " + dump.dir.String() + "/* " + targetDir).Implicits(dump.files)
 	}
 	rb.Command().Text("touch").Output(timestampFile)
 
@@ -588,10 +599,6 @@
 	implicits = append(implicits, oldDump.files...)
 	implicits = append(implicits, newDump.files...)
 	implicits = append(implicits, formattedMessageFile)
-	if oldDump.hashFile.Valid() {
-		implicits = append(implicits, oldDump.hashFile.Path())
-	}
-	implicits = append(implicits, newDump.hashFile.Path())
 	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
 		Rule:      aidlDiffApiRule,
 		Implicits: implicits,
@@ -600,8 +607,36 @@
 			"old":         oldDump.dir.String(),
 			"new":         newDump.dir.String(),
 			"messageFile": formattedMessageFile.String(),
-			"oldHashFile": oldDump.hashFile.String(),
-			"newHashFile": newDump.hashFile.String(),
+		},
+	})
+	return timestampFile
+}
+
+func (m *aidlApi) checkIntegrity(ctx android.ModuleContext, dump apiDump) android.WritablePath {
+	version := dump.dir.Base()
+	timestampFile := android.PathForModuleOut(ctx, "checkhash_"+version+".timestamp")
+	messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_integrity.txt")
+
+	i, _ := strconv.Atoi(version)
+	if i == 1 {
+		version = "latest-version"
+	} else {
+		version = strconv.Itoa(i - 1)
+	}
+
+	var implicits android.Paths
+	implicits = append(implicits, dump.files...)
+	implicits = append(implicits, dump.hashFile.Path())
+	implicits = append(implicits, messageFile)
+	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
+		Rule:      aidlVerifyHashRule,
+		Implicits: implicits,
+		Output:    timestampFile,
+		Args: map[string]string{
+			"apiDir":      dump.dir.String(),
+			"version":     version,
+			"hashFile":    dump.hashFile.Path().String(),
+			"messageFile": messageFile.String(),
 		},
 	})
 	return timestampFile
@@ -657,6 +692,11 @@
 		dumps = append(dumps, currentApiDump)
 	}
 	for i, _ := range dumps {
+		if dumps[i].hashFile.Valid() {
+			checkHashTimestamp := m.checkIntegrity(ctx, dumps[i])
+			m.checkHashTimestamps = append(m.checkHashTimestamps, checkHashTimestamp)
+		}
+
 		if i == 0 {
 			continue
 		}
@@ -1297,9 +1337,12 @@
 		// avoid needing to filter out duplicates.
 		info.HashFiles = android.FirstUniqueStrings(info.HashFiles)
 
+		implicits := android.PathsForSource(ctx, info.HashFiles)
+
 		ctx.Build(pctx, android.BuildParams{
-			Rule:   aidlMetadataRule,
-			Output: metadataPath,
+			Rule:      aidlMetadataRule,
+			Implicits: implicits,
+			Output:    metadataPath,
 			Args: map[string]string{
 				"name":      name,
 				"stability": info.Stability,
diff --git a/build/aidl_test.go b/build/aidl_test.go
index 547df47..62761f8 100644
--- a/build/aidl_test.go
+++ b/build/aidl_test.go
@@ -96,13 +96,13 @@
 	fs := map[string][]byte{
 		"a.java":              nil,
 		"AndroidManifest.xml": nil,
-		"build/make/target/product/security/testkey":              nil,
-		"framework/aidl/a.aidl":                                   nil,
-		"IFoo.aidl":                                               nil,
-		"libbinder_ndk.map.txt":                                   nil,
-		"system/tools/aidl/build/api_preamble.txt":                nil,
+		"build/make/target/product/security/testkey": nil,
+		"framework/aidl/a.aidl":                      nil,
+		"IFoo.aidl":                                  nil,
+		"libbinder_ndk.map.txt":                      nil,
 		"system/tools/aidl/build/message_check_compatibility.txt": nil,
 		"system/tools/aidl/build/message_check_equality.txt":      nil,
+		"system/tools/aidl/build/message_check_integrity.txt":     nil,
 	}
 
 	cc.GatherRequiredFilesForTest(fs)
@@ -230,6 +230,7 @@
 		}
 	`, withFiles(map[string][]byte{
 		"aidl_api/foo/1/foo.1.aidl": nil,
+		"aidl_api/foo/1/.hash":      nil,
 	}))
 
 	// For alias for the latest frozen version (=1)
@@ -291,7 +292,9 @@
 		}
 	`, withFiles(map[string][]byte{
 		"aidl_api/foo/1/foo.1.aidl": nil,
+		"aidl_api/foo/1/.hash":      nil,
 		"aidl_api/foo/2/foo.2.aidl": nil,
+		"aidl_api/foo/2/.hash":      nil,
 	}))
 
 	// alias for the latest frozen version (=2)
diff --git a/build/api_preamble.txt b/build/api_preamble.txt
deleted file mode 100644
index 3a2c34d..0000000
--- a/build/api_preamble.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
diff --git a/build/message_check_integrity.txt b/build/message_check_integrity.txt
new file mode 100644
index 0000000..57aaa90
--- /dev/null
+++ b/build/message_check_integrity.txt
@@ -0,0 +1,7 @@
+###############################################################################
+# ERROR: Modification detected of stable AIDL API file                        #
+###############################################################################
+Above AIDL file(s) has changed, resulting in a different hash. Hash values may
+be checked at runtime to verify interface stability. If a device is shipped
+with this change by ignoring this message, it has a high risk of breaking later
+when a module using the interface is updated, e.g., Mainline modules.
diff --git a/metadata/test.cpp b/metadata/test.cpp
index 9daf1b1..67d08b2 100644
--- a/metadata/test.cpp
+++ b/metadata/test.cpp
@@ -37,7 +37,7 @@
   EXPECT_THAT(info->types,
               ElementsAre("some_package.IFoo", "some_package.Thing",
                           "some_package.sub_package.IFoo", "some_package.sub_package.SubThing"));
-  EXPECT_THAT(info->hashes, ElementsAre("db5ac8f6ff15c327e0da41396ba7af9ec21fc48c",
+  EXPECT_THAT(info->hashes, ElementsAre("13e24b2fac6a979971819fba2ab0d6d7c4182122",
                                         "dc2a9292847e43b4360bb183f7491f0e9895eaa9",
                                         "54f935920ab0934c242145cf00f9852ae3f5a63e"));
 }