From c8f070f90d79b8e10acbbe52a2e4f5bd5595d8f4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 11 Apr 2017 16:20:02 +0300 Subject: [PATCH] Automatically exclude interop functions having non-compilable C stubs --- .../native/interop/indexer/MacroConstants.kt | 2 +- .../kotlin/native/interop/indexer/Utils.kt | 112 ++++++++++++++++-- .../native/interop/gen/jvm/StubGenerator.kt | 39 ++++-- .../kotlin/native/interop/gen/jvm/main.kt | 10 +- 4 files changed, 136 insertions(+), 27 deletions(-) diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt index a97d49d26b9..3f78bbd8991 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt @@ -85,7 +85,7 @@ private fun expandMacroAsConstant( reparseWithCodeSnippet(library, translationUnit, sourceFile, name) - if (getCompileErrors(translationUnit).firstOrNull() == null) { + if (!translationUnit.hasCompileErrors()) { return processCodeSnippet(translationUnit, name, typeConverter) } else { return null diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt index de4f1ad8b26..1eaba22531c 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt @@ -93,21 +93,39 @@ internal fun parseTranslationUnit( internal fun NativeLibrary.parse(index: CXIndex, options: Int = 0): CXTranslationUnit = parseTranslationUnit(index, this.createTempSource(), this.compilerArgs, options) -internal fun getCompileErrors(translationUnit: CXTranslationUnit): Sequence { - val numDiagnostics = clang_getNumDiagnostics(translationUnit) - return (0 .. numDiagnostics - 1).asSequence() - .map { index -> clang_getDiagnostic(translationUnit, index)!! } - .filter { - val severity = clang_getDiagnosticSeverity(it) - severity == CXDiagnosticSeverity.CXDiagnostic_Error || - severity == CXDiagnosticSeverity.CXDiagnostic_Fatal - }.map { - clang_formatDiagnostic(it, clang_defaultDiagnosticDisplayOptions()).convertAndDispose() +internal data class Diagnostic(val severity: CXDiagnosticSeverity, val format: String, + val location: CValue) + +internal fun CXTranslationUnit.getDiagnostics(): Sequence { + val numDiagnostics = clang_getNumDiagnostics(this) + return (0 until numDiagnostics).asSequence() + .map { index -> + val diagnostic = clang_getDiagnostic(this, index) + try { + val severity = clang_getDiagnosticSeverity(diagnostic) + + val format = clang_formatDiagnostic(diagnostic, clang_defaultDiagnosticDisplayOptions()) + .convertAndDispose() + + val location = clang_getDiagnosticLocation(diagnostic) + + Diagnostic(severity, format, location) + } finally { + clang_disposeDiagnostic(diagnostic) + } } } +internal fun CXTranslationUnit.getCompileErrors(): Sequence = + getDiagnostics().filter { it.isError() }.map { it.format } + +private fun Diagnostic.isError() = (severity == CXDiagnosticSeverity.CXDiagnostic_Error) || + (severity == CXDiagnosticSeverity.CXDiagnostic_Fatal) + +internal fun CXTranslationUnit.hasCompileErrors() = (this.getCompileErrors().firstOrNull() != null) + internal fun CXTranslationUnit.ensureNoCompileErrors(): CXTranslationUnit { - val firstError = getCompileErrors(this).firstOrNull() ?: return this + val firstError = this.getCompileErrors().firstOrNull() ?: return this throw Error(firstError) } @@ -202,4 +220,76 @@ internal fun NativeLibrary.includesDeclaration(cursor: CValue): Boolea } else { true } +} + +private fun CXTranslationUnit.getErrorLineNumbers(): Sequence = + getDiagnostics().filter { + it.isError() + }.map { + memScoped { + val lineNumberVar = alloc() + clang_getFileLocation(it.location, null, lineNumberVar.ptr, null, null) + lineNumberVar.value + } + } + +/** + * For each list of lines, checks if the code fragment composed from these lines is compilable against given library. + */ +fun List>.mapFragmentIsCompilable(originalLibrary: NativeLibrary): List { + val library = originalLibrary + .copy(compilerArgs = originalLibrary.compilerArgs + "-ferror-limit=0") + .precompileHeaders() + + val indicesOfNonCompilable = mutableSetOf() + + val fragmentsToCheck = this.withIndex().toMutableList() + + val index = clang_createIndex(excludeDeclarationsFromPCH = 1, displayDiagnostics = 0)!! + try { + val sourceFile = library.createTempSource() + val translationUnit = parseTranslationUnit(index, sourceFile, library.compilerArgs, options = 0) + .ensureNoCompileErrors() + + try { + while (fragmentsToCheck.isNotEmpty()) { + // Combine all fragments to be checked in a single file: + sourceFile.bufferedWriter().use { writer -> + writer.appendPreamble(library) + fragmentsToCheck.forEach { + it.value.forEach { + assert(!it.contains('\n')) + writer.appendln(it) + } + } + } + + clang_reparseTranslationUnit(translationUnit, 0, null, 0) + val errorLineNumbers = translationUnit.getErrorLineNumbers().toSet() + + // Retain only those fragments that contain compilation error locations: + var lastLineNumber = library.preambleLines.size + fragmentsToCheck.retainAll { + val firstLineNumber = lastLineNumber + 1 + lastLineNumber += it.value.size + (firstLineNumber .. lastLineNumber).any { it in errorLineNumbers } + } + + if (fragmentsToCheck.isNotEmpty()) { + // The first fragment is now known to be non-compilable. + val firstFragment = fragmentsToCheck.removeAt(0) + indicesOfNonCompilable.add(firstFragment.index) + } + + // The remaining fragments was potentially influenced by the first one, + // and thus require to be checked again. + } + } finally { + clang_disposeTranslationUnit(translationUnit) + } + } finally { + clang_disposeIndex(index) + } + + return this.indices.map { it !in indicesOfNonCompilable } } \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index 3a3b526b91c..18ad3fc8c10 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -768,6 +768,27 @@ class StubGenerator( return StubsFragment(kotlinStubLines, nativeStubLines) } + private fun generateStubsForFunctions(functions: List): List { + val stubs = functions.mapNotNull { + try { + generateStubsForFunction(it) + } catch (e: Throwable) { + log("Warning: cannot generate stubs for function ${it.name}") + null + } + } + + return stubs.map { it.nativeStubLines } + .mapFragmentIsCompilable(libraryForCStubs) + .mapIndexedNotNull { index, stubIsCompilable -> + if (stubIsCompilable) { + stubs[index] + } else { + null + } + } + } + private fun FunctionDecl.generateAsFfiVarargs(): Boolean = (platform == KotlinPlatform.NATIVE && this.isVararg && // Neither takes nor returns structs by value: !this.returnsRecord() && this.parameters.all { it.type.unwrapTypedefs() !is RecordType }) @@ -1224,13 +1245,7 @@ class StubGenerator( private fun generateStubs(): List { val stubs = mutableListOf() - functionsToBind.forEach { - try { - stubs.add(generateStubsForFunction(it)) - } catch (e: Throwable) { - log("Warning: cannot generate stubs for function ${it.name}") - } - } + stubs.addAll(generateStubsForFunctions(functionsToBind)) nativeIndex.macroConstants.forEach { try { @@ -1345,13 +1360,21 @@ class StubGenerator( else -> throw NotImplementedError(kotlinJniBridgeType) } - private val libraryForCStubs = configuration.library.copy( + val libraryForCStubs = configuration.library.copy( includes = mutableListOf().apply { add("stdint.h") if (platform == KotlinPlatform.JVM) { add("jni.h") } addAll(configuration.library.includes) + }, + + compilerArgs = configuration.library.compilerArgs + when (platform) { + KotlinPlatform.JVM -> listOf("", "linux", "darwin").map { + val javaHome = System.getProperty("java.home") + "-I$javaHome/../include/$it" + } + KotlinPlatform.NATIVE -> emptyList() } ) diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index f7a4acab17a..cdfca2c7cd5 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -306,7 +306,7 @@ private fun processLib(konanHome: String, val headerFiles = config.getSpaceSeparated("headers") + additionalHeaders val compilerOpts = config.getSpaceSeparated("compilerOpts") + - defaultOpts + additionalCompilerOpts + defaultOpts + additionalCompilerOpts val compiler = "clang" val language = Language.C val excludeSystemLibs = config.getProperty("excludeSystemLibs")?.toBoolean() ?: false @@ -360,11 +360,7 @@ private fun processLib(konanHome: String, val outOFile = createTempFile(suffix = ".o") - val javaHome = System.getProperty("java.home") - val compilerArgsForJniIncludes = listOf("", "linux", "darwin").map { "-I$javaHome/../include/$it" }.toTypedArray() - - val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *compilerOpts.toTypedArray(), - *compilerArgsForJniIncludes, + val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *gen.libraryForCStubs.compilerArgs.toTypedArray(), "-c", outCFile.path, "-o", outOFile.path) runCmd(compilerCmd, workDir, verbose) @@ -380,7 +376,7 @@ private fun processLib(konanHome: String, } else if (flavor == KotlinPlatform.NATIVE) { val outBcName = libName + ".bc" val outLib = nativeLibsDir + "/" + outBcName - val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *compilerOpts.toTypedArray(), + val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *gen.libraryForCStubs.compilerArgs.toTypedArray(), "-emit-llvm", "-c", outCFile.path, "-o", outLib) runCmd(compilerCmd, workDir, verbose)