[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.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) {
@@ -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"