Add option to limit what classes can have annotations
Bug: 292141694
Test: run-all-tests.sh
Test: atest --no-bazel-mode CtsUtilTestCasesRavenwood
Change-Id: I492206e4b14e02fb3d563c3bc5cd2f0b4907317a
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index 9703626..e02492d 100644
--- a/tools/hoststubgen/TEST_MAPPING
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -1,6 +1,8 @@
{
// TODO: Change to presubmit.
"postsubmit": [
- { "name": "tiny-framework-dump-test" }
+ { "name": "tiny-framework-dump-test" },
+ { "name": "hoststubgentest" },
+ { "name": "hoststubgen-invoke-test" }
]
}
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 182940e..fd4ec8b 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -99,6 +99,18 @@
visibility: ["//visibility:public"],
}
+java_test_host {
+ name: "hoststubgentest",
+ // main_class: "com.android.hoststubgen.Main",
+ srcs: ["test/**/*.kt"],
+ static_libs: [
+ "hoststubgen",
+ "truth",
+ ],
+ test_suites: ["general-tests"],
+ visibility: ["//visibility:private"],
+}
+
// File that contains the standard command line argumetns to hoststubgen.
// This is only for the prototype. The productionized version is "ravenwood-standard-options".
filegroup {
diff --git a/tools/hoststubgen/hoststubgen/invoketest/Android.bp b/tools/hoststubgen/hoststubgen/invoketest/Android.bp
new file mode 100644
index 0000000..7e90e42
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/invoketest/Android.bp
@@ -0,0 +1,20 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+sh_test_host {
+ name: "hoststubgen-invoke-test",
+ src: "hoststubgen-invoke-test.sh",
+ test_suites: ["general-tests"],
+
+ // Note: java_data: ["hoststubgen"] will only install the jar file, but not the command wrapper.
+ java_data: [
+ "hoststubgen",
+ "hoststubgen-test-tiny-framework",
+ ],
+}
diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
new file mode 100755
index 0000000..34b2145
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
@@ -0,0 +1,163 @@
+#!/bin/bash
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e # Exit when any command files
+
+# This script runs HostStubGen directly with various arguments and make sure
+# the tool behaves in the expected way.
+
+
+echo "# Listing files in the test environment"
+ls -lR
+
+echo "# Dumping the environment variables"
+env
+
+# Set up the constants and variables
+
+export TEMP=$TEST_TMPDIR
+
+JAR=hoststubgen-test-tiny-framework.jar
+STUB=$TEMP/stub.jar
+IMPL=$TEMP/impl.jar
+
+ANNOTATION_FILTER=$TEMP/annotation-filter.txt
+
+HOSTSTUBGEN_OUT=$TEMP/output.txt
+
+# Because of `set -e`, we can't return non-zero from functions, so we store
+# HostStubGen result in it.
+HOSTSTUBGEN_RC=0
+
+# Define the functions to
+
+
+# Note, because the build rule will only install hoststubgen.jar, but not the wrapper script,
+# we need to execute it manually with the java command.
+hoststubgen() {
+ java -jar ./hoststubgen.jar "$@"
+}
+
+run_hoststubgen() {
+ local test_name="$1"
+ local annotation_filter="$2"
+
+ echo "# Test: $test_name"
+
+ rm -f $HOSTSTUBGEN_OUT
+
+ local filter_arg=""
+
+ if [[ "$annotation_filter" != "" ]] ; then
+ echo "$annotation_filter" > $ANNOTATION_FILTER
+ filter_arg="--annotation-allowed-classes-file $ANNOTATION_FILTER"
+ echo "=== filter ==="
+ cat $ANNOTATION_FILTER
+ fi
+
+ hoststubgen \
+ --debug \
+ --in-jar $JAR \
+ --out-stub-jar $STUB \
+ --out-impl-jar $IMPL \
+ $filter_arg \
+ |& tee $HOSTSTUBGEN_OUT
+ HOSTSTUBGEN_RC=${PIPESTATUS[0]}
+ echo "HostStubGen exited with $HOSTSTUBGEN_RC"
+ return 0
+}
+
+run_hoststubgen_for_success() {
+ run_hoststubgen "$@"
+
+ if (( $HOSTSTUBGEN_RC != 0 )) ; then
+ echo "HostStubGen expected to finish successfully, but failed with $rc"
+ return 1
+ fi
+}
+
+run_hoststubgen_for_failure() {
+ local test_name="$1"
+ local expected_error_message="$2"
+ shift 2
+
+ run_hoststubgen "$test_name" "$@"
+
+ if (( $HOSTSTUBGEN_RC == 0 )) ; then
+ echo "HostStubGen expected to fail, but it didn't fail"
+ return 1
+ fi
+
+ # The output should contain the expected message. (note we se fgrep here.)
+ grep -Fq "$expected_error_message" $HOSTSTUBGEN_OUT
+}
+
+# Start the tests...
+
+# Pass "" as a filter to _not_ add `--annotation-allowed-classes-file`.
+run_hoststubgen_for_success "No annotation filter" ""
+
+# Now, we use " ", so we do add `--annotation-allowed-classes-file`.
+run_hoststubgen_for_failure "No classes are allowed to have annotations" \
+ "not allowed to have Ravenwood annotations" \
+ " "
+
+run_hoststubgen_for_success "All classes allowed (wildcard)" \
+ "
+* # Allow all classes
+"
+
+run_hoststubgen_for_failure "All classes disallowed (wildcard)" \
+ "not allowed to have Ravenwood annotations" \
+ "
+!* # Disallow all classes
+"
+
+run_hoststubgen_for_failure "Some classes not allowed (1)" \
+ "not allowed to have Ravenwood annotations" \
+ "
+android.hosttest.*
+com.android.hoststubgen.*
+com.supported.*
+"
+
+run_hoststubgen_for_failure "Some classes not allowed (2)" \
+ "not allowed to have Ravenwood annotations" \
+ "
+android.hosttest.*
+com.android.hoststubgen.*
+com.unsupported.*
+"
+
+run_hoststubgen_for_success "All classes allowed (package wildcard)" \
+ "
+android.hosttest.*
+com.android.hoststubgen.*
+com.supported.*
+com.unsupported.*
+"
+
+
+run_hoststubgen_for_failure "One specific class disallowed" \
+ "TinyFrameworkClassAnnotations is not allowed to have Ravenwood annotations" \
+ "
+!com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+* # All other classes allowed
+"
+
+
+
+echo "All tests passed"
+exit 0
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 872d568..f32dc72 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -27,6 +27,7 @@
import com.android.hoststubgen.filters.StubIntersectingFilter
import com.android.hoststubgen.filters.createFilterFromTextPolicyFile
import com.android.hoststubgen.filters.printAsTextPolicy
+import com.android.hoststubgen.utils.ClassFilter
import com.android.hoststubgen.visitors.BaseAdapter
import com.android.hoststubgen.visitors.PackageRedirectRemapper
import org.objectweb.asm.ClassReader
@@ -167,6 +168,14 @@
filter
)
+ val annotationAllowedClassesFilter = options.annotationAllowedClassesFile.let { filename ->
+ if (filename == null) {
+ ClassFilter.newNullFilter(true) // Allow all classes
+ } else {
+ ClassFilter.loadFromFile(filename, false)
+ }
+ }
+
// Next, Java annotation based filter.
filter = AnnotationBasedFilter(
errors,
@@ -181,7 +190,8 @@
options.nativeSubstituteAnnotations,
options.classLoadHookAnnotations,
options.stubStaticInitializerAnnotations,
- filter
+ annotationAllowedClassesFilter,
+ filter,
)
// Next, "text based" filter, which allows to override polices without touching
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index d74612d..aab02b8 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -51,6 +51,8 @@
var packageRedirects: MutableList<Pair<String, String>> = mutableListOf(),
+ var annotationAllowedClassesFile: String? = null,
+
var defaultClassLoadHook: String? = null,
var defaultMethodCallHook: String? = null,
@@ -171,6 +173,9 @@
"--package-redirect" ->
ret.packageRedirects += parsePackageRedirect(ai.nextArgRequired(arg))
+ "--annotation-allowed-classes-file" ->
+ ret.annotationAllowedClassesFile = ai.nextArgRequired(arg)
+
"--default-class-load-hook" ->
ret.defaultClassLoadHook = ai.nextArgRequired(arg)
@@ -314,6 +319,7 @@
nativeSubstituteAnnotations=$nativeSubstituteAnnotations,
classLoadHookAnnotations=$classLoadHookAnnotations,
packageRedirects=$packageRedirects,
+ $annotationAllowedClassesFile=$annotationAllowedClassesFile,
defaultClassLoadHook=$defaultClassLoadHook,
defaultMethodCallHook=$defaultMethodCallHook,
intersectStubJars=$intersectStubJars,
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
index f75062b..937e56c 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -58,4 +58,29 @@
return listOf(b)
}
return a + b
-}
\ No newline at end of file
+}
+
+
+/**
+ * Exception for a parse error in a file
+ */
+class ParseException : Exception, UserErrorException {
+ val hasSourceInfo: Boolean
+
+ constructor(message: String) : super(message) {
+ hasSourceInfo = false
+ }
+
+ constructor(message: String, file: String, line: Int) :
+ super("$message in file $file line $line") {
+ hasSourceInfo = true
+ }
+
+ fun withSourceInfo(filename: String, lineNo: Int): ParseException {
+ if (hasSourceInfo) {
+ return this // Already has source information.
+ } else {
+ return ParseException(this.message ?: "", filename, lineNo)
+ }
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
index 9f3ec4d..9bb5381e 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
@@ -25,9 +25,11 @@
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.findAnnotationValueAsString
import com.android.hoststubgen.asm.findAnyAnnotation
+import com.android.hoststubgen.asm.toHumanReadableClassName
import com.android.hoststubgen.asm.toHumanReadableMethodName
import com.android.hoststubgen.asm.toJvmClassName
import com.android.hoststubgen.log
+import com.android.hoststubgen.utils.ClassFilter
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
@@ -51,6 +53,7 @@
nativeSubstituteAnnotations_: Set<String>,
classLoadHookAnnotations_: Set<String>,
stubStaticInitializerAnnotations_: Set<String>,
+ private val annotationAllowedClassesFilter: ClassFilter,
fallback: OutputFilter,
) : DelegatingFilter(fallback) {
private var stubAnnotations = convertToInternalNames(stubAnnotations_)
@@ -62,7 +65,8 @@
private var substituteAnnotations = convertToInternalNames(substituteAnnotations_)
private var nativeSubstituteAnnotations = convertToInternalNames(nativeSubstituteAnnotations_)
private var classLoadHookAnnotations = convertToInternalNames(classLoadHookAnnotations_)
- private var stubStaticInitializerAnnotations = convertToInternalNames(stubStaticInitializerAnnotations_)
+ private var stubStaticInitializerAnnotations =
+ convertToInternalNames(stubStaticInitializerAnnotations_)
/** Annotations that control API visibility. */
private var visibilityAnnotations: Set<String> = convertToInternalNames(
@@ -135,15 +139,22 @@
* name1 - 4 are only used in exception messages.
*/
private fun findAnnotation(
- visibles: List<AnnotationNode>?,
- invisibles: List<AnnotationNode>?,
- type: String,
- name1: String,
- name2: String = "",
- name3: String = "",
+ className: String,
+ visibles: List<AnnotationNode>?,
+ invisibles: List<AnnotationNode>?,
+ type: String,
+ name1: String,
+ name2: String = "",
+ name3: String = "",
): FilterPolicyWithReason? {
detectInvalidAnnotations(visibles, invisibles, type, name1, name2, name3)
+ if (!annotationAllowedClassesFilter.matches(className)) {
+ throw InvalidAnnotationException(
+ "Class ${className.toHumanReadableClassName()} is not allowed to have " +
+ "Ravenwood annotations. Contact g/ravenwood for more details.")
+ }
+
findAnyAnnotation(stubAnnotations, visibles, invisibles)?.let {
return FilterPolicy.Stub.withReason(reasonAnnotation)
}
@@ -170,6 +181,7 @@
val cn = classes.getClass(className)
findAnnotation(
+ cn.name,
cn.visibleAnnotations,
cn.invisibleAnnotations,
"class",
@@ -193,6 +205,7 @@
cn.fields?.firstOrNull { it.name == fieldName }?.let {fn ->
findAnnotation(
+ cn.name,
fn.visibleAnnotations,
fn.invisibleAnnotations,
"field",
@@ -229,6 +242,7 @@
// If there's no substitution, then we check the annotation.
findAnnotation(
+ cn.name,
mn.visibleAnnotations,
mn.invisibleAnnotations,
"method",
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index 46546e8..416f085 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -15,7 +15,7 @@
*/
package com.android.hoststubgen.filters
-import com.android.hoststubgen.UserErrorException
+import com.android.hoststubgen.ParseException
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.log
import com.android.hoststubgen.normalizeTextLine
@@ -46,30 +46,6 @@
return (access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED)) != 0
}
-/**
- * Exception for a parse error.
- */
-private class ParseException : Exception, UserErrorException {
- val hasSourceInfo: Boolean
-
- constructor(message: String) : super(message) {
- hasSourceInfo = false
- }
-
- constructor(message: String, file: String, line: Int) :
- super("$message in file $file line $line") {
- hasSourceInfo = true
- }
-
- fun withSourceInfo(filename: String, lineNo: Int): ParseException {
- if (hasSourceInfo) {
- return this // Already has source information.
- } else {
- return ParseException(this.message ?: "", filename, lineNo)
- }
- }
-}
-
private const val FILTER_REASON = "file-override"
/**
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt
new file mode 100644
index 0000000..01a7ab3
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.utils
+
+import com.android.hoststubgen.ParseException
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.asm.toJvmClassName
+import com.android.hoststubgen.normalizeTextLine
+import java.io.File
+
+/**
+ * General purpose filter for class names.
+ */
+class ClassFilter private constructor (
+ val defaultResult: Boolean,
+) {
+ private data class FilterElement(
+ val allowed: Boolean,
+ val internalName: String,
+ val isPrefix: Boolean,
+ ) {
+ fun matches(classInternalName: String): Boolean {
+ if (isPrefix) {
+ return classInternalName.startsWith(internalName)
+ } else {
+ return classInternalName == internalName
+ }
+ }
+ }
+
+ private val elements: MutableList<FilterElement> = mutableListOf()
+
+ private val cache: MutableMap<String, Boolean> = mutableMapOf()
+
+ /**
+ * Takes an internal class name (e.g. "com/android/hoststubgen/ClassName") and returns if
+ * matches the filter or not.
+ */
+ fun matches(classInternalName: String): Boolean {
+ cache[classInternalName]?.let {
+ return it
+ }
+
+ var result = defaultResult
+ run outer@{
+ elements.forEach { e ->
+ if (e.matches(classInternalName)) {
+ result = e.allowed
+ return@outer // break equivalent.
+ }
+ }
+ }
+ cache[classInternalName] = result
+
+ return result
+ }
+
+ fun getCacheSizeForTest(): Int {
+ return cache.size
+ }
+
+ companion object {
+ /**
+ * Return a filter that alawys returns true or false.
+ */
+ fun newNullFilter(defaultResult: Boolean): ClassFilter {
+ return ClassFilter(defaultResult)
+ }
+
+ /** Build a filter from a file. */
+ fun loadFromFile(filename: String, defaultResult: Boolean): ClassFilter {
+ return buildFromString(File(filename).readText(), defaultResult, filename)
+ }
+
+ /** Build a filter from a string (for unit tests). */
+ fun buildFromString(
+ filterString: String,
+ defaultResult: Boolean,
+ filenameForErrorMessage: String
+ ): ClassFilter {
+ val ret = ClassFilter(defaultResult)
+
+ var lineNo = 0
+ filterString.split('\n').forEach { s ->
+ lineNo++
+
+ var line = normalizeTextLine(s)
+
+ if (line.isEmpty()) {
+ return@forEach // skip empty lines.
+ }
+
+ line = line.toHumanReadableClassName() // Convert all the slashes to periods.
+
+ var allow = true
+ if (line.startsWith("!")) {
+ allow = false
+ line = line.substring(1).trimStart()
+ }
+
+ // Special case -- matches any class names.
+ if (line == "*") {
+ ret.elements.add(FilterElement(allow, "", true))
+ return@forEach
+ }
+
+ // Handle wildcard -- e.g. "package.name.*"
+ if (line.endsWith(".*")) {
+ ret.elements.add(FilterElement(
+ allow, line.substring(0, line.length - 2).toJvmClassName(), true))
+ return@forEach
+ }
+
+ // Any other uses of "*" would be an error.
+ if (line.contains('*')) {
+ throw ParseException(
+ "Wildcard (*) can only show up as the last element",
+ filenameForErrorMessage,
+ lineNo
+ )
+ }
+ ret.elements.add(FilterElement(allow, line.toJvmClassName(), false))
+ }
+
+ return ret
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
index 3dc6da3..e7873d6 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
@@ -16,6 +16,7 @@
static_libs: [
"hoststubgen-annotations",
],
+ visibility: ["//frameworks/base/tools/hoststubgen:__subpackages__"],
}
// Create stub/impl jars from "hoststubgen-test-tiny-framework", using the following 3 rules.
@@ -30,6 +31,7 @@
":hoststubgen-test-tiny-framework",
"policy-override-tiny-framework.txt",
],
+ visibility: ["//visibility:private"],
}
java_genrule_host {
@@ -41,6 +43,7 @@
out: [
"host_stub.jar",
],
+ visibility: ["//visibility:private"],
}
java_genrule_host {
@@ -52,6 +55,7 @@
out: [
"host_impl.jar",
],
+ visibility: ["//visibility:private"],
}
// Same as "hoststubgen-test-tiny-framework-host", but with more options, to test more hoststubgen
@@ -71,6 +75,7 @@
":hoststubgen-test-tiny-framework",
"policy-override-tiny-framework.txt",
],
+ visibility: ["//visibility:private"],
}
java_genrule_host {
@@ -82,6 +87,7 @@
out: [
"host_stub.jar",
],
+ visibility: ["//visibility:private"],
}
java_genrule_host {
@@ -93,6 +99,7 @@
out: [
"host_impl.jar",
],
+ visibility: ["//visibility:private"],
}
// Compile the test jar, using 2 rules.
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt
new file mode 100644
index 0000000..bd9e85e
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt
@@ -0,0 +1,29 @@
+# Only classes listed here can use the hoststubgen annotations.
+
+# For each class, we check each item in this file, and when a match is found, we
+# either allow it if the line doesn't have a !, or disallow if the line has a !.
+# All the lines after the matching line will be ignored.
+
+
+# To allow a specific class to use annotations:
+# com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+
+# To disallow a specific class to use annotations:
+# !com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+
+# To allow a specific package to use annotations:
+# com.android.hoststubgen.test.*
+
+# To disallow a specific package to use annotations:
+# !com.android.hoststubgen.test.*
+
+
+com.android.hoststubgen.test.tinyframework.*
+com.supported.*
+com.unsupported.*
+
+# Use this to allow all packages
+# *
+
+# Use this to allow all packages
+# !*
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
index e212890..872bbf8 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
@@ -93,6 +93,7 @@
--gen-keep-all-file out/tiny-framework_keep_all.txt \
--gen-input-dump-file out/tiny-framework_dump.txt \
--package-redirect com.unsupported:com.supported \
+ --annotation-allowed-classes-file annotation-allowed-classes-tiny-framework.txt \
$HOSTSTUBGEN_OPTS
# Extract the jar files, so we can look into them.
diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt
new file mode 100644
index 0000000..f651514
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.utils
+
+import com.android.hoststubgen.ParseException
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Test
+
+class ClassFilterTest {
+ @Test
+ fun testDefaultTrue() {
+ val f = ClassFilter.newNullFilter(true)
+ assertThat(f.matches("a/b/c")).isEqualTo(true)
+ }
+
+ @Test
+ fun testDefaultFalse() {
+ val f = ClassFilter.newNullFilter(false)
+ assertThat(f.matches("a/b/c")).isEqualTo(false)
+ }
+
+ @Test
+ fun testComplex1() {
+ val f = ClassFilter.buildFromString("""
+ # ** this is a comment **
+ a.b.c # allow
+ !a.b.d # disallow
+ * # allow all
+ """.trimIndent(), false, "X")
+ assertThat(f.getCacheSizeForTest()).isEqualTo(0)
+
+ assertThat(f.matches("a/b/c")).isEqualTo(true)
+ assertThat(f.getCacheSizeForTest()).isEqualTo(1)
+
+ assertThat(f.matches("a/b/d")).isEqualTo(false)
+ assertThat(f.matches("x")).isEqualTo(true)
+
+ assertThat(f.getCacheSizeForTest()).isEqualTo(3)
+
+ // Make sure the cache is working
+ assertThat(f.matches("x")).isEqualTo(true)
+ }
+
+ @Test
+ fun testComplex2() {
+ val f = ClassFilter.buildFromString("""
+ a.b.c # allow
+ !a.* # disallow everything else in package "a".
+ !d.e.f # disallow d.e.f.
+
+ # everything else is allowed by default
+ """.trimIndent(), true, "X")
+ assertThat(f.matches("a/b/c")).isEqualTo(true)
+ assertThat(f.matches("a/x")).isEqualTo(false)
+ assertThat(f.matches("d/e/f")).isEqualTo(false)
+ assertThat(f.matches("d/e/f/g")).isEqualTo(true)
+ assertThat(f.matches("x")).isEqualTo(true)
+ }
+
+ @Test
+ fun testBadFilter1() {
+ try {
+ ClassFilter.buildFromString("""
+ a*
+ """.trimIndent(), true, "FILENAME")
+ fail("ParseException didn't happen")
+ } catch (e: ParseException) {
+ assertThat(e.message).contains("Wildcard")
+ assertThat(e.message).contains("FILENAME")
+ assertThat(e.message).contains("line 1")
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index ba1d404..4afa2d7 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -33,6 +33,9 @@
# First, build all the test / etc modules. This shouldn't fail.
run m "${MUST_BUILD_MODULES[@]}"
+# Run the hoststubgen unittests / etc
+run atest hoststubgentest hoststubgen-invoke-test
+
# Next, run the golden check. This should always pass too.
# The following scripts _should_ pass too, but they depend on the internal paths to soong generated
# files, and they may fail when something changes in the build system.