mirror of
https://github.com/github/codeql.git
synced 2025-12-18 01:33:15 +01:00
Try our best to fix up the truncated class graph exposed by the Kotlin Android extensions plugin
This commit is contained in:
committed by
Ian Lynagh
parent
2d1308980a
commit
110a2c7b87
@@ -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`
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user