KT-56369: [cinterop] Throw an error for an unexpected module imports
Module imports require explicit '-fmodules' compiler flag. If the indexer meets a module import but there is no the flag then it throws an error.
This commit is contained in:
committed by
Space Team
parent
8e71488adf
commit
4ad5be35f4
+29
-6
@@ -1,7 +1,7 @@
|
|||||||
package org.jetbrains.kotlin.native.interop.indexer
|
package org.jetbrains.kotlin.native.interop.indexer
|
||||||
|
|
||||||
import clang.*
|
import clang.*
|
||||||
import kotlinx.cinterop.*
|
import kotlinx.cinterop.readValue
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
|
||||||
data class ModulesInfo(val topLevelHeaders: List<IncludeInfo>, val ownHeaders: Set<String>, val modules: List<String>)
|
data class ModulesInfo(val topLevelHeaders: List<IncludeInfo>, val ownHeaders: Set<String>, val modules: List<String>)
|
||||||
@@ -9,23 +9,29 @@ data class ModulesInfo(val topLevelHeaders: List<IncludeInfo>, val ownHeaders: S
|
|||||||
fun getModulesInfo(compilation: Compilation, modules: List<String>): ModulesInfo {
|
fun getModulesInfo(compilation: Compilation, modules: List<String>): ModulesInfo {
|
||||||
if (modules.isEmpty()) return ModulesInfo(emptyList(), emptySet(), emptyList())
|
if (modules.isEmpty()) return ModulesInfo(emptyList(), emptySet(), emptyList())
|
||||||
|
|
||||||
|
val areModulesEnabled = compilation.compilerArgs.contains("-fmodules")
|
||||||
withIndex(excludeDeclarationsFromPCH = false) { index ->
|
withIndex(excludeDeclarationsFromPCH = false) { index ->
|
||||||
ModularCompilation(compilation).use {
|
ModularCompilation(compilation).use {
|
||||||
val modulesASTFiles = getModulesASTFiles(index, it, modules)
|
val modulesASTFiles = getModulesASTFiles(index, it, modules)
|
||||||
return buildModulesInfo(index, modules, modulesASTFiles)
|
return buildModulesInfo(index, modules, modulesASTFiles, areModulesEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class IncludeInfo(val headerPath: String, val moduleName: String?)
|
data class IncludeInfo(val headerPath: String, val moduleName: String?)
|
||||||
|
|
||||||
private fun buildModulesInfo(index: CXIndex, modules: List<String>, modulesASTFiles: List<String>): ModulesInfo {
|
private fun buildModulesInfo(
|
||||||
|
index: CXIndex,
|
||||||
|
modules: List<String>,
|
||||||
|
modulesASTFiles: List<String>,
|
||||||
|
areModulesEnabled: Boolean
|
||||||
|
): ModulesInfo {
|
||||||
val ownHeaders = mutableSetOf<String>()
|
val ownHeaders = mutableSetOf<String>()
|
||||||
val topLevelHeaders = linkedSetOf<IncludeInfo>()
|
val topLevelHeaders = linkedSetOf<IncludeInfo>()
|
||||||
modulesASTFiles.forEach {
|
modulesASTFiles.forEach {
|
||||||
val moduleTranslationUnit = clang_createTranslationUnit(index, it)!!
|
val moduleTranslationUnit = clang_createTranslationUnit(index, it)!!
|
||||||
try {
|
try {
|
||||||
val modulesHeaders = getModulesHeaders(index, moduleTranslationUnit, modules.toSet(), topLevelHeaders)
|
val modulesHeaders = getModulesHeaders(index, moduleTranslationUnit, modules.toSet(), topLevelHeaders, areModulesEnabled)
|
||||||
modulesHeaders.mapTo(ownHeaders) { it.canonicalPath }
|
modulesHeaders.mapTo(ownHeaders) { it.canonicalPath }
|
||||||
} finally {
|
} finally {
|
||||||
clang_disposeTranslationUnit(moduleTranslationUnit)
|
clang_disposeTranslationUnit(moduleTranslationUnit)
|
||||||
@@ -35,7 +41,7 @@ private fun buildModulesInfo(index: CXIndex, modules: List<String>, modulesASTFi
|
|||||||
return ModulesInfo(topLevelHeaders.toList(), ownHeaders, modules)
|
return ModulesInfo(topLevelHeaders.toList(), ownHeaders, modules)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal open class ModularCompilation(compilation: Compilation): Compilation by compilation, Disposable {
|
internal open class ModularCompilation(compilation: Compilation) : Compilation by compilation, Disposable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val moduleCacheFlag = "-fmodules-cache-path="
|
private const val moduleCacheFlag = "-fmodules-cache-path="
|
||||||
@@ -91,12 +97,28 @@ private fun getModulesHeaders(
|
|||||||
index: CXIndex,
|
index: CXIndex,
|
||||||
translationUnit: CXTranslationUnit,
|
translationUnit: CXTranslationUnit,
|
||||||
modules: Set<String>,
|
modules: Set<String>,
|
||||||
topLevelHeaders: LinkedHashSet<IncludeInfo>
|
topLevelHeaders: LinkedHashSet<IncludeInfo>,
|
||||||
|
areModulesEnabled: Boolean
|
||||||
): Set<CXFile> {
|
): Set<CXFile> {
|
||||||
val nonModularIncludes = mutableMapOf<CXFile, MutableSet<CXFile>>()
|
val nonModularIncludes = mutableMapOf<CXFile, MutableSet<CXFile>>()
|
||||||
val result = mutableSetOf<CXFile>()
|
val result = mutableSetOf<CXFile>()
|
||||||
|
val errors = mutableListOf<Throwable>()
|
||||||
|
|
||||||
indexTranslationUnit(index, translationUnit, 0, object : Indexer {
|
indexTranslationUnit(index, translationUnit, 0, object : Indexer {
|
||||||
|
override fun importedASTFile(info: CXIdxImportedASTFileInfo) {
|
||||||
|
val isModuleImport = info.isImplicit == 0
|
||||||
|
if (isModuleImport && !areModulesEnabled) {
|
||||||
|
val name = clang_Module_getFullName(info.module).convertAndDispose()
|
||||||
|
val headerPath = clang_indexLoc_getCXSourceLocation(info.loc.readValue()).getContainingFile()?.canonicalPath
|
||||||
|
val message = buildString {
|
||||||
|
appendLine("use of '@import' when modules are disabled")
|
||||||
|
appendLine("header: '$headerPath'")
|
||||||
|
appendLine("module name: '$name'")
|
||||||
|
}
|
||||||
|
errors.add(Error(message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun ppIncludedFile(info: CXIdxIncludedFileInfo) {
|
override fun ppIncludedFile(info: CXIdxIncludedFileInfo) {
|
||||||
val file = info.file!!
|
val file = info.file!!
|
||||||
val includer = clang_indexLoc_getCXSourceLocation(info.hashLoc.readValue()).getContainingFile()
|
val includer = clang_indexLoc_getCXSourceLocation(info.hashLoc.readValue()).getContainingFile()
|
||||||
@@ -122,6 +144,7 @@ private fun getModulesHeaders(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (errors.isNotEmpty()) throw errors.first()
|
||||||
|
|
||||||
// There are cases when non-modular includes should also be considered as a part of module. For example:
|
// There are cases when non-modular includes should also be considered as a part of module. For example:
|
||||||
// 1. Some module maps are broken,
|
// 1. Some module maps are broken,
|
||||||
|
|||||||
+33
@@ -98,6 +98,39 @@ class ModuleTests : IndexerTests() {
|
|||||||
assertEquals(listOf(fooH.absolutePath), modulesInfo.topLevelHeaders.canonicalize())
|
assertEquals(listOf(fooH.absolutePath), modulesInfo.topLevelHeaders.canonicalize())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testModuleImport() {
|
||||||
|
val files = TempFiles("testModuleImport")
|
||||||
|
val fooH = files.file("Foo.h", "@import Bar;")
|
||||||
|
files.file("Bar.h", "")
|
||||||
|
files.file("module.modulemap", """
|
||||||
|
module Foo {
|
||||||
|
header "Foo.h"
|
||||||
|
}
|
||||||
|
module Bar {
|
||||||
|
header "Bar.h"
|
||||||
|
}
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
//without '-fmodules'
|
||||||
|
val error = assertFails {
|
||||||
|
getModulesInfo(compilation("-I${files.directory}"), listOf("Foo"))
|
||||||
|
}
|
||||||
|
assertContains(
|
||||||
|
error.message.orEmpty(),
|
||||||
|
"""
|
||||||
|
use of '@import' when modules are disabled
|
||||||
|
header: '${fooH.absolutePath}'
|
||||||
|
module name: 'Bar'
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
//with '-fmodules'
|
||||||
|
val modulesInfo = getModulesInfo(compilation("-I${files.directory}", "-fmodules"), listOf("Foo"))
|
||||||
|
assertEquals(setOf(fooH.absolutePath), modulesInfo.ownHeaders)
|
||||||
|
assertEquals(listOf(fooH.absolutePath), modulesInfo.topLevelHeaders.canonicalize())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testMissingModule() {
|
fun testMissingModule() {
|
||||||
val files = TempFiles("testMissingModule")
|
val files = TempFiles("testMissingModule")
|
||||||
|
|||||||
Reference in New Issue
Block a user