Java ASM CheckClassAdapter на ClassNode

Адаптер ASM CheckClassAdapter очень полезен для получения полезных выходных данных журнала о причинах сбоя класса в случае ошибок проверки, однако его нельзя использовать, если кадры карты стека недействительны. CheckClassAdapter нельзя использовать в этих ситуациях, потому что он принимает ClassReader, а это означает, что для его использования для проверки моего преобразованного ClassNode я должен сделать следующее:

ClassWriter verifyWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
classNode.accept(verifyWriter);
CheckClassAdapter.verify(new ClassReader(verifyWriter.toByteArray()), true, printDumpLogFile);

Когда ClassWriter дает сбой из-за того, что байт-код моего ClassNode странно недействителен, выходные данные, переданные в CheckClassAdapter, недействительны и очень искажены с множеством пустых кадров и NOP. Есть ли способ передать ClassNode в CheckClassAdapter, избегая использования ClassWriter, который ограничивает его полезность?


person Nirvana    schedule 15.01.2021    source источник
comment
Не могли бы вы поделиться одним или несколькими проблемными файлами класса? Я хотел бы попробовать кое-что.   -  person kriegaex    schedule 16.01.2021
comment
Кстати, как вы создаете classNode?   -  person kriegaex    schedule 16.01.2021


Ответы (2)


Если у вас есть способ получить байт-код входного класса вместо ClassNode, вы можете использовать что-то вроде этого:

CheckClassAdapter.verify(
  bytes,
  true,
  new PrintWriter(System.out)
);

Например, если байт-код уже хранится в файле класса, вы можете сделать это:

CheckClassAdapter.verify(
  new ClassReader(new FileInputStream(args[0])),
  true,
  new PrintWriter(System.out)
);

Но на самом деле, если кадры карты стека недействительны, CheckClassAdapter может ничего не сообщать. По крайней мере у меня есть файлы классов, в которых его нет. Кстати, то же самое относится и к org.apache.bcel.verifier.Verifier. Тем не менее, JVM выдает VerifyError, т. е. загрузка класса в настоящую JVM является окончательной проверкой.

person kriegaex    schedule 16.01.2021
comment
Я должен был быть более явным, читаемый файл класса преобразуется во время выполнения, и добавляется верификатор, когда этот преобразованный класс не может быть записан. Верификатор помогает отлаживать проблемы с кодом. У меня уже есть дамп кода проблемного сгенерированного класса с помощью TraceClassVisitor, но использование верификатора делает гораздо более ясным, где проблема, чем полагаться на ошибки JVM. - person Nirvana; 16.01.2021

Я нашел решение, создав класс, расширяющий CheckClassAdapter, и добавив метод с параметром ClassNode. Прочитав исходный код CheckClassAdapter, я обнаружил, что ClassReader все равно посещает узел класса.

