From 6cc60ad6a2388b61efc77b1a609f5a1cfef57a4d Mon Sep 17 00:00:00 2001 From: Alexander Shabalin Date: Wed, 20 Jan 2021 19:42:09 +0300 Subject: [PATCH] Support sanitizers in runtime tests (#4622) --- .../kotlin/bitcode/CompileToBitcode.kt | 32 +++++-- .../kotlin/bitcode/CompileToBitcodePlugin.kt | 39 ++++++-- .../kotlin/testing/native/NativeTest.kt | 73 ++++++++++---- kotlin-native/runtime/build.gradle.kts | 35 +++---- .../runtime/src/main/cpp/AllocTest.cpp | 4 +- .../runtime/src/main/cpp/TestSupport.hpp | 3 + .../runtime/src/mm/cpp/ExtraObjectData.cpp | 20 +++- .../konan/target/KonanTargetExtenstions.kt | 11 +++ .../jetbrains/kotlin/konan/target/Linker.kt | 94 +++++++++++++++---- .../kotlin/konan/target/Sanitizer.kt | 11 +++ 10 files changed, 256 insertions(+), 66 deletions(-) create mode 100644 kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Sanitizer.kt diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcode.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcode.kt index 039fb2f39f5..1ef5d444f5b 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcode.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcode.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ExecClang import org.jetbrains.kotlin.konan.target.Family import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.konan.target.SanitizerKind import java.io.File import javax.inject.Inject @@ -19,7 +20,7 @@ open class CompileToBitcode @Inject constructor( val srcRoot: File, val folderName: String, val target: String, - val outputGroup: String + val outputGroup: String, ) : DefaultTask() { enum class Language { @@ -46,9 +47,22 @@ open class CompileToBitcode @Inject constructor( @Input var language = Language.CPP - private val targetDir by lazy { project.buildDir.resolve("bitcode/$outputGroup/$target") } + @Input @Optional + var sanitizer: SanitizerKind? = null - val objDir by lazy { File(targetDir, folderName) } + private val targetDir: File + get() { + val sanitizerSuffix = when (sanitizer) { + null -> "" + SanitizerKind.ADDRESS -> "-asan" + SanitizerKind.THREAD -> "-tsan" + } + return project.buildDir.resolve("bitcode/$outputGroup/$target$sanitizerSuffix") + } + + @get:Input + val objDir + get() = File(targetDir, folderName) private val KonanTarget.isMINGW get() = this.family == Family.MINGW @@ -63,6 +77,11 @@ open class CompileToBitcode @Inject constructor( val compilerFlags: List get() { val commonFlags = listOf("-c", "-emit-llvm") + headersDirs.map { "-I$it" } + val sanitizerFlags = when (sanitizer) { + null -> listOf() + SanitizerKind.ADDRESS -> listOf("-fsanitize=address") + SanitizerKind.THREAD -> listOf("-fsanitize=thread") + } val languageFlags = when (language) { Language.C -> // Used flags provided by original build of allocator C code. @@ -73,7 +92,7 @@ open class CompileToBitcode @Inject constructor( "-Wno-unused-parameter", // False positives with polymorphic functions. "-fPIC".takeIf { !HostManager().targetByName(target).isMINGW }) } - return commonFlags + languageFlags + compilerArgs + return commonFlags + sanitizerFlags + languageFlags + compilerArgs } @get:SkipWhenEmpty @@ -127,8 +146,9 @@ open class CompileToBitcode @Inject constructor( } } - @OutputFile - val outFile = File(targetDir, "${folderName}.bc") + @get:OutputFile + val outFile: File + get() = File(targetDir, "${folderName}.bc") @TaskAction fun compile() { diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcodePlugin.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcodePlugin.kt index 349ce47ea22..86233499e43 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcodePlugin.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/bitcode/CompileToBitcodePlugin.kt @@ -9,6 +9,9 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.BasePlugin import org.jetbrains.kotlin.createCompilationDatabasesFromCompileToBitcodeTasks +import org.jetbrains.kotlin.konan.target.PlatformManager +import org.jetbrains.kotlin.konan.target.SanitizerKind +import org.jetbrains.kotlin.konan.target.supportedSanitizers import java.io.File import javax.inject.Inject @@ -45,20 +48,40 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) { configurationBlock: CompileToBitcode.() -> Unit = {} ) { targetList.get().forEach { targetName -> - project.tasks.register( - "${targetName}${name.snakeCaseToCamelCase().capitalize()}", - CompileToBitcode::class.java, - srcDir, name, targetName, outputGroup - ).configure { - it.group = BasePlugin.BUILD_GROUP - it.description = "Compiles '$name' to bitcode for $targetName" - it.configurationBlock() + val platformManager = project.rootProject.findProperty("platformManager") as PlatformManager + val target = platformManager.targetByName(targetName) + val sanitizers: List = target.supportedSanitizers() + listOf(null) + sanitizers.forEach { sanitizer -> + project.tasks.register( + "${targetName}${name.snakeCaseToCamelCase().capitalize()}${suffixForSanitizer(sanitizer)}", + CompileToBitcode::class.java, + srcDir, name, targetName, outputGroup + ).configure { + it.sanitizer = sanitizer + it.group = BasePlugin.BUILD_GROUP + val sanitizerDescription = when (sanitizer) { + null -> "" + SanitizerKind.ADDRESS -> " with ASAN" + SanitizerKind.THREAD -> " with TSAN" + } + it.description = "Compiles '$name' to bitcode for $targetName$sanitizerDescription" + it.configurationBlock() + } } } } companion object { + private fun String.snakeCaseToCamelCase() = split('_').joinToString(separator = "") { it.capitalize() } + + fun suffixForSanitizer(sanitizer: SanitizerKind?) = + when (sanitizer) { + null -> "" + SanitizerKind.ADDRESS -> "_ASAN" + SanitizerKind.THREAD -> "_TSAN" + } + } } diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/testing/native/NativeTest.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/testing/native/NativeTest.kt index 017500775d9..97ae40ed586 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/testing/native/NativeTest.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/testing/native/NativeTest.kt @@ -13,6 +13,7 @@ import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.tasks.* import org.jetbrains.kotlin.ExecClang import org.jetbrains.kotlin.bitcode.CompileToBitcode +import org.jetbrains.kotlin.bitcode.CompileToBitcodeExtension import org.jetbrains.kotlin.konan.target.* open class CompileNativeTest @Inject constructor( @@ -25,18 +26,29 @@ open class CompileNativeTest @Inject constructor( @Input val clangArgs = mutableListOf() + @Input @Optional + var sanitizer: SanitizerKind? = null + + @Input + private val sanitizerFlags = when (sanitizer) { + null -> listOf() + SanitizerKind.ADDRESS -> listOf("-fsanitize=address") + SanitizerKind.THREAD -> listOf("-fsanitize=thread") + } + @TaskAction fun compile() { val plugin = project.convention.getPlugin(ExecClang::class.java) + val args = clangArgs + sanitizerFlags + listOf(inputFile.absolutePath, "-o", outputFile.absolutePath) if (target.family.isAppleFamily) { plugin.execToolchainClang(target) { it.executable = "clang++" - it.args = clangArgs + listOf(inputFile.absolutePath, "-o", outputFile.absolutePath) + it.args = args } } else { plugin.execBareClang { it.executable = "clang++" - it.args = clangArgs + listOf(inputFile.absolutePath, "-o", outputFile.absolutePath) + it.args = args } } } @@ -92,7 +104,7 @@ open class LinkNativeTest @Inject constructor( @Internal val target: String, @Internal val linkerArgs: List, private val platformManager: PlatformManager, - private val mimallocEnabled: Boolean + private val mimallocEnabled: Boolean, ) : DefaultTask () { companion object { fun create( @@ -103,7 +115,7 @@ open class LinkNativeTest @Inject constructor( target: String, outputFile: File, linkerArgs: List, - mimallocEnabled: Boolean + mimallocEnabled: Boolean, ): LinkNativeTest = project.tasks.create( taskName, LinkNativeTest::class.java, @@ -133,6 +145,9 @@ open class LinkNativeTest @Inject constructor( linkerArgs, mimallocEnabled) } + @Input @Optional + var sanitizer: SanitizerKind? = null + @get:Input val commands: List> get() { @@ -149,7 +164,8 @@ open class LinkNativeTest @Inject constructor( kind = LinkerOutputKind.EXECUTABLE, outputDsymBundle = "", needsProfileLibrary = false, - mimallocEnabled = mimallocEnabled + mimallocEnabled = mimallocEnabled, + sanitizer = sanitizer, ).map { it.argsWithExecutable } } @@ -163,11 +179,11 @@ open class LinkNativeTest @Inject constructor( } } -fun createTestTask( +private fun createTestTask( project: Project, testName: String, - testTaskName: String, testedTaskNames: List, + sanitizer: SanitizerKind?, configureCompileToBitcode: CompileToBitcode.() -> Unit = {}, ): Task { val platformManager = project.rootProject.findProperty("platformManager") as PlatformManager @@ -188,6 +204,7 @@ fun createTestTask( "${it.folderName}Tests", target, "test" ).apply { + this.sanitizer = sanitizer excludeFiles = emptyList() includeFiles = listOf("**/*Test.cpp", "**/*Test.mm") dependsOn(it) @@ -201,18 +218,19 @@ fun createTestTask( else task } + // TODO: Consider using sanitized versions. val testFrameworkTasks = listOf( project.tasks.getByName("${target}Googletest") as CompileToBitcode, project.tasks.getByName("${target}Googlemock") as CompileToBitcode ) - val testSupportTask = project.tasks.getByName("${target}TestSupport") as CompileToBitcode + val testSupportTask = project.tasks.getByName("${target}TestSupport${CompileToBitcodeExtension.suffixForSanitizer(sanitizer)}") as CompileToBitcode // TODO: It may make sense to merge llvm-link, compile and link to a single task. val llvmLinkTask = project.tasks.create( - "${testTaskName}LlvmLink", + "${testName}LlvmLink", LlvmLinkNativeTest::class.java, - testTaskName, target, testSupportTask.outFile + testName, target, testSupportTask.outFile ).apply { val tasksToLink = (compileToBitcodeTasks + testedTasks + testFrameworkTasks) inputFiles = project.files(tasksToLink.map { it.outFile }) @@ -222,11 +240,12 @@ fun createTestTask( val clangFlags = platformManager.platform(konanTarget).configurables as ClangFlags val compileTask = project.tasks.create( - "${testTaskName}Compile", + "${testName}Compile", CompileNativeTest::class.java, llvmLinkTask.outputFile, konanTarget, ).apply { + this.sanitizer = sanitizer dependsOn(llvmLinkTask) clangArgs.addAll(clangFlags.clangFlags) clangArgs.addAll(clangFlags.clangNooptFlags) @@ -236,19 +255,20 @@ fun createTestTask( val linkTask = LinkNativeTest.create( project, platformManager, - "${testTaskName}Link", + "${testName}Link${CompileToBitcodeExtension.suffixForSanitizer(sanitizer)}", listOf(compileTask.outputFile), target, - testTaskName, - mimallocEnabled + testName, + mimallocEnabled, ).apply { + this.sanitizer = sanitizer dependsOn(compileTask) } - return project.tasks.create(testTaskName, Exec::class.java).apply { + return project.tasks.create(testName, Exec::class.java).apply { dependsOn(linkTask) - workingDir = project.buildDir.resolve("testReports/$testTaskName") + workingDir = project.buildDir.resolve("testReports/$testName") val xmlReport = workingDir.resolve("report.xml") executable(linkTask.outputFile) args("--gtest_output=xml:${xmlReport.absoluteFile}") @@ -267,3 +287,24 @@ fun createTestTask( } } } + +// TODO: These tests should be created by `CompileToBitcodeExtension` +fun createTestTasks( + project: Project, + targetName: String, + testTaskName: String, + testedTaskNames: List, + configureCompileToBitcode: CompileToBitcode.() -> Unit = {}, +): List { + val platformManager = project.rootProject.findProperty("platformManager") as PlatformManager + val target = platformManager.targetByName(targetName) + val sanitizers: List = target.supportedSanitizers() + listOf(null) + return sanitizers.map { sanitizer -> + val suffix = CompileToBitcodeExtension.suffixForSanitizer(sanitizer) + val name = testTaskName + suffix + val testedNames = testedTaskNames.map { + it + suffix + } + createTestTask(project, name, testedNames, sanitizer, configureCompileToBitcode) + } +} diff --git a/kotlin-native/runtime/build.gradle.kts b/kotlin-native/runtime/build.gradle.kts index 1d7ffe5b032..bfdedff0eb5 100644 --- a/kotlin-native/runtime/build.gradle.kts +++ b/kotlin-native/runtime/build.gradle.kts @@ -5,6 +5,8 @@ import org.jetbrains.kotlin.* import org.jetbrains.kotlin.testing.native.* import org.jetbrains.kotlin.bitcode.CompileToBitcode +import org.jetbrains.kotlin.bitcode.CompileToBitcodeExtension +import org.jetbrains.kotlin.konan.target.* plugins { id("compile-to-bitcode") @@ -42,6 +44,7 @@ bitcode { "${target}ExperimentalMemoryManager" ) includeRuntime() + // TODO: Should depend on the sanitizer. linkerArgs.add(project.file("../common/build/bitcode/main/$target/hash.bc").path) } @@ -107,9 +110,11 @@ bitcode { } targetList.forEach { targetName -> - createTestTask( + val allTests = mutableListOf() + + allTests.addAll(createTestTasks( project, - "StdAlloc", + targetName, "${targetName}StdAllocRuntimeTests", listOf( "${targetName}Runtime", @@ -120,11 +125,11 @@ targetList.forEach { targetName -> ) ) { includeRuntime() - } + }) - createTestTask( + allTests.addAll(createTestTasks( project, - "Mimalloc", + targetName, "${targetName}MimallocRuntimeTests", listOf( "${targetName}Runtime", @@ -136,11 +141,11 @@ targetList.forEach { targetName -> ) ) { includeRuntime() - } + }) - createTestTask( + allTests.addAll(createTestTasks( project, - "ExperimentalMMMimalloc", + targetName, "${targetName}ExperimentalMMMimallocRuntimeTests", listOf( "${targetName}Runtime", @@ -151,11 +156,11 @@ targetList.forEach { targetName -> ) ) { includeRuntime() - } + }) - createTestTask( + allTests.addAll(createTestTasks( project, - "ExperimentalMMStdAlloc", + targetName, "${targetName}ExperimentalMMStdAllocRuntimeTests", listOf( "${targetName}Runtime", @@ -165,13 +170,11 @@ targetList.forEach { targetName -> ) ) { includeRuntime() - } + }) + // TODO: This "all tests" tasks should be provided by `CompileToBitcodeExtension` tasks.register("${targetName}RuntimeTests") { - dependsOn("${targetName}StdAllocRuntimeTests") - dependsOn("${targetName}MimallocRuntimeTests") - dependsOn("${targetName}ExperimentalMMStdAllocRuntimeTests") - dependsOn("${targetName}ExperimentalMMMimallocRuntimeTests") + dependsOn(allTests) } } diff --git a/kotlin-native/runtime/src/main/cpp/AllocTest.cpp b/kotlin-native/runtime/src/main/cpp/AllocTest.cpp index 7ba0fb9be48..e9b58dc6c59 100644 --- a/kotlin-native/runtime/src/main/cpp/AllocTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/AllocTest.cpp @@ -112,7 +112,9 @@ TEST_F(KonanAllocatorAwareTest, PlacementAllocated) { TEST_F(KonanAllocatorAwareTest, PlacementConstructedArray) { constexpr size_t kCount = 5; - std::array buffer; + // TODO: Consider removing support for placement new[] altogether, since there's no + // portable way to know needed storage size ahead of time. + alignas(A) std::array buffer; A* as = new (buffer.data()) A[kCount]; std::vector actual; diff --git a/kotlin-native/runtime/src/main/cpp/TestSupport.hpp b/kotlin-native/runtime/src/main/cpp/TestSupport.hpp index 521a7d93951..f2e7b2ade92 100644 --- a/kotlin-native/runtime/src/main/cpp/TestSupport.hpp +++ b/kotlin-native/runtime/src/main/cpp/TestSupport.hpp @@ -8,6 +8,9 @@ namespace kotlin { #if KONAN_WINDOWS // TODO: Figure out why creating many threads on windows is so slow. constexpr int kDefaultThreadCount = 10; +#elif __has_feature(thread_sanitizer) +// TSAN has a huge overhead. +constexpr int kDefaultThreadCount = 10; #else constexpr int kDefaultThreadCount = 100; #endif diff --git a/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.cpp b/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.cpp index 4fc9b1a4914..61959c6a32c 100644 --- a/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.cpp +++ b/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.cpp @@ -14,9 +14,27 @@ using namespace kotlin; +namespace { + +template +ALWAYS_INLINE T UnsafeRead(T* location) noexcept { +#if __has_feature(thread_sanitizer) + // Make TSAN think that this load is fine. + return __atomic_load_n(location, __ATOMIC_ACQUIRE); +#else + return *location; +#endif +} + +} // namespace + // static mm::ExtraObjectData& mm::ExtraObjectData::Install(ObjHeader* object) noexcept { - TypeInfo* typeInfo = object->typeInfoOrMeta_; + // TODO: Consider extracting initialization scheme with speculative load. + // `object->typeInfoOrMeta_` is assigned at most once. If we read some old value (i.e. not a meta object), + // we will fail at CAS below. If we read the new value, we will immediately return it. + TypeInfo* typeInfo = UnsafeRead(&object->typeInfoOrMeta_); + if (auto* metaObject = ObjHeader::AsMetaObject(typeInfo)) { return mm::ExtraObjectData::FromMetaObjHeader(metaObject); } diff --git a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTargetExtenstions.kt b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTargetExtenstions.kt index 1d6a0aefbe7..3320f1397f5 100644 --- a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTargetExtenstions.kt +++ b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTargetExtenstions.kt @@ -30,3 +30,14 @@ fun KonanTarget.supportsThreads(): Boolean = is KonanTarget.ZEPHYR -> false else -> true } + +fun KonanTarget.supportedSanitizers(): List = + when(this) { + is KonanTarget.LINUX_X64 -> listOf(SanitizerKind.ADDRESS) + is KonanTarget.MACOS_X64 -> listOf(SanitizerKind.THREAD) + // TODO: Enable ASAN on macOS. Currently there's an incompatibility between clang frontend version and clang_rt.asan version. + // TODO: Enable TSAN on linux. Currently there's a link error between clang_rt.tsan and libstdc++. + // TODO: Consider supporting mingw. + // TODO: Support macOS arm64 + else -> listOf() + } diff --git a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Linker.kt b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Linker.kt index aac5339b34e..81c0fc403c8 100644 --- a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Linker.kt +++ b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Linker.kt @@ -76,7 +76,8 @@ abstract class LinkerFlags(val configurables: Configurables) { libraries: List, linkerArgs: List, optimize: Boolean, debug: Boolean, kind: LinkerOutputKind, outputDsymBundle: String, - needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List + needsProfileLibrary: Boolean, mimallocEnabled: Boolean, + sanitizer: SanitizerKind? = null): List /** * Returns list of commands that link object files into a single one. @@ -93,7 +94,7 @@ abstract class LinkerFlags(val configurables: Configurables) { return libraries } - protected open fun provideCompilerRtLibrary(libraryName: String): String? { + protected open fun provideCompilerRtLibrary(libraryName: String, isDynamic: Boolean = false): String? { System.err.println("Can't provide $libraryName.") return null } @@ -123,7 +124,11 @@ class AndroidLinker(targetProperties: AndroidConfigurables) libraries: List, linkerArgs: List, optimize: Boolean, debug: Boolean, kind: LinkerOutputKind, outputDsymBundle: String, - needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List { + needsProfileLibrary: Boolean, mimallocEnabled: Boolean, + sanitizer: SanitizerKind?): List { + require(sanitizer == null) { + "Sanitizers are unsupported" + } if (kind == LinkerOutputKind.STATIC_LIBRARY) return staticGnuArCommands(ar, executable, objectFiles, libraries) @@ -170,7 +175,12 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables) get() = this == KonanTarget.TVOS_X64 || this == KonanTarget.IOS_X64 || this == KonanTarget.WATCHOS_X86 || this == KonanTarget.WATCHOS_X64 - override fun provideCompilerRtLibrary(libraryName: String): String? { + private val compilerRtDir: String? by lazy { + val dir = File("$absoluteTargetToolchain/usr/lib/clang/").listFiles.firstOrNull()?.absolutePath + if (dir != null) "$dir/lib/darwin/" else null + } + + override fun provideCompilerRtLibrary(libraryName: String, isDynamic: Boolean): String? { val prefix = when (target.family) { Family.IOS -> "ios" Family.WATCHOS -> "watchos" @@ -184,10 +194,11 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables) "" } - val dir = File("$absoluteTargetToolchain/usr/lib/clang/").listFiles.firstOrNull()?.absolutePath + val dir = compilerRtDir val mangledLibraryName = if (libraryName.isEmpty()) "" else "${libraryName}_" + val extension = if (isDynamic) "_dynamic.dylib" else ".a" - return if (dir != null) "$dir/lib/darwin/libclang_rt.$mangledLibraryName$prefix$suffix.a" else null + return if (dir != null) "$dir/libclang_rt.$mangledLibraryName$prefix$suffix$extension" else null } private val osVersionMinFlags: List by lazy { @@ -210,14 +221,19 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables) libraries: List, linkerArgs: List, optimize: Boolean, debug: Boolean, kind: LinkerOutputKind, outputDsymBundle: String, - needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List { - if (kind == LinkerOutputKind.STATIC_LIBRARY) + needsProfileLibrary: Boolean, mimallocEnabled: Boolean, + sanitizer: SanitizerKind?): List { + if (kind == LinkerOutputKind.STATIC_LIBRARY) { + require(sanitizer == null) { + "Sanitizers are unsupported" + } return listOf(Command(libtool).apply { +"-static" +listOf("-o", executable) +objectFiles +libraries }) + } val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY val result = mutableListOf() @@ -237,7 +253,12 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables) if (needsProfileLibrary) +profileLibrary!! +libraries +linkerArgs - +rpath(dynamic) + +rpath(dynamic, sanitizer) + when (sanitizer) { + null -> {} + SanitizerKind.ADDRESS -> +provideCompilerRtLibrary("asan", isDynamic=true)!! + SanitizerKind.THREAD -> +provideCompilerRtLibrary("tsan", isDynamic=true)!! + } } // TODO: revise debug information handling. @@ -255,7 +276,7 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables) provideCompilerRtLibrary("") } - private fun rpath(dynamic: Boolean): List = listOfNotNull( + private fun rpath(dynamic: Boolean, sanitizer: SanitizerKind?): List = listOfNotNull( when (target.family) { Family.OSX -> "@executable_path/../Frameworks" Family.IOS, @@ -263,7 +284,8 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables) Family.TVOS -> "@executable_path/Frameworks" else -> error(target) }, - "@loader_path/Frameworks".takeIf { dynamic } + "@loader_path/Frameworks".takeIf { dynamic }, + compilerRtDir.takeIf { sanitizer != null }, ).flatMap { listOf("-rpath", it) } fun dsymUtilCommand(executable: ExecutableFile, outputDsymBundle: String) = @@ -319,7 +341,10 @@ class GccBasedLinker(targetProperties: GccConfigurables) private val specificLibs = abiSpecificLibraries.map { "-L${absoluteTargetSysRoot}/$it" } - override fun provideCompilerRtLibrary(libraryName: String): String? { + override fun provideCompilerRtLibrary(libraryName: String, isDynamic: Boolean): String? { + require(!isDynamic) { + "Dynamic compiler rt librares are unsupported" + } val targetSuffix = when (target) { KonanTarget.LINUX_X64 -> "x86_64" else -> error("$target is not supported.") @@ -334,9 +359,14 @@ class GccBasedLinker(targetProperties: GccConfigurables) libraries: List, linkerArgs: List, optimize: Boolean, debug: Boolean, kind: LinkerOutputKind, outputDsymBundle: String, - needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List { - if (kind == LinkerOutputKind.STATIC_LIBRARY) + needsProfileLibrary: Boolean, mimallocEnabled: Boolean, + sanitizer: SanitizerKind?): List { + if (kind == LinkerOutputKind.STATIC_LIBRARY) { + require(sanitizer == null) { + "Sanitizers are unsupported" + } return staticGnuArCommands(ar, executable, objectFiles, libraries) + } val isMips = target == KonanTarget.LINUX_MIPS32 || target == KonanTarget.LINUX_MIPSEL32 val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY val crtPrefix = "$absoluteTargetSysRoot/$crtFilesLocation" @@ -373,6 +403,19 @@ class GccBasedLinker(targetProperties: GccConfigurables) +linkerGccFlags +if (dynamic) "$libGcc/crtendS.o" else "$libGcc/crtend.o" +"$crtPrefix/crtn.o" + when (sanitizer) { + null -> {} + SanitizerKind.ADDRESS -> { + +"-lrt" + +provideCompilerRtLibrary("asan")!! + +provideCompilerRtLibrary("asan_cxx")!! + } + SanitizerKind.THREAD -> { + +"-lrt" + +provideCompilerRtLibrary("tsan")!! + +provideCompilerRtLibrary("tsan_cxx")!! + } + } }) } } @@ -387,7 +430,10 @@ class MingwLinker(targetProperties: MingwConfigurables) override fun filterStaticLibraries(binaries: List) = binaries.filter { it.isWindowsStaticLib || it.isUnixStaticLib } - override fun provideCompilerRtLibrary(libraryName: String): String? { + override fun provideCompilerRtLibrary(libraryName: String, isDynamic: Boolean): String? { + require(!isDynamic) { + "Dynamic compiler rt librares are unsupported" + } val targetSuffix = when (target) { KonanTarget.MINGW_X64 -> "x86_64" else -> error("$target is not supported.") @@ -400,7 +446,11 @@ class MingwLinker(targetProperties: MingwConfigurables) libraries: List, linkerArgs: List, optimize: Boolean, debug: Boolean, kind: LinkerOutputKind, outputDsymBundle: String, - needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List { + needsProfileLibrary: Boolean, mimallocEnabled: Boolean, + sanitizer: SanitizerKind?): List { + require(sanitizer == null) { + "Sanitizers are unsupported" + } if (kind == LinkerOutputKind.STATIC_LIBRARY) return staticGnuArCommands(ar, executable, objectFiles, libraries) @@ -437,8 +487,12 @@ class WasmLinker(targetProperties: WasmConfigurables) libraries: List, linkerArgs: List, optimize: Boolean, debug: Boolean, kind: LinkerOutputKind, outputDsymBundle: String, - needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List { + needsProfileLibrary: Boolean, mimallocEnabled: Boolean, + sanitizer: SanitizerKind?): List { if (kind != LinkerOutputKind.EXECUTABLE) throw Error("Unsupported linker output kind") + require(sanitizer == null) { + "Sanitizers are unsupported" + } val linkage = Command("$llvmBin/wasm-ld").apply { +objectFiles @@ -489,8 +543,12 @@ open class ZephyrLinker(targetProperties: ZephyrConfigurables) libraries: List, linkerArgs: List, optimize: Boolean, debug: Boolean, kind: LinkerOutputKind, outputDsymBundle: String, - needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List { + needsProfileLibrary: Boolean, mimallocEnabled: Boolean, + sanitizer: SanitizerKind?): List { if (kind != LinkerOutputKind.EXECUTABLE) throw Error("Unsupported linker output kind: $kind") + require(sanitizer == null) { + "Sanitizers are unsupported" + } return listOf(Command(linker).apply { +listOf("-r", "--gc-sections", "--entry", "main") +listOf("-o", executable) diff --git a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Sanitizer.kt b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Sanitizer.kt new file mode 100644 index 00000000000..3ab83ccfaed --- /dev/null +++ b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/Sanitizer.kt @@ -0,0 +1,11 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.konan.target + +enum class SanitizerKind { + ADDRESS, + THREAD, +}