Fix race condition by delegating to instance in "winning" class loader

The language parser registry is shared across unique class loaders, but
the registered parser exists in a single class loader. Work around this
by delegating to an instance of the detector in whichever class loader
"won" by registering the parser first.

Also adds missing visit call for method declarations.

Fixes: 300097739
Test: ./gradlew :room:integration-tests:room-testapp:lintDebug
Change-Id: Ideafe62421843e52576e54cd7aff3d08d07f5240
diff --git a/lint-checks/src/main/java/androidx/build/lint/aidl/AidlDefinitionDetector.kt b/lint-checks/src/main/java/androidx/build/lint/aidl/AidlDefinitionDetector.kt
index b1e086f..d219896 100644
--- a/lint-checks/src/main/java/androidx/build/lint/aidl/AidlDefinitionDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/aidl/AidlDefinitionDetector.kt
@@ -31,33 +31,51 @@
 import com.android.tools.lint.detector.api.Scope
 import com.intellij.core.CoreFileTypeRegistry
 import com.intellij.lang.LanguageParserDefinitions
+import com.intellij.openapi.fileTypes.LanguageFileType
 import com.intellij.openapi.fileTypes.UnknownFileType
 import com.intellij.openapi.vfs.local.CoreLocalFileSystem
 import com.intellij.psi.PsiManager
 import com.intellij.psi.SingleRootFileViewProvider
 import java.io.File
+import java.lang.reflect.Method
 
 /**
  * Abstract class for detectors running against AIDL definitions (e.g. .aidl files).
  */
 abstract class AidlDefinitionDetector : Detector(), OtherFileScanner {
 
-    override fun getApplicableFiles() = Scope.OTHER_SCOPE
+    private var delegate: Any? = null
+    private var methodRun: Method? = null
 
     override fun beforeCheckEachProject(context: Context) {
-        val aidlFileType = AidlFileType.INSTANCE
+        val detectorClass = this.javaClass
+        var aidlFileType: LanguageFileType = AidlFileType.INSTANCE
+        var useMethodRun: Method? = null
+        var useDelegate: Any? = null
 
-        // We only need to register the language parser once per daemon process...
+        // Register the parser, if it hasn't already been registered by another instance.
         LanguageParserDefinitions.INSTANCE.apply {
             synchronized(this) {
-                val existingParsers = forLanguage(aidlFileType.language)
-                if (existingParsers == null) {
+                val existingParser = forLanguage(aidlFileType.language)
+                if (existingParser == null) {
                     addExplicitExtension(aidlFileType.language, AidlParserDefinition())
+                } else {
+                    // An instance of this class already registered the parser, potentially from a
+                    // different ClassLoader. Avoid conflicts (see b/300097739) by delegating to a
+                    // detector instance loaded from the registered parser's ClassLoader.
+                    val classLoader = existingParser.javaClass.classLoader
+                    classLoader.loadClass(detectorClass.name).let {
+                        useDelegate = it.getConstructor().newInstance()
+                        useMethodRun = it.getMethod("runInternal", Context::class.java)
+                    }
+                    classLoader.loadClass(AidlFileType::class.qualifiedName).let {
+                        aidlFileType = it.getField("INSTANCE").get(null) as LanguageFileType
+                    }
                 }
             }
         }
 
-        // ...but we need to register the file type every time we run Gradle.
+        // Register the file type, if it hasn't already ben registered by another instance.
         (CoreFileTypeRegistry.getInstance() as CoreFileTypeRegistry).apply {
             synchronized(this) {
                 val existingFileType = getFileTypeByExtension(aidlFileType.defaultExtension)
@@ -66,9 +84,24 @@
                 }
             }
         }
+
+        delegate = useDelegate
+        methodRun = useMethodRun
     }
 
+    override fun getApplicableFiles() = Scope.OTHER_SCOPE
+
     override fun run(context: Context) {
+        // Call the delegate's run method, if we have one.
+        methodRun?.invoke(delegate, context) ?: runInternal(context)
+    }
+
+    /**
+     * Actual implementation of [run]. This must be Java-visible since it will be called via
+     * reflection from what is _technically_ a different class.
+     */
+    @Suppress("MemberVisibilityCanBePrivate")
+    fun runInternal(context: Context) {
         if (context.file.extension == AidlFileType.DEFAULT_ASSOCIATED_EXTENSION) {
             ioFileToAidlFile(context, context.file)
                 .allAidlDeclarations
@@ -84,6 +117,8 @@
                 visitAidlInterfaceDeclaration(context, aidlDeclaration)
             is AidlParcelableDeclaration ->
                 visitAidlParcelableDeclaration(context, aidlDeclaration)
+            is AidlMethodDeclaration ->
+                visitAidlMethodDeclaration(context, aidlDeclaration)
             is AidlUnionDeclaration -> {
                 listOf(
                     aidlDeclaration.interfaceDeclarationList,
diff --git a/lint-checks/src/test/java/androidx/build/lint/AbstractLintDetectorTest.kt b/lint-checks/src/test/java/androidx/build/lint/AbstractLintDetectorTest.kt
index 0ed4dd8..3cd0e5c 100644
--- a/lint-checks/src/test/java/androidx/build/lint/AbstractLintDetectorTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/AbstractLintDetectorTest.kt
@@ -73,6 +73,7 @@
                 *stubs,
                 *files
             )
+            .allowDuplicates()
             .run()
     }
 }