public static void verify(
  final ClassReader classReader,
  final ClassLoader loader,
  final boolean printResults,
  final PrintWriter printWriter) {
ClassNode classNode = new ClassNode();
classReader.accept(
    new CheckClassAdapter(/*latest*/ Opcodes.ASM10_EXPERIMENTAL, classNode, false) {},
    ClassReader.SKIP_DEBUG);

Как видите, ASM мог бы предоставить методу только ввод данных в ClassNode, но по какой-то причине этого не сделал.

Вот код класса, который расширяет CheckClassAdapter, добавляя эту функциональность:

public class CheckClassAdapterClassNode extends CheckClassAdapter {

public CheckClassAdapterClassNode(ClassVisitor classVisitor) {
    super(classVisitor);
}

/**
 * Checks the given class.
 *
 * @param classNode the class to be checked.
 * @param loader a <code>ClassLoader</code> which will be used to load referenced classes. May be
 *     {@literal null}.
 * @param printResults whether to print the results of the bytecode verification.
 * @param printWriter where the results (or the stack trace in case of error) must be printed.
 */
public static void verify(
        final ClassNode classNode,
        final ClassLoader loader,
        final boolean printResults,
        final PrintWriter printWriter) {

    Type syperType = classNode.superName == null ? null : Type.getObjectType(classNode.superName);
    List<MethodNode> methods = classNode.methods;

    List<Type> interfaces = new ArrayList<>();
    for (String interfaceName : classNode.interfaces) {
        interfaces.add(Type.getObjectType(interfaceName));
    }

    for (MethodNode method : methods) {
        SimpleVerifier verifier =
                new SimpleVerifier(
                        Type.getObjectType(classNode.name),
                        syperType,
                        interfaces,
                        (classNode.access & Opcodes.ACC_INTERFACE) != 0);
        Analyzer<BasicValue> analyzer = new Analyzer<>(verifier);
        if (loader != null) {
            verifier.setClassLoader(loader);
        }
        try {
            analyzer.analyze(classNode.name, method);
        } catch (AnalyzerException e) {
            e.printStackTrace(printWriter);
        }
        if (printResults) {
            printAnalyzerResult(method, analyzer, printWriter);
        }
    }
    printWriter.flush();
}

static void printAnalyzerResult(
        final MethodNode method, final Analyzer<BasicValue> analyzer, final PrintWriter printWriter) {
    Textifier textifier = new Textifier();
    TraceMethodVisitor traceMethodVisitor = new TraceMethodVisitor(textifier);

    printWriter.println(method.name + method.desc);
    for (int i = 0; i < method.instructions.size(); ++i) {
        if (method.instructions.get(i) == null) continue;
        method.instructions.get(i).accept(traceMethodVisitor);

        StringBuilder stringBuilder = new StringBuilder();
        Frame<BasicValue> frame = analyzer.getFrames()[i];
        if (frame == null) {
            stringBuilder.append('?');
        } else {
            for (int j = 0; j < frame.getLocals(); ++j) {
                stringBuilder.append(getUnqualifiedName(frame.getLocal(j).toString())).append(' ');
            }
            stringBuilder.append(" : ");
            for (int j = 0; j < frame.getStackSize(); ++j) {
                stringBuilder.append(getUnqualifiedName(frame.getStack(j).toString())).append(' ');
            }
        }
        while (stringBuilder.length() < method.maxStack + method.maxLocals + 1) {
            stringBuilder.append(' ');
        }
        printWriter.print(Integer.toString(i + 100000).substring(1));
        printWriter.print(
                " " + stringBuilder + " : " + textifier.text.get(textifier.text.size() - 1));
    }
    for (TryCatchBlockNode tryCatchBlock : method.tryCatchBlocks) {
        tryCatchBlock.accept(traceMethodVisitor);
        printWriter.print(" " + textifier.text.get(textifier.text.size() - 1));
    }
    printWriter.println();
}

private static String getUnqualifiedName(final String name) {
    int lastSlashIndex = name.lastIndexOf('/');
    if (lastSlashIndex == -1) {
        return name;
    } else {
        int endIndex = name.length();
        if (name.charAt(endIndex - 1) == ';') {
            endIndex--;
        }
        return name.substring(lastSlashIndex + 1, endIndex);
    }
}

}

Скорее всего, я создам задачу в репозитории ASM и добавлю запрос на извлечение, чтобы добавить это.

person Nirvana    schedule 16.01.2021
comment
Я не анализировал ваш код, но я рад, что вы смогли решить свою проблему. Когда у вас будет достаточно очков репутации, вы также можете принять свой ответ, чтобы закрыть вопрос. Также отредактируйте свой ответ, добавив ссылку на проблему ASM и / или PR для дальнейшего использования. Спасибо. - person kriegaex; 17.01.2021
comment
Мне также было бы интересно увидеть MCVE и диагностический вывод для вашего файла класса с поддельной картой стека. Есть что-нибудь, что вы можете распознать? В своем случае не вижу ничего подозрительного, вроде бы все хорошо, а на самом деле это не так. Однако я не использую TraceMethodVisitor в дополнение к CheckClassAdapter. Я не профессионал ASM, поэтому я хотел бы кое-что узнать из этой проблемы. - person kriegaex; 17.01.2021
comment
Нет необходимости создавать подкласс для копирования метода static. Кроме того, для того, что вы хотите проверить, будет достаточно простого classNode.accept(new CheckClassAdapter(null));. - person Holger; 18.01.2021