Try our best to fix up the truncated class graph exposed by the Kotlin Android extensions plugin

This commit is contained in:
Chris Smowton
2022-02-24 16:59:30 +00:00
committed by Ian Lynagh
parent 2d1308980a
commit 110a2c7b87
4 changed files with 88 additions and 19 deletions

View File

@@ -9,7 +9,7 @@ import java.util.ArrayList
import java.util.HashSet
import java.util.zip.GZIPOutputStream
class ExternalClassExtractor(val logger: FileLogger, val invocationTrapFile: String, val sourceFilePath: String, val primitiveTypeMapping: PrimitiveTypeMapping, val pluginContext: IrPluginContext, val genericSpecialisationsExtracted: MutableSet<String>) {
class ExternalClassExtractor(val logger: FileLogger, val invocationTrapFile: String, val sourceFilePath: String, val primitiveTypeMapping: PrimitiveTypeMapping, val pluginContext: IrPluginContext, val globalExtensionState: KotlinExtractorGlobalState) {
val externalClassesDone = HashSet<IrClass>()
val externalClassWorkList = ArrayList<IrClass>()
@@ -47,7 +47,7 @@ class ExternalClassExtractor(val logger: FileLogger, val invocationTrapFile: Str
// file information
val ftw = tw.makeFileTrapWriter(binaryPath, true)
val fileExtractor = KotlinFileExtractor(logger, ftw, binaryPath, manager, this, primitiveTypeMapping, pluginContext, genericSpecialisationsExtracted)
val fileExtractor = KotlinFileExtractor(logger, ftw, binaryPath, manager, this, primitiveTypeMapping, pluginContext, globalExtensionState)
// Populate a location and compilation-unit package for the file. This is similar to
// the beginning of `KotlinFileExtractor.extractFileContents` but without an `IrFile`

View File

@@ -58,12 +58,12 @@ class KotlinExtractorExtension(
FileUtil.logger = logger
val srcDir = File(System.getenv("CODEQL_EXTRACTOR_JAVA_SOURCE_ARCHIVE_DIR").takeUnless { it.isNullOrEmpty() } ?: "kotlin-extractor/src")
srcDir.mkdirs()
val genericSpecialisationsExtracted = HashSet<String>()
val globalExtensionState = KotlinExtractorGlobalState()
moduleFragment.files.mapIndexed { index: Int, file: IrFile ->
val fileExtractionProblems = FileExtractionProblems(invocationExtractionProblems)
val fileTrapWriter = tw.makeSourceFileTrapWriter(file, true)
fileTrapWriter.writeCompilation_compiling_files(compilation, index, fileTrapWriter.fileId)
doFile(fileExtractionProblems, invocationTrapFile, fileTrapWriter, checkTrapIdentical, logCounter, trapDir, srcDir, file, primitiveTypeMapping, pluginContext, genericSpecialisationsExtracted)
doFile(fileExtractionProblems, invocationTrapFile, fileTrapWriter, checkTrapIdentical, logCounter, trapDir, srcDir, file, primitiveTypeMapping, pluginContext, globalExtensionState)
fileTrapWriter.writeCompilation_compiling_files_completed(compilation, index, fileExtractionProblems.extractionResult())
}
logger.printLimitedDiagnosticCounts()
@@ -78,6 +78,13 @@ class KotlinExtractorExtension(
}
}
class KotlinExtractorGlobalState {
val genericSpecialisationsExtracted = HashSet<String>()
val syntheticToRealClassMap = HashMap<IrClass, IrClass?>()
val syntheticToRealFunctionMap = HashMap<IrSimpleFunction, IrSimpleFunction?>()
val syntheticToRealFieldMap = HashMap<IrField, IrField?>()
}
/*
ExtractionProblems distinguish 2 kinds of problems:
* Recoverable problems: e.g. if we check something that we expect to be
@@ -146,7 +153,7 @@ fun doFile(fileExtractionProblems: FileExtractionProblems,
srcFile: IrFile,
primitiveTypeMapping: PrimitiveTypeMapping,
pluginContext: IrPluginContext,
genericSpecialisationsExtracted: MutableSet<String>) {
globalExtensionState: KotlinExtractorGlobalState) {
val srcFilePath = srcFile.path
val logger = FileLogger(logCounter, fileTrapWriter)
logger.info("Extracting file $srcFilePath")
@@ -178,8 +185,8 @@ fun doFile(fileExtractionProblems: FileExtractionProblems,
// Now elevate to a SourceFileTrapWriter, and populate the
// file information
val sftw = tw.makeSourceFileTrapWriter(srcFile, true)
val externalClassExtractor = ExternalClassExtractor(logger, invocationTrapFile, srcFilePath, primitiveTypeMapping, pluginContext, genericSpecialisationsExtracted)
val fileExtractor = KotlinFileExtractor(logger, sftw, srcFilePath, null, externalClassExtractor, primitiveTypeMapping, pluginContext, genericSpecialisationsExtracted)
val externalClassExtractor = ExternalClassExtractor(logger, invocationTrapFile, srcFilePath, primitiveTypeMapping, pluginContext, globalExtensionState)
val fileExtractor = KotlinFileExtractor(logger, sftw, srcFilePath, null, externalClassExtractor, primitiveTypeMapping, pluginContext, globalExtensionState)
fileExtractor.extractFileContents(srcFile, sftw.fileId)
externalClassExtractor.extractExternalClasses()

View File

@@ -38,8 +38,8 @@ open class KotlinFileExtractor(
externalClassExtractor: ExternalClassExtractor,
primitiveTypeMapping: PrimitiveTypeMapping,
pluginContext: IrPluginContext,
genericSpecialisationsExtracted: MutableSet<String>
): KotlinUsesExtractor(logger, tw, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, genericSpecialisationsExtracted) {
globalExtensionState: KotlinExtractorGlobalState
): KotlinUsesExtractor(logger, tw, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, globalExtensionState) {
inline fun <T> with(kind: String, element: IrElement, f: () -> T): T {
try {
@@ -1233,7 +1233,7 @@ open class KotlinFileExtractor(
fun extractCall(c: IrCall, callable: Label<out DbCallable>, stmtExprParent: StmtExprParent) {
with("call", c) {
val target = c.symbol.owner
val target = tryReplaceAndroidSyntheticFunction(c.symbol.owner)
// The vast majority of types of call want an expr context, so make one available lazily:
val exprParent by lazy {
@@ -1680,7 +1680,7 @@ open class KotlinFileExtractor(
extractExpressionExpr(c.getValueArgument(0)!!, callable, id, 1, enclosingStmt)
}
else -> {
extractMethodAccess(c.symbol.owner, true, true)
extractMethodAccess(target, true, true)
}
}
}
@@ -2283,7 +2283,7 @@ open class KotlinFileExtractor(
tw.writeHasLocation(id, locId)
tw.writeCallableEnclosingExpr(id, callable)
tw.writeStatementEnclosingExpr(id, exprParent.enclosingStmt)
val owner = e.symbol.owner
val owner = tryReplaceAndroidSyntheticField(e.symbol.owner)
val vId = useField(owner)
tw.writeVariableBinding(id, vId)
tw.writeStatementEnclosingExpr(id, exprParent.enclosingStmt)
@@ -2358,12 +2358,13 @@ open class KotlinFileExtractor(
}
is IrSetField -> {
tw.writeExprs_assignexpr(id, type.javaResult.id, exprParent.parent, exprParent.idx)
val lhsType = useType(e.symbol.owner.type)
val realField = tryReplaceAndroidSyntheticField(e.symbol.owner)
val lhsType = useType(realField.type)
tw.writeExprs_varaccess(lhsId, lhsType.javaResult.id, id, 0)
tw.writeExprsKotlinType(lhsId, lhsType.kotlinResult.id)
// TODO: location, enclosing callable?
tw.writeStatementEnclosingExpr(id, exprParent.enclosingStmt)
val vId = useField(e.symbol.owner)
val vId = useField(realField)
tw.writeVariableBinding(lhsId, vId)
extractExpressionExpr(e.value, callable, id, 1, exprParent.enclosingStmt)

View File

@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -31,7 +32,7 @@ open class KotlinUsesExtractor(
val externalClassExtractor: ExternalClassExtractor,
val primitiveTypeMapping: PrimitiveTypeMapping,
val pluginContext: IrPluginContext,
val genericSpecialisationsExtracted: MutableSet<String>
val globalExtensionState: KotlinExtractorGlobalState
) {
fun usePackage(pkg: String): Label<out DbPackage> {
return extractPackage(pkg)
@@ -123,7 +124,7 @@ open class KotlinUsesExtractor(
val filePath = getIrClassBinaryPath(cls)
val newTrapWriter = tw.makeFileTrapWriter(filePath, false)
val newLogger = FileLogger(logger.logCounter, newTrapWriter)
return KotlinFileExtractor(newLogger, newTrapWriter, filePath, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, genericSpecialisationsExtracted)
return KotlinFileExtractor(newLogger, newTrapWriter, filePath, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, globalExtensionState)
}
if (this is KotlinFileExtractor && this.filePath == clsFile.path) {
@@ -132,7 +133,7 @@ open class KotlinUsesExtractor(
val newTrapWriter = tw.makeSourceFileTrapWriter(clsFile, false)
val newLogger = FileLogger(logger.logCounter, newTrapWriter)
return KotlinFileExtractor(newLogger, newTrapWriter, clsFile.path, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, genericSpecialisationsExtracted)
return KotlinFileExtractor(newLogger, newTrapWriter, clsFile.path, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, globalExtensionState)
}
// The Kotlin compiler internal representation of Outer<T>.Inner<S>.InnerInner<R> is InnerInner<R, S, T>. This function returns just `R`.
@@ -233,9 +234,69 @@ open class KotlinUsesExtractor(
externalClassExtractor.extractLater(c)
}
fun tryReplaceAndroidSyntheticClass(c: IrClass): IrClass {
// The Android Kotlin Extensions Gradle plugin introduces synthetic functions, fields and classes. The most
// obvious signature is that they lack any supertype information even though they are not root classes.
// If possible, replace them by a real version of the same class.
if (c.superTypes.isNotEmpty() ||
c.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB ||
c.hasEqualFqName(FqName("java.lang.Object")))
return c
return globalExtensionState.syntheticToRealClassMap.getOrPut(c) {
val result = c.fqNameWhenAvailable?.let {
pluginContext.referenceClass(it)?.owner
}
if (result == null) {
logger.warn("Failed to replace synthetic class ${c.name}")
} else {
logger.info("Replaced synthetic class ${c.name} with its real equivalent")
}
result
} ?: c
}
fun tryReplaceAndroidSyntheticFunction(f: IrSimpleFunction): IrSimpleFunction {
val parentClass = f.parent as? IrClass ?: return f
val replacementClass = tryReplaceAndroidSyntheticClass(parentClass)
if (replacementClass === parentClass)
return f
return globalExtensionState.syntheticToRealFunctionMap.getOrPut(f) {
val result = replacementClass.declarations.find { replacementDecl ->
replacementDecl is IrSimpleFunction && replacementDecl.name == f.name && replacementDecl.valueParameters.zip(f.valueParameters).all {
it.first.type == it.second.type
}
} as IrSimpleFunction?
if (result == null) {
logger.warn("Failed to replace synthetic class function ${f.name}")
} else {
logger.info("Replaced synthetic class function ${f.name} with its real equivalent")
}
result
} ?: f
}
fun tryReplaceAndroidSyntheticField(f: IrField): IrField {
val parentClass = f.parent as? IrClass ?: return f
val replacementClass = tryReplaceAndroidSyntheticClass(parentClass)
if (replacementClass === parentClass)
return f
return globalExtensionState.syntheticToRealFieldMap.getOrPut(f) {
val result = replacementClass.declarations.find { replacementDecl ->
replacementDecl is IrField && replacementDecl.name == f.name
} as IrField?
if (result == null) {
logger.warn("Failed to replace synthetic class field ${f.name}")
} else {
logger.info("Replaced synthetic class field ${f.name} with its real equivalent")
}
result
} ?: f
}
// `typeArgs` can be null to describe a raw generic type.
// For non-generic types it will be zero-length list.
fun addClassLabel(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?, inReceiverContext: Boolean = false): TypeResult<DbClassorinterface> {
fun addClassLabel(cBeforeReplacement: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?, inReceiverContext: Boolean = false): TypeResult<DbClassorinterface> {
val c = tryReplaceAndroidSyntheticClass(cBeforeReplacement)
val classLabelResult = getClassLabel(c, argsIncludingOuterClasses)
var instanceSeenBefore = true
@@ -255,7 +316,7 @@ open class KotlinUsesExtractor(
extractorWithCSource.extractClassInstance(c, argsIncludingOuterClasses)
}
if (inReceiverContext && genericSpecialisationsExtracted.add(classLabelResult.classLabel)) {
if (inReceiverContext && globalExtensionState.genericSpecialisationsExtracted.add(classLabelResult.classLabel)) {
val supertypeMode = if (argsIncludingOuterClasses == null) ExtractSupertypesMode.Raw else ExtractSupertypesMode.Specialised(argsIncludingOuterClasses)
extractorWithCSource.extractClassSupertypes(c, classLabel, supertypeMode, true)
extractorWithCSource.extractMemberPrototypes(c, argsIncludingOuterClasses, classLabel)