diff options
8 files changed, 343 insertions, 322 deletions
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 0cf9ff0ab3b5..629d846212af 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -12,6 +12,14 @@ package { } filegroup { + name: "ravenwood-common-policies", + srcs: [ + "texts/ravenwood-common-policies.txt", + ], + visibility: ["//visibility:private"], +} + +filegroup { name: "ravenwood-services-policies", srcs: [ "texts/ravenwood-services-policies.txt", diff --git a/ravenwood/Framework.bp b/ravenwood/Framework.bp index 07b453431751..5cb1479514a3 100644 --- a/ravenwood/Framework.bp +++ b/ravenwood/Framework.bp @@ -30,6 +30,7 @@ genrule_defaults { tools: ["hoststubgen"], srcs: [ ":framework-minus-apex-for-host", + ":ravenwood-common-policies", ":ravenwood-framework-policies", ":ravenwood-standard-options", ":ravenwood-annotation-allowed-classes", @@ -46,6 +47,7 @@ framework_minus_apex_cmd = "$(location hoststubgen) " + "--debug-log $(location hoststubgen_framework-minus-apex.log) " + "--out-jar $(location ravenwood.jar) " + "--in-jar $(location :framework-minus-apex-for-host) " + + "--policy-override-file $(location :ravenwood-common-policies) " + "--policy-override-file $(location :ravenwood-framework-policies) " + "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) " @@ -180,17 +182,18 @@ java_genrule { "--debug-log $(location hoststubgen_services.core.log) " + "--stats-file $(location hoststubgen_services.core_stats.csv) " + "--supported-api-list-file $(location hoststubgen_services.core_apis.csv) " + - - "--out-jar $(location ravenwood.jar) " + - "--gen-keep-all-file $(location hoststubgen_services.core_keep_all.txt) " + "--gen-input-dump-file $(location hoststubgen_services.core_dump.txt) " + + "--out-jar $(location ravenwood.jar) " + "--in-jar $(location :services.core-for-host) " + + + "--policy-override-file $(location :ravenwood-common-policies) " + "--policy-override-file $(location :ravenwood-services-policies) " + "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ", srcs: [ ":services.core-for-host", + ":ravenwood-common-policies", ":ravenwood-services-policies", ":ravenwood-standard-options", ":ravenwood-annotation-allowed-classes", @@ -247,21 +250,20 @@ java_genrule { "--debug-log $(location hoststubgen_core-icu4j-for-host.log) " + "--stats-file $(location hoststubgen_core-icu4j-for-host_stats.csv) " + "--supported-api-list-file $(location hoststubgen_core-icu4j-for-host_apis.csv) " + - - "--out-jar $(location ravenwood.jar) " + - "--gen-keep-all-file $(location hoststubgen_core-icu4j-for-host_keep_all.txt) " + "--gen-input-dump-file $(location hoststubgen_core-icu4j-for-host_dump.txt) " + + "--out-jar $(location ravenwood.jar) " + "--in-jar $(location :core-icu4j-for-host) " + - "--policy-override-file $(location :icu-ravenwood-policies) " + - "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ", + + "--policy-override-file $(location :ravenwood-common-policies) " + + "--policy-override-file $(location :icu-ravenwood-policies) ", srcs: [ ":core-icu4j-for-host", + ":ravenwood-common-policies", ":icu-ravenwood-policies", ":ravenwood-standard-options", - ":ravenwood-annotation-allowed-classes", ], out: [ "ravenwood.jar", diff --git a/ravenwood/texts/ravenwood-common-policies.txt b/ravenwood/texts/ravenwood-common-policies.txt new file mode 100644 index 000000000000..08f5397730da --- /dev/null +++ b/ravenwood/texts/ravenwood-common-policies.txt @@ -0,0 +1,20 @@ +# Ravenwood "policy" that should apply to all code. + +# Keep all AIDL interfaces +class :aidl keepclass + +# Keep all feature flag implementations +class :feature_flags keepclass + +# Keep all sysprops generated code implementations +class :sysprops keepclass + +# Keep all resource R classes +class :r keepclass + +# Support APIs not available in standard JRE +class java.io.FileDescriptor keep + method getInt$ ()I @com.android.ravenwood.RavenwoodJdkPatch.getInt$ + method setInt$ (I)V @com.android.ravenwood.RavenwoodJdkPatch.setInt$ +class java.util.LinkedHashMap keep + method eldest ()Ljava/util/Map$Entry; @com.android.ravenwood.RavenwoodJdkPatch.eldest diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt index d962c8232bf7..3649f0e78f09 100644 --- a/ravenwood/texts/ravenwood-framework-policies.txt +++ b/ravenwood/texts/ravenwood-framework-policies.txt @@ -1,29 +1,10 @@ # Ravenwood "policy" file for framework-minus-apex. -# Keep all AIDL interfaces -class :aidl keepclass - -# Keep all feature flag implementations -class :feature_flags keepclass - -# Keep all sysprops generated code implementations -class :sysprops keepclass - -# Keep all resource R classes -class :r keepclass - # To avoid VerifyError on nano proto files (b/324063814), we rename nano proto classes. # Note: The "rename" directive must use slashes (/) as a package name separator. rename com/.*/nano/ devicenano/ rename android/.*/nano/ devicenano/ -# Support APIs not available in standard JRE -class java.io.FileDescriptor keep - method getInt$ ()I @com.android.ravenwood.RavenwoodJdkPatch.getInt$ - method setInt$ (I)V @com.android.ravenwood.RavenwoodJdkPatch.setInt$ -class java.util.LinkedHashMap keep - method eldest ()Ljava/util/Map$Entry; @com.android.ravenwood.RavenwoodJdkPatch.eldest - # Exported to Mainline modules; cannot use annotations class com.android.internal.util.FastXmlSerializer keepclass class com.android.internal.util.FileRotator keepclass diff --git a/ravenwood/texts/ravenwood-services-policies.txt b/ravenwood/texts/ravenwood-services-policies.txt index 5cdb4f74d7c0..cc2fa602b3c3 100644 --- a/ravenwood/texts/ravenwood-services-policies.txt +++ b/ravenwood/texts/ravenwood-services-policies.txt @@ -1,7 +1 @@ # Ravenwood "policy" file for services.core. - -# Keep all AIDL interfaces -class :aidl keepclass - -# Keep all feature flag implementations -class :feature_flags keepclass diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 165bb5772449..6d8d7b768b91 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -27,7 +27,7 @@ import com.android.hoststubgen.filters.ImplicitOutputFilter import com.android.hoststubgen.filters.KeepNativeFilter import com.android.hoststubgen.filters.OutputFilter import com.android.hoststubgen.filters.SanitizationFilter -import com.android.hoststubgen.filters.createFilterFromTextPolicyFile +import com.android.hoststubgen.filters.TextFileFilterPolicyParser import com.android.hoststubgen.filters.printAsTextPolicy import com.android.hoststubgen.utils.ClassFilter import com.android.hoststubgen.visitors.BaseAdapter @@ -178,8 +178,10 @@ class HostStubGen(val options: HostStubGenOptions) { // Next, "text based" filter, which allows to override polices without touching // the target code. - options.policyOverrideFile.ifSet { - filter = createFilterFromTextPolicyFile(it, allClasses, filter) + if (options.policyOverrideFiles.isNotEmpty()) { + val parser = TextFileFilterPolicyParser(allClasses, filter) + options.policyOverrideFiles.forEach(parser::parse) + filter = parser.createOutputFilter() } // Apply the implicit filter. diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index b083d89c61d6..55e853e3e2fb 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt @@ -100,7 +100,7 @@ class HostStubGenOptions( var defaultClassLoadHook: SetOnce<String?> = SetOnce(null), var defaultMethodCallHook: SetOnce<String?> = SetOnce(null), - var policyOverrideFile: SetOnce<String?> = SetOnce(null), + var policyOverrideFiles: MutableList<String> = mutableListOf(), var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove), @@ -164,7 +164,7 @@ class HostStubGenOptions( "--out-jar", "--out-impl-jar" -> ret.outJar.set(nextArg()) "--policy-override-file" -> - ret.policyOverrideFile.set(nextArg())!!.ensureFileExists() + ret.policyOverrideFiles.add(nextArg().ensureFileExists()) "--clean-up-on-error" -> ret.cleanUpOnError.set(true) "--no-clean-up-on-error" -> ret.cleanUpOnError.set(false) @@ -291,7 +291,7 @@ class HostStubGenOptions( annotationAllowedClassesFile=$annotationAllowedClassesFile, defaultClassLoadHook=$defaultClassLoadHook, defaultMethodCallHook=$defaultMethodCallHook, - policyOverrideFile=$policyOverrideFile, + policyOverrideFiles=${policyOverrideFiles.toTypedArray().contentToString()}, defaultPolicy=$defaultPolicy, cleanUpOnError=$cleanUpOnError, enableClassChecker=$enableClassChecker, 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 073b503401b5..caf80ebec0c9 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt @@ -23,13 +23,10 @@ import com.android.hoststubgen.asm.toJvmClassName import com.android.hoststubgen.log import com.android.hoststubgen.normalizeTextLine import com.android.hoststubgen.whitespaceRegex -import org.objectweb.asm.Opcodes -import org.objectweb.asm.tree.ClassNode -import java.io.BufferedReader -import java.io.FileReader +import java.io.File import java.io.PrintWriter -import java.util.Objects import java.util.regex.Pattern +import org.objectweb.asm.tree.ClassNode /** * Print a class node as a "keep" policy. @@ -49,256 +46,56 @@ fun printAsTextPolicy(pw: PrintWriter, cn: ClassNode) { } } -/** Return true if [access] is either public or protected. */ -private fun isVisible(access: Int): Boolean { - return (access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED)) != 0 -} - private const val FILTER_REASON = "file-override" -/** - * Read a given "policy" file and return as an [OutputFilter] - */ -fun createFilterFromTextPolicyFile( - filename: String, - classes: ClassNodes, - fallback: OutputFilter, - ): OutputFilter { - log.i("Loading offloaded annotations from $filename ...") - log.withIndent { - val subclassFilter = SubclassFilter(classes, fallback) - val packageFilter = PackageFilter(subclassFilter) - val imf = InMemoryOutputFilter(classes, packageFilter) - - var lineNo = 0 - - var aidlPolicy: FilterPolicyWithReason? = null - var featureFlagsPolicy: FilterPolicyWithReason? = null - var syspropsPolicy: FilterPolicyWithReason? = null - var rFilePolicy: FilterPolicyWithReason? = null - val typeRenameSpec = mutableListOf<TextFilePolicyRemapperFilter.TypeRenameSpec>() - val methodReplaceSpec = - mutableListOf<TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec>() - - try { - BufferedReader(FileReader(filename)).use { reader -> - var className = "" - - while (true) { - var line = reader.readLine() ?: break - lineNo++ - - line = normalizeTextLine(line) +private enum class SpecialClass { + NotSpecial, + Aidl, + FeatureFlags, + Sysprops, + RFile, +} +class TextFileFilterPolicyParser( + private val classes: ClassNodes, + fallback: OutputFilter +) { + private val subclassFilter = SubclassFilter(classes, fallback) + private val packageFilter = PackageFilter(subclassFilter) + private val imf = InMemoryOutputFilter(classes, packageFilter) + private var aidlPolicy: FilterPolicyWithReason? = null + private var featureFlagsPolicy: FilterPolicyWithReason? = null + private var syspropsPolicy: FilterPolicyWithReason? = null + private var rFilePolicy: FilterPolicyWithReason? = null + private val typeRenameSpec = mutableListOf<TextFilePolicyRemapperFilter.TypeRenameSpec>() + private val methodReplaceSpec = + mutableListOf<TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec>() + + private lateinit var currentClassName: String + + /** + * Read a given "policy" file and return as an [OutputFilter] + */ + fun parse(file: String) { + log.i("Loading offloaded annotations from $file ...") + log.withIndent { + var lineNo = 0 + try { + File(file).forEachLine { + lineNo++ + val line = normalizeTextLine(it) if (line.isEmpty()) { - continue // skip empty lines. - } - - - // TODO: Method too long, break it up. - - val fields = line.split(whitespaceRegex).toTypedArray() - when (fields[0].lowercase()) { - "p", "package" -> { - if (fields.size < 3) { - throw ParseException("Package ('p') expects 2 fields.") - } - val name = fields[1] - val rawPolicy = fields[2] - if (resolveExtendingClass(name) != null) { - throw ParseException("Package can't be a super class type") - } - if (resolveSpecialClass(name) != SpecialClass.NotSpecial) { - throw ParseException("Package can't be a special class type") - } - if (rawPolicy.startsWith("!")) { - throw ParseException("Package can't have a substitution") - } - if (rawPolicy.startsWith("~")) { - throw ParseException("Package can't have a class load hook") - } - val policy = parsePolicy(rawPolicy) - if (!policy.isUsableWithClasses) { - throw ParseException("Package can't have policy '$policy'") - } - packageFilter.addPolicy(name, policy.withReason(FILTER_REASON)) - } - - "c", "class" -> { - if (fields.size < 3) { - throw ParseException("Class ('c') expects 2 fields.") - } - className = fields[1] - - // superClass is set when the class name starts with a "*". - val superClass = resolveExtendingClass(className) - - // :aidl, etc? - val classType = resolveSpecialClass(className) - - if (fields[2].startsWith("!")) { - if (classType != SpecialClass.NotSpecial) { - // We could support it, but not needed at least for now. - throw ParseException( - "Special class can't have a substitution") - } - // It's a redirection class. - val toClass = fields[2].substring(1) - imf.setRedirectionClass(className, toClass) - } else if (fields[2].startsWith("~")) { - if (classType != SpecialClass.NotSpecial) { - // We could support it, but not needed at least for now. - throw ParseException( - "Special class can't have a class load hook") - } - // It's a class-load hook - val callback = fields[2].substring(1) - imf.setClassLoadHook(className, callback) - } else { - val policy = parsePolicy(fields[2]) - if (!policy.isUsableWithClasses) { - throw ParseException("Class can't have policy '$policy'") - } - Objects.requireNonNull(className) - - when (classType) { - SpecialClass.NotSpecial -> { - // TODO: Duplicate check, etc - if (superClass == null) { - imf.setPolicyForClass( - className, policy.withReason(FILTER_REASON) - ) - } else { - subclassFilter.addPolicy(superClass, - policy.withReason("extends $superClass")) - } - } - SpecialClass.Aidl -> { - if (aidlPolicy != null) { - throw ParseException( - "Policy for AIDL classes already defined") - } - aidlPolicy = policy.withReason( - "$FILTER_REASON (special-class AIDL)") - } - SpecialClass.FeatureFlags -> { - if (featureFlagsPolicy != null) { - throw ParseException( - "Policy for feature flags already defined") - } - featureFlagsPolicy = policy.withReason( - "$FILTER_REASON (special-class feature flags)") - } - SpecialClass.Sysprops -> { - if (syspropsPolicy != null) { - throw ParseException( - "Policy for sysprops already defined") - } - syspropsPolicy = policy.withReason( - "$FILTER_REASON (special-class sysprops)") - } - SpecialClass.RFile -> { - if (rFilePolicy != null) { - throw ParseException( - "Policy for R file already defined") - } - rFilePolicy = policy.withReason( - "$FILTER_REASON (special-class R file)") - } - } - } - } - - "f", "field" -> { - if (fields.size < 3) { - throw ParseException("Field ('f') expects 2 fields.") - } - val name = fields[1] - val policy = parsePolicy(fields[2]) - if (!policy.isUsableWithFields) { - throw ParseException("Field can't have policy '$policy'") - } - Objects.requireNonNull(className) - - // TODO: Duplicate check, etc - imf.setPolicyForField(className, name, policy.withReason(FILTER_REASON)) - } - - "m", "method" -> { - if (fields.size < 4) { - throw ParseException("Method ('m') expects 3 fields.") - } - val name = fields[1] - val signature = fields[2] - val policy = parsePolicy(fields[3]) - - if (!policy.isUsableWithMethods) { - throw ParseException("Method can't have policy '$policy'") - } - - Objects.requireNonNull(className) - - imf.setPolicyForMethod(className, name, signature, - policy.withReason(FILTER_REASON)) - if (policy == FilterPolicy.Substitute) { - val fromName = fields[3].substring(1) - - if (fromName == name) { - throw ParseException( - "Substitution must have a different name") - } - - // Set the policy for the "from" method. - imf.setPolicyForMethod(className, fromName, signature, - FilterPolicy.Keep.withReason(FILTER_REASON)) - - val classAndMethod = splitWithLastPeriod(fromName) - if (classAndMethod != null) { - // If the substitution target contains a ".", then - // it's a method call redirect. - methodReplaceSpec.add( - TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec( - className.toJvmClassName(), - name, - signature, - classAndMethod.first.toJvmClassName(), - classAndMethod.second, - ) - ) - } else { - // It's an in-class replace. - // ("@RavenwoodReplace" equivalent) - imf.setRenameTo(className, fromName, signature, name) - } - } - } - "r", "rename" -> { - if (fields.size < 3) { - throw ParseException("Rename ('r') expects 2 fields.") - } - // Add ".*" to make it a prefix match. - val pattern = Pattern.compile(fields[1] + ".*") - - // Removing the leading /'s from the prefix. This allows - // using a single '/' as an empty suffix, which is useful to have a - // "negative" rename rule to avoid subsequent raname's from getting - // applied. (Which is needed for services.jar) - val prefix = fields[2].trimStart('/') - - typeRenameSpec += TextFilePolicyRemapperFilter.TypeRenameSpec( - pattern, prefix) - } - - else -> { - throw ParseException("Unknown directive \"${fields[0]}\"") - } + return@forEachLine // skip empty lines. } + parseLine(line) } + } catch (e: ParseException) { + throw e.withSourceInfo(file, lineNo) } - } catch (e: ParseException) { - throw e.withSourceInfo(filename, lineNo) } + } + fun createOutputFilter(): OutputFilter { var ret: OutputFilter = imf if (typeRenameSpec.isNotEmpty()) { ret = TextFilePolicyRemapperFilter(typeRenameSpec, ret) @@ -309,54 +106,271 @@ fun createFilterFromTextPolicyFile( // Wrap the in-memory-filter with AHF. ret = AndroidHeuristicsFilter( - classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, ret) + classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, ret + ) return ret } -} -private enum class SpecialClass { - NotSpecial, - Aidl, - FeatureFlags, - Sysprops, - RFile, -} + private fun parseLine(line: String) { + val fields = line.split(whitespaceRegex).toTypedArray() + when (fields[0].lowercase()) { + "p", "package" -> parsePackage(fields) + "c", "class" -> parseClass(fields) + "f", "field" -> parseField(fields) + "m", "method" -> parseMethod(fields) + "r", "rename" -> parseRename(fields) + else -> throw ParseException("Unknown directive \"${fields[0]}\"") + } + } -private fun resolveSpecialClass(className: String): SpecialClass { - if (!className.startsWith(":")) { - return SpecialClass.NotSpecial + private fun resolveSpecialClass(className: String): SpecialClass { + if (!className.startsWith(":")) { + return SpecialClass.NotSpecial + } + when (className.lowercase()) { + ":aidl" -> return SpecialClass.Aidl + ":feature_flags" -> return SpecialClass.FeatureFlags + ":sysprops" -> return SpecialClass.Sysprops + ":r" -> return SpecialClass.RFile + } + throw ParseException("Invalid special class name \"$className\"") } - when (className.lowercase()) { - ":aidl" -> return SpecialClass.Aidl - ":feature_flags" -> return SpecialClass.FeatureFlags - ":sysprops" -> return SpecialClass.Sysprops - ":r" -> return SpecialClass.RFile + + private fun resolveExtendingClass(className: String): String? { + if (!className.startsWith("*")) { + return null + } + return className.substring(1) } - throw ParseException("Invalid special class name \"$className\"") -} -private fun resolveExtendingClass(className: String): String? { - if (!className.startsWith("*")) { - return null + private fun parsePolicy(s: String): FilterPolicy { + return when (s.lowercase()) { + "k", "keep" -> FilterPolicy.Keep + "t", "throw" -> FilterPolicy.Throw + "r", "remove" -> FilterPolicy.Remove + "kc", "keepclass" -> FilterPolicy.KeepClass + "i", "ignore" -> FilterPolicy.Ignore + "rdr", "redirect" -> FilterPolicy.Redirect + else -> { + if (s.startsWith("@")) { + FilterPolicy.Substitute + } else { + throw ParseException("Invalid policy \"$s\"") + } + } + } } - return className.substring(1) -} -private fun parsePolicy(s: String): FilterPolicy { - return when (s.lowercase()) { - "k", "keep" -> FilterPolicy.Keep - "t", "throw" -> FilterPolicy.Throw - "r", "remove" -> FilterPolicy.Remove - "kc", "keepclass" -> FilterPolicy.KeepClass - "i", "ignore" -> FilterPolicy.Ignore - "rdr", "redirect" -> FilterPolicy.Redirect - else -> { - if (s.startsWith("@")) { - FilterPolicy.Substitute + private fun parsePackage(fields: Array<String>) { + if (fields.size < 3) { + throw ParseException("Package ('p') expects 2 fields.") + } + val name = fields[1] + val rawPolicy = fields[2] + if (resolveExtendingClass(name) != null) { + throw ParseException("Package can't be a super class type") + } + if (resolveSpecialClass(name) != SpecialClass.NotSpecial) { + throw ParseException("Package can't be a special class type") + } + if (rawPolicy.startsWith("!")) { + throw ParseException("Package can't have a substitution") + } + if (rawPolicy.startsWith("~")) { + throw ParseException("Package can't have a class load hook") + } + val policy = parsePolicy(rawPolicy) + if (!policy.isUsableWithClasses) { + throw ParseException("Package can't have policy '$policy'") + } + packageFilter.addPolicy(name, policy.withReason(FILTER_REASON)) + } + + private fun parseClass(fields: Array<String>) { + if (fields.size < 3) { + throw ParseException("Class ('c') expects 2 fields.") + } + currentClassName = fields[1] + + // superClass is set when the class name starts with a "*". + val superClass = resolveExtendingClass(currentClassName) + + // :aidl, etc? + val classType = resolveSpecialClass(currentClassName) + + if (fields[2].startsWith("!")) { + if (classType != SpecialClass.NotSpecial) { + // We could support it, but not needed at least for now. + throw ParseException( + "Special class can't have a substitution" + ) + } + // It's a redirection class. + val toClass = fields[2].substring(1) + imf.setRedirectionClass(currentClassName, toClass) + } else if (fields[2].startsWith("~")) { + if (classType != SpecialClass.NotSpecial) { + // We could support it, but not needed at least for now. + throw ParseException( + "Special class can't have a class load hook" + ) + } + // It's a class-load hook + val callback = fields[2].substring(1) + imf.setClassLoadHook(currentClassName, callback) + } else { + val policy = parsePolicy(fields[2]) + if (!policy.isUsableWithClasses) { + throw ParseException("Class can't have policy '$policy'") + } + + when (classType) { + SpecialClass.NotSpecial -> { + // TODO: Duplicate check, etc + if (superClass == null) { + imf.setPolicyForClass( + currentClassName, policy.withReason(FILTER_REASON) + ) + } else { + subclassFilter.addPolicy( + superClass, + policy.withReason("extends $superClass") + ) + } + } + + SpecialClass.Aidl -> { + if (aidlPolicy != null) { + throw ParseException( + "Policy for AIDL classes already defined" + ) + } + aidlPolicy = policy.withReason( + "$FILTER_REASON (special-class AIDL)" + ) + } + + SpecialClass.FeatureFlags -> { + if (featureFlagsPolicy != null) { + throw ParseException( + "Policy for feature flags already defined" + ) + } + featureFlagsPolicy = policy.withReason( + "$FILTER_REASON (special-class feature flags)" + ) + } + + SpecialClass.Sysprops -> { + if (syspropsPolicy != null) { + throw ParseException( + "Policy for sysprops already defined" + ) + } + syspropsPolicy = policy.withReason( + "$FILTER_REASON (special-class sysprops)" + ) + } + + SpecialClass.RFile -> { + if (rFilePolicy != null) { + throw ParseException( + "Policy for R file already defined" + ) + } + rFilePolicy = policy.withReason( + "$FILTER_REASON (special-class R file)" + ) + } + } + } + } + + private fun parseField(fields: Array<String>) { + if (fields.size < 3) { + throw ParseException("Field ('f') expects 2 fields.") + } + val name = fields[1] + val policy = parsePolicy(fields[2]) + if (!policy.isUsableWithFields) { + throw ParseException("Field can't have policy '$policy'") + } + require(this::currentClassName.isInitialized) + + // TODO: Duplicate check, etc + imf.setPolicyForField(currentClassName, name, policy.withReason(FILTER_REASON)) + } + + private fun parseMethod(fields: Array<String>) { + if (fields.size < 4) { + throw ParseException("Method ('m') expects 3 fields.") + } + val name = fields[1] + val signature = fields[2] + val policy = parsePolicy(fields[3]) + + if (!policy.isUsableWithMethods) { + throw ParseException("Method can't have policy '$policy'") + } + + require(this::currentClassName.isInitialized) + + imf.setPolicyForMethod( + currentClassName, name, signature, + policy.withReason(FILTER_REASON) + ) + if (policy == FilterPolicy.Substitute) { + val fromName = fields[3].substring(1) + + if (fromName == name) { + throw ParseException( + "Substitution must have a different name" + ) + } + + // Set the policy for the "from" method. + imf.setPolicyForMethod( + currentClassName, fromName, signature, + FilterPolicy.Keep.withReason(FILTER_REASON) + ) + + val classAndMethod = splitWithLastPeriod(fromName) + if (classAndMethod != null) { + // If the substitution target contains a ".", then + // it's a method call redirect. + methodReplaceSpec.add( + TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec( + currentClassName.toJvmClassName(), + name, + signature, + classAndMethod.first.toJvmClassName(), + classAndMethod.second, + ) + ) } else { - throw ParseException("Invalid policy \"$s\"") + // It's an in-class replace. + // ("@RavenwoodReplace" equivalent) + imf.setRenameTo(currentClassName, fromName, signature, name) } } } + + private fun parseRename(fields: Array<String>) { + if (fields.size < 3) { + throw ParseException("Rename ('r') expects 2 fields.") + } + // Add ".*" to make it a prefix match. + val pattern = Pattern.compile(fields[1] + ".*") + + // Removing the leading /'s from the prefix. This allows + // using a single '/' as an empty suffix, which is useful to have a + // "negative" rename rule to avoid subsequent raname's from getting + // applied. (Which is needed for services.jar) + val prefix = fields[2].trimStart('/') + + typeRenameSpec += TextFilePolicyRemapperFilter.TypeRenameSpec( + pattern, prefix + ) + } } |