Skip to content

Relocation breaks inner classes which contains '$' in name #792

@Him188

Description

@Him188

The following Kotlin code is compiled with an inner class named $serializer by the Kotlin compiler.

package foo
@Serializable // This annotation instructs the compiler to generate an extra inner class named `$serializer`
enum class MyEnum {
    VALUE1
}

The qualified name of the inner class will be foo.MyEnum$$serializer.

If relocation is enabled, shadow runs RelocatorRemapper on the class. In org.objectweb.asm.commons.Remapper, it maps inner class names by substring-ing the name after the last $. So the resultant qualified name for the example will be foo.MyEnum$serializer (Note one $ is missing). This breaks Kotlin's class metadata and will cause AssertionError in runtime. See relevant issues like mamoe/mirai#2230.


Here's the org.objectweb.asm.commons.Remapper.mapInnerClassName for your reference.

  /**
   * Maps an inner class name to its new name. The default implementation of this method provides a
   * strategy that will work for inner classes produced by Java, but not necessarily other
   * languages. Subclasses can override.
   *
   * @param name the fully-qualified internal name of the inner class.
   * @param ownerName the internal name of the owner class of the inner class.
   * @param innerName the internal name of the inner class.
   * @return the new inner name of the inner class.
   */
  public String mapInnerClassName(
      final String name, final String ownerName, final String innerName) {
    final String remappedInnerName = this.mapType(name);
    if (remappedInnerName.contains("$")) {
      int index = remappedInnerName.lastIndexOf('$') + 1;
      while (index < remappedInnerName.length()
          && Character.isDigit(remappedInnerName.charAt(index))) {
        index++;
      }
      return remappedInnerName.substring(index);
    } else {
      return innerName;
    }
  }

Shadow Version

7.1.2

Gradle Version

7.3.0

Expected Behavior

Code runs successfully.

Actual Behavior

Gradle Build Script(s)

Content of Shadow JAR (jar tf <jar file> - post link to GIST if too long)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions