1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.hoststubgen 17 18 import com.android.hoststubgen.filters.FilterPolicy 19 import java.io.BufferedReader 20 import java.io.File 21 import java.io.FileReader 22 23 /** 24 * A single value that can only set once. 25 */ 26 class SetOnce<T>( 27 private var value: T, 28 ) { 29 class SetMoreThanOnceException : Exception() 30 31 private var set = false 32 33 fun set(v: T) { 34 if (set) { 35 throw SetMoreThanOnceException() 36 } 37 if (v == null) { 38 throw NullPointerException("This shouldn't happen") 39 } 40 set = true 41 value = v 42 } 43 44 val get: T 45 get() = this.value 46 47 val isSet: Boolean 48 get() = this.set 49 50 fun <R> ifSet(block: (T & Any) -> R): R? { 51 if (isSet) { 52 return block(value!!) 53 } 54 return null 55 } 56 57 override fun toString(): String { 58 return "$value" 59 } 60 } 61 62 /** 63 * Options that can be set from command line arguments. 64 */ 65 class HostStubGenOptions( 66 /** Input jar file*/ 67 var inJar: SetOnce<String> = SetOnce(""), 68 69 /** Output stub jar file */ 70 var outStubJar: SetOnce<String?> = SetOnce(null), 71 72 /** Output implementation jar file */ 73 var outImplJar: SetOnce<String?> = SetOnce(null), 74 75 var inputJarDumpFile: SetOnce<String?> = SetOnce(null), 76 77 var inputJarAsKeepAllFile: SetOnce<String?> = SetOnce(null), 78 79 var stubAnnotations: MutableSet<String> = mutableSetOf(), 80 var keepAnnotations: MutableSet<String> = mutableSetOf(), 81 var throwAnnotations: MutableSet<String> = mutableSetOf(), 82 var removeAnnotations: MutableSet<String> = mutableSetOf(), 83 var stubClassAnnotations: MutableSet<String> = mutableSetOf(), 84 var keepClassAnnotations: MutableSet<String> = mutableSetOf(), 85 86 var substituteAnnotations: MutableSet<String> = mutableSetOf(), 87 var nativeSubstituteAnnotations: MutableSet<String> = mutableSetOf(), 88 var classLoadHookAnnotations: MutableSet<String> = mutableSetOf(), 89 var keepStaticInitializerAnnotations: MutableSet<String> = mutableSetOf(), 90 91 var packageRedirects: MutableList<Pair<String, String>> = mutableListOf(), 92 93 var annotationAllowedClassesFile: SetOnce<String?> = SetOnce(null), 94 95 var defaultClassLoadHook: SetOnce<String?> = SetOnce(null), 96 var defaultMethodCallHook: SetOnce<String?> = SetOnce(null), 97 98 var intersectStubJars: MutableSet<String> = mutableSetOf(), 99 100 var policyOverrideFile: SetOnce<String?> = SetOnce(null), 101 102 var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove), 103 104 var cleanUpOnError: SetOnce<Boolean> = SetOnce(false), 105 106 var enableClassChecker: SetOnce<Boolean> = SetOnce(false), 107 var enablePreTrace: SetOnce<Boolean> = SetOnce(false), 108 var enablePostTrace: SetOnce<Boolean> = SetOnce(false), 109 110 var enableNonStubMethodCallDetection: SetOnce<Boolean> = SetOnce(false), 111 112 var statsFile: SetOnce<String?> = SetOnce(null), 113 114 var apiListFile: SetOnce<String?> = SetOnce(null), 115 ) { 116 companion object { 117 ensureFileExistsnull118 private fun String.ensureFileExists(): String { 119 if (!File(this).exists()) { 120 throw InputFileNotFoundException(this) 121 } 122 return this 123 } 124 parsePackageRedirectnull125 private fun parsePackageRedirect(fromColonTo: String): Pair<String, String> { 126 val colon = fromColonTo.indexOf(':') 127 if ((colon < 1) || (colon + 1 >= fromColonTo.length)) { 128 throw ArgumentsException("--package-redirect must be a colon-separated string") 129 } 130 // TODO check for duplicates 131 return Pair(fromColonTo.substring(0, colon), fromColonTo.substring(colon + 1)) 132 } 133 parseArgsnull134 fun parseArgs(args: Array<String>): HostStubGenOptions { 135 val ret = HostStubGenOptions() 136 137 val ai = ArgIterator(expandAtFiles(args)) 138 139 var allAnnotations = mutableSetOf<String>() 140 141 fun ensureUniqueAnnotation(name: String): String { 142 if (!allAnnotations.add(name)) { 143 throw DuplicateAnnotationException(ai.current) 144 } 145 return name 146 } 147 148 fun setLogFile(level: LogLevel, filename: String) { 149 log.addFilePrinter(level, filename) 150 log.i("$level log file: $filename") 151 } 152 153 while (true) { 154 val arg = ai.nextArgOptional() 155 if (arg == null) { 156 break 157 } 158 159 // Define some shorthands... 160 fun nextArg(): String = ai.nextArgRequired(arg) 161 fun SetOnce<String>.setNextStringArg(): String = nextArg().also { this.set(it) } 162 fun SetOnce<String?>.setNextStringArg(): String = nextArg().also { this.set(it) } 163 fun MutableSet<String>.addUniqueAnnotationArg(): String = 164 nextArg().also { this += ensureUniqueAnnotation(it) } 165 166 try { 167 when (arg) { 168 // TODO: Write help 169 "-h", "--help" -> TODO("Help is not implemented yet") 170 171 "-v", "--verbose" -> log.setConsoleLogLevel(LogLevel.Verbose) 172 "-d", "--debug" -> log.setConsoleLogLevel(LogLevel.Debug) 173 "-q", "--quiet" -> log.setConsoleLogLevel(LogLevel.None) 174 175 "--in-jar" -> ret.inJar.setNextStringArg().ensureFileExists() 176 "--out-stub-jar" -> ret.outStubJar.setNextStringArg() 177 "--out-impl-jar" -> ret.outImplJar.setNextStringArg() 178 179 "--policy-override-file" -> 180 ret.policyOverrideFile.setNextStringArg().ensureFileExists() 181 182 "--clean-up-on-error" -> ret.cleanUpOnError.set(true) 183 "--no-clean-up-on-error" -> ret.cleanUpOnError.set(false) 184 185 "--default-remove" -> ret.defaultPolicy.set(FilterPolicy.Remove) 186 "--default-throw" -> ret.defaultPolicy.set(FilterPolicy.Throw) 187 "--default-keep" -> ret.defaultPolicy.set(FilterPolicy.Keep) 188 "--default-stub" -> ret.defaultPolicy.set(FilterPolicy.Stub) 189 190 "--stub-annotation" -> 191 ret.stubAnnotations.addUniqueAnnotationArg() 192 193 "--keep-annotation" -> 194 ret.keepAnnotations.addUniqueAnnotationArg() 195 196 "--stub-class-annotation" -> 197 ret.stubClassAnnotations.addUniqueAnnotationArg() 198 199 "--keep-class-annotation" -> 200 ret.keepClassAnnotations.addUniqueAnnotationArg() 201 202 "--throw-annotation" -> 203 ret.throwAnnotations.addUniqueAnnotationArg() 204 205 "--remove-annotation" -> 206 ret.removeAnnotations.addUniqueAnnotationArg() 207 208 "--substitute-annotation" -> 209 ret.substituteAnnotations.addUniqueAnnotationArg() 210 211 "--native-substitute-annotation" -> 212 ret.nativeSubstituteAnnotations.addUniqueAnnotationArg() 213 214 "--class-load-hook-annotation" -> 215 ret.classLoadHookAnnotations.addUniqueAnnotationArg() 216 217 "--keep-static-initializer-annotation" -> 218 ret.keepStaticInitializerAnnotations.addUniqueAnnotationArg() 219 220 "--package-redirect" -> 221 ret.packageRedirects += parsePackageRedirect(nextArg()) 222 223 "--annotation-allowed-classes-file" -> 224 ret.annotationAllowedClassesFile.setNextStringArg() 225 226 "--default-class-load-hook" -> 227 ret.defaultClassLoadHook.setNextStringArg() 228 229 "--default-method-call-hook" -> 230 ret.defaultMethodCallHook.setNextStringArg() 231 232 "--intersect-stub-jar" -> 233 ret.intersectStubJars += nextArg().ensureFileExists() 234 235 "--gen-keep-all-file" -> 236 ret.inputJarAsKeepAllFile.setNextStringArg() 237 238 // Following options are for debugging. 239 "--enable-class-checker" -> ret.enableClassChecker.set(true) 240 "--no-class-checker" -> ret.enableClassChecker.set(false) 241 242 "--enable-pre-trace" -> ret.enablePreTrace.set(true) 243 "--no-pre-trace" -> ret.enablePreTrace.set(false) 244 245 "--enable-post-trace" -> ret.enablePostTrace.set(true) 246 "--no-post-trace" -> ret.enablePostTrace.set(false) 247 248 "--enable-non-stub-method-check" -> 249 ret.enableNonStubMethodCallDetection.set(true) 250 251 "--no-non-stub-method-check" -> 252 ret.enableNonStubMethodCallDetection.set(false) 253 254 "--gen-input-dump-file" -> ret.inputJarDumpFile.setNextStringArg() 255 256 "--verbose-log" -> setLogFile(LogLevel.Verbose, nextArg()) 257 "--debug-log" -> setLogFile(LogLevel.Debug, nextArg()) 258 259 "--stats-file" -> ret.statsFile.setNextStringArg() 260 "--supported-api-list-file" -> ret.apiListFile.setNextStringArg() 261 262 else -> throw ArgumentsException("Unknown option: $arg") 263 } 264 } catch (e: SetOnce.SetMoreThanOnceException) { 265 throw ArgumentsException("Duplicate or conflicting argument found: $arg") 266 } 267 } 268 269 if (!ret.inJar.isSet) { 270 throw ArgumentsException("Required option missing: --in-jar") 271 } 272 if (!ret.outStubJar.isSet && !ret.outImplJar.isSet) { 273 log.w("Neither --out-stub-jar nor --out-impl-jar is set." + 274 " $executableName will not generate jar files.") 275 } 276 277 if (ret.enableNonStubMethodCallDetection.get) { 278 log.w("--enable-non-stub-method-check is not fully implemented yet." + 279 " See the todo in doesMethodNeedNonStubCallCheck().") 280 } 281 282 return ret 283 } 284 285 /** 286 * Scan the arguments, and if any of them starts with an `@`, then load from the file 287 * and use its content as arguments. 288 * 289 * In this file, each line is treated as a single argument. 290 * 291 * The file can contain '#' as comments. 292 */ expandAtFilesnull293 private fun expandAtFiles(args: Array<String>): List<String> { 294 val ret = mutableListOf<String>() 295 296 args.forEach { arg -> 297 if (!arg.startsWith('@')) { 298 ret += arg 299 return@forEach 300 } 301 // Read from the file, and add each line to the result. 302 val filename = arg.substring(1).ensureFileExists() 303 304 log.v("Expanding options file $filename") 305 306 BufferedReader(FileReader(filename)).use { reader -> 307 while (true) { 308 var line = reader.readLine() 309 if (line == null) { 310 break // EOF 311 } 312 313 line = normalizeTextLine(line) 314 if (line.isNotEmpty()) { 315 ret += line 316 } 317 } 318 } 319 } 320 return ret 321 } 322 } 323 324 open class ArgumentsException(message: String?) : Exception(message), UserErrorException 325 326 /** Thrown when the same annotation is used with different annotation arguments. */ 327 class DuplicateAnnotationException(annotationName: String?) : 328 ArgumentsException("Duplicate annotation specified: '$annotationName'") 329 330 /** Thrown when an input file does not exist. */ 331 class InputFileNotFoundException(filename: String) : 332 ArgumentsException("File '$filename' not found") 333 334 private class ArgIterator( 335 private val args: List<String>, 336 private var currentIndex: Int = -1 337 ) { 338 val current: String 339 get() = args.get(currentIndex) 340 341 /** 342 * Get the next argument, or [null] if there's no more arguments. 343 */ nextArgOptionalnull344 fun nextArgOptional(): String? { 345 if ((currentIndex + 1) >= args.size) { 346 return null 347 } 348 return args.get(++currentIndex) 349 } 350 351 /** 352 * Get the next argument, or throw if 353 */ nextArgRequirednull354 fun nextArgRequired(argName: String): String { 355 nextArgOptional().let { 356 if (it == null) { 357 throw ArgumentsException("Missing parameter for option $argName") 358 } 359 if (it.isEmpty()) { 360 throw ArgumentsException("Parameter can't be empty for option $argName") 361 } 362 return it 363 } 364 } 365 } 366 toStringnull367 override fun toString(): String { 368 return """ 369 HostStubGenOptions{ 370 inJar='$inJar', 371 outStubJar='$outStubJar', 372 outImplJar='$outImplJar', 373 inputJarDumpFile=$inputJarDumpFile, 374 inputJarAsKeepAllFile=$inputJarAsKeepAllFile, 375 stubAnnotations=$stubAnnotations, 376 keepAnnotations=$keepAnnotations, 377 throwAnnotations=$throwAnnotations, 378 removeAnnotations=$removeAnnotations, 379 stubClassAnnotations=$stubClassAnnotations, 380 keepClassAnnotations=$keepClassAnnotations, 381 substituteAnnotations=$substituteAnnotations, 382 nativeSubstituteAnnotations=$nativeSubstituteAnnotations, 383 classLoadHookAnnotations=$classLoadHookAnnotations, 384 keepStaticInitializerAnnotations=$keepStaticInitializerAnnotations, 385 packageRedirects=$packageRedirects, 386 $annotationAllowedClassesFile=$annotationAllowedClassesFile, 387 defaultClassLoadHook=$defaultClassLoadHook, 388 defaultMethodCallHook=$defaultMethodCallHook, 389 intersectStubJars=$intersectStubJars, 390 policyOverrideFile=$policyOverrideFile, 391 defaultPolicy=$defaultPolicy, 392 cleanUpOnError=$cleanUpOnError, 393 enableClassChecker=$enableClassChecker, 394 enablePreTrace=$enablePreTrace, 395 enablePostTrace=$enablePostTrace, 396 enableNonStubMethodCallDetection=$enableNonStubMethodCallDetection, 397 statsFile=$statsFile, 398 apiListFile=$apiListFile, 399 } 400 """.trimIndent() 401 } 402 } 403