Switch boot jars package check to using dex jars

The switch to use dex jars instead of class jars means that a boot jar
that is defined by a dex_import module will now be checked against the
package_allowed_list.txt so it is possible that it will detect
previously unreported problems.

Test: m check-boot-jars - for failing and passing cases
Bug: 171479578
Bug: 125517186
Change-Id: Ie614898dade0fb43c9418d7afb9138169db6f097
diff --git a/java/boot_jars.go b/java/boot_jars.go
index 900eb7a..e706547 100644
--- a/java/boot_jars.go
+++ b/java/boot_jars.go
@@ -87,6 +87,7 @@
 
 	rule := android.NewRuleBuilder()
 	checkBootJars := rule.Command().BuiltTool(ctx, "check_boot_jars").
+		Input(ctx.Config().HostToolPath(ctx, "dexdump")).
 		Input(android.PathForSource(ctx, "build/soong/scripts/check_boot_jars/package_allowed_list.txt"))
 
 	// If this is not an unbundled build and missing dependencies are not allowed
@@ -96,14 +97,9 @@
 	// Iterate over the module names on the boot classpath in order
 	for _, name := range android.SortedStringKeys(moduleToApex) {
 		if apexVariant, ok := nameToApexVariant[name]; ok {
-			if dep, ok := apexVariant.(Dependency); ok {
-				// Add the implementation jars for the module to be checked. This uses implementation
-				// and resources jar as that is what the previous make based check uses.
-				for _, jar := range dep.ImplementationAndResourcesJars() {
-					checkBootJars.Input(jar)
-				}
-			} else if _, ok := apexVariant.(*DexImport); ok {
-				// TODO(b/171479578): ignore deximport when doing package check until boot_jars.go can check dex jars.
+			if dep, ok := apexVariant.(interface{ DexJarBuildPath() android.Path }); ok {
+				// Add the dex implementation jar for the module to be checked.
+				checkBootJars.Input(dep.DexJarBuildPath())
 			} else {
 				ctx.Errorf("module %q is of type %q which is not supported as a boot jar", name, ctx.ModuleType(apexVariant))
 			}
diff --git a/scripts/check_boot_jars/check_boot_jars.py b/scripts/check_boot_jars/check_boot_jars.py
index cf4ef27..63fc9a9 100755
--- a/scripts/check_boot_jars/check_boot_jars.py
+++ b/scripts/check_boot_jars/check_boot_jars.py
@@ -3,7 +3,7 @@
 """
 Check boot jars.
 
-Usage: check_boot_jars.py <package_allow_list_file> <jar1> <jar2> ...
+Usage: check_boot_jars.py <dexdump_path> <package_allow_list_file> <jar1> <jar2> ...
 """
 import logging
 import os.path
@@ -38,28 +38,44 @@
     return False
   return True
 
+# Pattern that matches the class descriptor in a "Class descriptor" line output
+# by dexdump and extracts the class name - with / instead of .
+CLASS_DESCRIPTOR_RE = re.compile("'L([^;]+);'")
 
-def CheckJar(allow_list_path, jar):
-  """Check a jar file.
+def CheckDexJar(dexdump_path, allow_list_path, jar):
+  """Check a dex jar file.
   """
-  # Get the list of files inside the jar file.
-  p = subprocess.Popen(args='jar tf %s' % jar,
+  # Get the class descriptor lines in the dexdump output. This filters out lines
+  # that do not contain class descriptors to reduce the size of the data read by
+  # this script.
+  p = subprocess.Popen(args='%s %s | grep "Class descriptor "' % (dexdump_path, jar),
       stdout=subprocess.PIPE, shell=True)
   stdout, _ = p.communicate()
   if p.returncode != 0:
     return False
-  items = stdout.split()
+  # Split the output into lines
+  lines = stdout.split('\n')
   classes = 0
-  for f in items:
-    if f.endswith('.class'):
-      classes += 1
-      package_name = os.path.dirname(f)
-      package_name = package_name.replace('/', '.')
-      if not package_name or not allow_list_re.match(package_name):
-        print >> sys.stderr, ('Error: %s contains class file %s, whose package name %s is empty or'
-                              ' not in the allow list %s of packages allowed on the bootclasspath.'
-                              % (jar, f, package_name, allow_list_path))
-        return False
+  for line in lines:
+    # The last line will be empty
+    if line == '':
+      continue
+    # Try and find the descriptor on the line. Fail immediately if it cannot be found
+    # as the dexdump output has probably changed.
+    found = CLASS_DESCRIPTOR_RE.search(line)
+    if not found:
+      print >> sys.stderr, ('Could not find class descriptor in line `%s`' % line)
+      return False
+    # Extract the class name (using / instead of .) from the class descriptor line
+    f = found.group(1)
+    classes += 1
+    package_name = os.path.dirname(f)
+    package_name = package_name.replace('/', '.')
+    if not package_name or not allow_list_re.match(package_name):
+      print >> sys.stderr, ('Error: %s contains class file %s, whose package name "%s" is empty or'
+                            ' not in the allow list %s of packages allowed on the bootclasspath.'
+                            % (jar, f, package_name, allow_list_path))
+      return False
   if classes == 0:
     print >> sys.stderr, ('Error: %s does not contain any class files.' % jar)
     return False
@@ -67,17 +83,18 @@
 
 
 def main(argv):
-  if len(argv) < 2:
+  if len(argv) < 3:
     print __doc__
     return 1
-  allow_list_path = argv[0]
+  dexdump_path = argv[0]
+  allow_list_path = argv[1]
 
   if not LoadAllowList(allow_list_path):
     return 1
 
   passed = True
-  for jar in argv[1:]:
-    if not CheckJar(allow_list_path, jar):
+  for jar in argv[2:]:
+    if not CheckDexJar(dexdump_path, allow_list_path, jar):
       passed = False
   if not passed:
     return 1