Pull to refresh

Comments 13

Я не хочу убивать всё удовольствие от дотошно проделанной работы, и допускаю, что эта статья написана в больше образовательных, чем практичных целях, но не могу не задать вопрос:


А почему нельзя было сделать новый проект с единственной зависимостью — ошибочным джарником — и единственным классом — изменённой версией файла?


Или у класса было слишком много зависимостей? Но на это не похоже. Или это нельзя было юридически? Но код же в любом случае был декомпилен и модифицирован.

Юридически код в общем-то под LGPL с открытыми исходниками. И всё бы понятно, академический интерес и всё такое, но настораживает отсутствие в списке вариантов действий — просто скачать исходники и подправить нужный файлик...

Это было бы не так интересно лично для меня. :)


Если уж совсем придираться, то список вариантов действий далеко не исчерпывающий. Можете считать, что вариант "0", который про pull-request в репозиторий, является подмножеством "скачать исходники и подправить нужный файлик", но с дополнительным шагом "не только подправить, но ещё и поделиться".


Да и статьи бы в таком случае не было бы.

Да, я думал об этом, что можно просто целиком и полностью перекомпилировать ошибочный класс, но это было бы не столь интересно. И мне хотелось чуть поглубже нырнуть в JVM в хаотичном поиске знаний, как первопричина.


Так что да, это больше образовательная статья в духе буханки и троллейбуса. Плюс самая малость пользы.

Кстати всё это можно попробовать проделать и через asm:

Заголовок спойлера
public class BadClass {
    public String badCheck(String value) {
        System.out.println("123");
        return value;
    }
}

public class GoodClass {
    public static String goodCheck(String value) {
        System.out.println("321 " + value);
        return Objects.requireNonNullElse(value, "hello!");
    }
}

public class Application {
    public static void main(String[] args) throws Exception {
        ClassReader reader = new ClassReader(Application.class.getResourceAsStream("BadClass.class"));
        ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
        reader.accept(new YourClassVisitor(writer), ClassReader.EXPAND_FRAMES);

        FileOutputStream out = new FileOutputStream(Application.class.getResource("BadClass.class").getFile());
        out.write(writer.toByteArray());
        out.close();

        new BadClass().badCheck(" test ");
    }

    public static class BadClassVisitor extends ClassVisitor {
        public YourClassVisitor(ClassVisitor cv) {
            super(Opcodes.ASM9, cv);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            if (name.equals("badCheck")) {
                return new BadMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions));
            }
            return super.visitMethod(access, name, desc, signature, exceptions);
        }

        private static class BadMethodVisitor extends MethodVisitor {
            public YourMethodVisitor(MethodVisitor mv) {
                super(Opcodes.ASM9, mv);
            }

            @Override
            public void visitCode() {
                mv.visitVarInsn(Opcodes.ALOAD, 1);
                mv.visitMethodInsn(Opcodes.INVOKESTATIC,"com/lampa/liqui/GoodClass","goodCheck","(Ljava/lang/String;)Ljava/lang/String;",false);
                mv.visitInsn(Opcodes.ARETURN);
            }
        }
    }
}


Это этот asm? Не слышал о нём ранее. Впрочем, манипуляция байткодом – столь специфичный раздел, что в этом нет ничего удивительного.


Спасибо информацию. Кажется, я знаю, чем я буду развлекаться в следующий раз.

Он самый. В идеале прямо в рантайме хуки добавлять, но этого я еще не пробовал)

Мне, если честно, было боязно смотреть, через что можно сделать это в рантайме. Например, непонятно, через какой класслоадер этот код был загружен, и мог ли бы он быть загружен через ещё какой-нибудь другой класслоадер тестовым фреймворком.


Я не очень хорошо представляю, какие побочные эффекты рантаймовое изменение может иметь, вся тема instrumentationдля меня пока сокрыта. :)

Скачать сорцы (git clone git://git.code.sf.net/p/dbunit/code.git dbunit-code.git), git checkout dbunit-2.7.0, отредактировать файл, mvn -DskipTests install — не вариант? Не дольше 5 минут.

Вариант, прекрасный вариант, если баг необходимо пофиксить как можно быстрее. У меня на это ковыряние ушло часа три с перерывом, и оно мне было интересно. :)


А на написание и вычитку статьи ушло полтора дня. Попытка получения виртуальных очков в интернете была более сомнительным поступком, чем самообразование. :/

Как способ быстро вникнуть в основы строения class-файла с нуля такой подход неплох.
С практической же точки зрения можно было:


  1. Понизить версию class-файла до 49.0 (Java 1.5). В этом случае верификация будет проводиться старым алгоритмом, без использования StackMapTable. Естественно, это подходит только для быстрой проверки гипотезы и только чуть лучше флага -noverify. Не сработает, если в class-файле будет что-то, чего не было в Java 1.5. Те же лямбды, например.


  2. Воспользоваться для редактирования class-файла нормальным инструментом, а именно — asmtools. Он поддерживается в актуальном состоянии и, к примеру, метод modified() выглядит в нём так:



  public Method modified:"(Ljava/lang/Object;)Ljava/lang/Object;"
    throws java/lang/RuntimeException
    stack 1 locals 2
  {
        aload_1;
        ifnonnull   L8;
        aconst_null;
        goto    L12;
    L8: stack_frame_type same;
        aload_1;
        invokevirtual   Method java/lang/Object.toString:"()Ljava/lang/String;";
    L12:
        stack_frame_type stack1;
        stack_map class java/lang/Object;
        areturn;

  }

Дизассемблировать, пропатчить нужный метод и ассемблировать обратно — дело пяти минут.
Более скучные варианты с перекомпиляцией из исходников рассматривать не будем.

если так уж вышло, что исходников, к примеру, нет, то скорее всего можно декомпелировать этот 1 файлик, поменять и собрать его опять через javac. а так вы конечно приобрели много новых, но, по большому счёту, не нужных в обычной жизни знаний :)

С декомпиляцией могут возникнуть небольшие проблемы.
Пример: Gist.

Sign up to leave a comment.

Articles