Skip to content

Stub generator emits assembly-qualified .NET names in Java class descriptors #735

Description

@klangille

I hit another problem with converted dlls. I was able to fix it locally with ChatGPT and wanted to share the fix here.

Problem

When IKVM generates stubs for converted assemblies, class-valued annotation metadata can contain a .NET assembly-qualified type name instead of a valid Java descriptor.

For example, metadata intended to contain:

Ljava/lang/Thread;

may instead contain:

Ljava/lang/Thread, IKVM.Java, Version=..., Culture=..., PublicKeyToken=...;

The comma and assembly metadata make the generated descriptor invalid.

Cause

StubGenerator encodes class-valued annotation elements directly from the stored type string. Some of these strings originate from Type.AssemblyQualifiedName, so they include .NET assembly metadata.

Required changes

In src/IKVM.Runtime/StubGen/StubGenerator.cs, add a helper that detects assembly-qualified descriptors, resolves the underlying IKVM Java class, and returns its canonical Java descriptor:

static string DecodeTypeName(string typeName)
{
#if !FIRST_PASS && !EXPORTER
    int index = typeName.IndexOf(',');
    if (index > 0)
    {
        // Resolve an assembly-qualified .NET type name back to
        // its canonical Java class descriptor.
        try
        {
            typeName =
                "L" +
                java.lang.Class
                    .forName(typeName.Substring(1, typeName.Length - 2).Replace('/', '.'))
                    .getName()
                    .Replace('.', '/') +
                ";";
        }
        catch
        {
            // Preserve the original value if it cannot be resolved.
        }
    }
#endif

    return typeName;
}

For FIRST_PASS and EXPORTER builds, the helper can remain a no-op if required by the existing conditional compilation structure:

#if FIRST_PASS || EXPORTER
static string DecodeTypeName(string typeName) => typeName;
#endif

Pass type descriptors through DecodeTypeName before adding them to the constant pool.

For object-array annotation data, update the class, enum, and nested-annotation cases:

encoder.Enum(
    builder.Constants.GetOrAddUtf8(DecodeTypeName((string)v[1])),
    builder.Constants.GetOrAddUtf8((string)v[2]));
encoder.Class(
    builder.Constants.GetOrAddUtf8(DecodeTypeName((string)v[1])));
e.Annotation(
    builder.Constants.GetOrAddUtf8(DecodeTypeName((string)v[1])),
    e2 =>
    {
        // Existing element encoding
    });

Also update the CustomAttributeTypedArgument TAG_CLASS path:

encoder.Class(
    builder.Constants.GetOrAddUtf8(
        DecodeTypeName((string)data[1].Value)));

Expected result

Assembly-qualified .NET names are normalized before being written to Java annotation metadata. Generated output contains valid descriptors such as Ljava/lang/Thread;, without the trailing comma, assembly name, version, culture, or public-key token.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions