Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[core] Javassist sets the generic type of the class but the decompilation is lost #2272

Closed
Milory opened this issue Sep 11, 2024 · 4 comments

Comments

@Milory
Copy link

Milory commented Sep 11, 2024

Issue details

platform

  1. macos 14.6.1 (23G93)
  2. jdk (Zulu 8.78.0.19-CA-macos-aarch64)
  3. Decompilation tools (jadx-gui 1.5.0)
  4. javassist (3.30.2-GA)

test code

    public static void main(String[] args) throws Exception {
        // 获取默认的类池
        ClassPool pool = ClassPool.getDefault();

        // 创建类 Wrapper<T>
        CtClass ctClass = pool.makeClass("com.Wrapper");

        // 获取类文件的常量池
        ConstPool constPool = ctClass.getClassFile().getConstPool();

        // 定义泛型参数 T
        TypeParameter[] typeParams = new TypeParameter[] { new TypeParameter("T") };

        // 实现 Comparable<Wrapper<T>>
        ClassType[] interfaces = {
                new ClassType("java.lang.Comparable", new SignatureAttribute.TypeArgument[]{
                        new SignatureAttribute.TypeArgument(new ClassType("com.Wrapper", new SignatureAttribute.TypeArgument[]{
                                new SignatureAttribute.TypeArgument(new ClassType("T"))
                        }))
                })
        };

        // 构建类签名
        ClassSignature classSignature = new ClassSignature(typeParams, ClassType.OBJECT, interfaces);

        // 将类签名应用到类文件中
        SignatureAttribute signatureAttribute = new SignatureAttribute(constPool, classSignature.encode());
        ctClass.getClassFile().addAttribute(signatureAttribute);

        // 添加字段 T value
        CtField valueField = new CtField(pool.get("java.lang.Object"), "value", ctClass);
        valueField.setGenericSignature("TT;");
        ctClass.addField(valueField);

        // 将生成的类写入文件
        ctClass.writeFile(SAVE_PATH);
    }

Decompile and view source code

package com;

/* loaded from: Wrapper.class */
public class Wrapper<T> {
    T value;
}

After decompiling through jadx, it was found that the implemented interfaces content was lost。
However, when the code is decompiled through the javap command, the code can be displayed normally

$ javap Wrapper 
Compiled from "Wrapper.java"
public class com.Wrapper<T> implements java.lang.Comparable<com.Wrapper<T>> {
  T value;
  public com.Wrapper();
}

Relevant log output or stacktrace

No response

Provide sample and class/method full name

No response

Jadx version

1.5.0

@Milory Milory added bug Core Issues in jadx-core module labels Sep 11, 2024
@skylot
Copy link
Owner

skylot commented Sep 11, 2024

@Milory please share your sample

@Milory
Copy link
Author

Milory commented Sep 12, 2024

test.zip

The MainTest.java file in the compressed file is a complete example, but it first depends on Lombok and Javassist, for example:

        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.30.2-GA</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.32</version>
        </dependency>

The Wrapper.class file in the compressed package is the generated target file。

@skylot
Copy link
Owner

skylot commented Sep 12, 2024

Hm, @Milory looks like you add Comparable interface only to signature and not to actual interfaces list:

ctClass.setInterfaces(new CtClass[]{pool.get("java.lang.Comparable")});

Because of this, class doesn't implement Comparable and check at runtime:

Object obj = ctClass.toClass().getConstructor().newInstance();
System.out.println("Is instance of Comparable: " + (obj instanceof Comparable));
Comparable cmp = (Comparable) obj;

print Is instance of Comparable: false and next cast fail with ClassCastException.

It is weird that javap use info from signature without checks.
But jadx don't trust signatures because it is just an optional metadata.
I will add another check to add a warning about incorrect signature for this case.

@Milory
Copy link
Author

Milory commented Sep 13, 2024

I'm very sorry, this is my problem. I suddenly realized that there was no implementation interface for the Wrapper set in the code example. After adding this part of the code, everything became normal。

       // 获取默认的类池
        ClassPool pool = ClassPool.getDefault();

        // 创建类 Wrapper<T>
        CtClass ctClass = pool.makeClass("com.Wrapper");
        CtClass comparableCtClass = pool.get("java.lang.Comparable");

        // 设置 Wrapper 实现 Comparable
        ctClass.addInterface(comparableCtClass);

        // 获取类文件的常量池
        ConstPool constPool = ctClass.getClassFile().getConstPool();

        // 定义泛型参数 T
        SignatureAttribute.TypeParameter[] typeParams = new SignatureAttribute.TypeParameter[] { new SignatureAttribute.TypeParameter("T") };

        // 实现 Comparable<Wrapper<T>>
        SignatureAttribute.ClassType[] interfaces = {
                new SignatureAttribute.ClassType("java.lang.Comparable", new SignatureAttribute.TypeArgument[]{
                        new SignatureAttribute.TypeArgument(new SignatureAttribute.ClassType("com.Wrapper", new SignatureAttribute.TypeArgument[]{
                                new SignatureAttribute.TypeArgument(new SignatureAttribute.ClassType("T"))
                        }))
                })
        };

        // 构建类签名
        SignatureAttribute.ClassSignature classSignature = new SignatureAttribute.ClassSignature(typeParams, SignatureAttribute.ClassType.OBJECT, interfaces);

        // 将类签名应用到类文件中
        SignatureAttribute signatureAttribute = new SignatureAttribute(constPool, classSignature.encode());
        ctClass.getClassFile().addAttribute(signatureAttribute);

        // 添加字段 T value
        CtField valueField = new CtField(pool.get("java.lang.Object"), "value", ctClass);
        valueField.setGenericSignature("TT;");
        ctClass.addField(valueField);

        CtMethod method = CtMethod.make("public int compareTo(com.Wrapper o) { return 0; }", ctClass);
        method.setGenericSignature("(Lcom/Wrapper<TT;>;)I");
        ctClass.addMethod(method);

        // 将生成的类写入文件
//        ctClass.writeFile(SAVE_PATH);

        Object obj = ctClass.toClass().newInstance();
        System.out.println("Is instance of Comparable: " + (obj instanceof java.lang.Comparable));
image

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests

2 participants