[JVM IR] Ensure class names are valid for JVM.

The JVM IR backend was producing class names with "<no name provided>"
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.
This commit is contained in:
Mark Punzalan
2020-01-13 14:44:50 -08:00
committed by Alexander Udalov
parent 049bb54ea6
commit be228d594c
2 changed files with 18 additions and 15 deletions
@@ -35,13 +35,13 @@ import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.java.JvmAnnotationNames import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.name.Name 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.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_SYNTHETIC_ANNOTATION_FQ_NAME 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.TRANSIENT_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.resolve.jvm.annotations.VOLATILE_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.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin 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.serialization.DescriptorSerializer
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Opcodes
@@ -110,6 +110,13 @@ open class ClassCodegen protected constructor(
val superClassInfo = irClass.getSuperClassInfo(typeMapper) val superClassInfo = irClass.getSuperClassInfo(typeMapper)
val signature = getSignature(irClass, type, superClassInfo, 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( visitor.defineClass(
irClass.descriptor.psiElement, irClass.descriptor.psiElement,
state.classFileVersion, state.classFileVersion,
@@ -281,20 +288,14 @@ open class ClassCodegen protected constructor(
companion object { companion object {
fun generate(irClass: IrClass, context: JvmBackendContext) { 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() ClassCodegen(irClass, context).generate()
} }
private fun badClass(irClass: IrClass, mode: ClassBuilderMode) { // Note: This is copied from JvmSimpleNameBacktickChecker.
if (mode.generateBodies) { private val INVALID_CHARS = setOf('.', ';', '[', ']', '/', '<', '>', ':', '\\')
throw IllegalStateException("Generating bad class in ClassBuilderMode = $mode: ${irClass.dump()}")
} private fun JvmClassSignature.hasInvalidName() =
} name.splitToSequence('/').any { identifier -> identifier.any { it in INVALID_CHARS } }
} }
private fun generateDeclaration(declaration: IrDeclaration) { private fun generateDeclaration(declaration: IrDeclaration) {
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.util.isAnonymousObject import org.jetbrains.kotlin.ir.util.isAnonymousObject
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.NameUtils
import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type
@@ -102,10 +103,11 @@ class InventNamesForLocalClasses(private val context: JvmBackendContext) : FileL
val simpleName = declaration.name.asString() val simpleName = declaration.name.asString()
val internalName = when { 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 -> 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. // We save the name of the function to reuse it in the reference to it (produced by the closure conversion) later.
localFunctionNames[(declaration as IrFunction).symbol] = name localFunctionNames[declaration.symbol] = name
} }
} }
enclosingName != null -> "$enclosingName$$simpleName" enclosingName != null -> "$enclosingName$$simpleName"