From be228d594c7d4d5962a5fc20c0bcf0fb20326c76 Mon Sep 17 00:00:00 2001 From: Mark Punzalan Date: Mon, 13 Jan 2020 14:44:50 -0800 Subject: [PATCH] [JVM IR] Ensure class names are valid for JVM. The JVM IR backend was producing class names with "" for unnamed functions (i.e., `fun(...) {}`). This produces class files with names invalid in Windows. This change adds validation of names using the same set of invalid characters used in the frontend, which should ensure class file names are valid. --- .../backend/jvm/codegen/ClassCodegen.kt | 25 ++++++++++--------- .../jvm/lower/InventNamesForLocalClasses.kt | 8 +++--- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt index 6d2a8f09959..c4d8e6dade6 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt @@ -35,13 +35,13 @@ import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.JvmAnnotationNames import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_SYNTHETIC_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.jvm.annotations.TRANSIENT_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.jvm.annotations.VOLATILE_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature import org.jetbrains.kotlin.serialization.DescriptorSerializer import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.org.objectweb.asm.Opcodes @@ -110,6 +110,13 @@ open class ClassCodegen protected constructor( val superClassInfo = irClass.getSuperClassInfo(typeMapper) val signature = getSignature(irClass, type, superClassInfo, typeMapper) + // Ensure that the backend only produces class names that would be valid in the frontend for JVM. + // hasInvalidName() uses a set copied from JvmSimpleNameBacktickChecker, which in turn references JVMS 4.7.9.1: + // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.9.1. + if (context.state.classBuilderMode.generateBodies && signature.hasInvalidName()) { + throw IllegalStateException("Generating class with invalid name '${type.className}': ${irClass.dump()}") + } + visitor.defineClass( irClass.descriptor.psiElement, state.classFileVersion, @@ -281,20 +288,14 @@ open class ClassCodegen protected constructor( companion object { fun generate(irClass: IrClass, context: JvmBackendContext) { - val state = context.state - - if (irClass.name == SpecialNames.NO_NAME_PROVIDED) { - badClass(irClass, state.classBuilderMode) - } - ClassCodegen(irClass, context).generate() } - private fun badClass(irClass: IrClass, mode: ClassBuilderMode) { - if (mode.generateBodies) { - throw IllegalStateException("Generating bad class in ClassBuilderMode = $mode: ${irClass.dump()}") - } - } + // Note: This is copied from JvmSimpleNameBacktickChecker. + private val INVALID_CHARS = setOf('.', ';', '[', ']', '/', '<', '>', ':', '\\') + + private fun JvmClassSignature.hasInvalidName() = + name.splitToSequence('/').any { identifier -> identifier.any { it in INVALID_CHARS } } } private fun generateDeclaration(declaration: IrDeclaration) { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/InventNamesForLocalClasses.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/InventNamesForLocalClasses.kt index 92126bb5437..eccf77f616c 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/InventNamesForLocalClasses.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/InventNamesForLocalClasses.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.util.isAnonymousObject import org.jetbrains.kotlin.ir.visitors.IrElementVisitor import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.NameUtils import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.org.objectweb.asm.Type @@ -102,10 +103,11 @@ class InventNamesForLocalClasses(private val context: JvmBackendContext) : FileL val simpleName = declaration.name.asString() val internalName = when { - declaration.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA -> { + declaration is IrFunction && !NameUtils.hasName(declaration.name) -> { + // Replace "unnamed" function names with indices. inventName(null, data).also { name -> - // We save the name of the lambda to reuse it in the reference to it (produced by the closure conversion) later. - localFunctionNames[(declaration as IrFunction).symbol] = name + // We save the name of the function to reuse it in the reference to it (produced by the closure conversion) later. + localFunctionNames[declaration.symbol] = name } } enclosingName != null -> "$enclosingName$$simpleName"