Pull to refresh

Comments 15

интересно, какой университет вы заканчивали, что так (со стороны) просто разбираетесь в таких сложных вещах?
обычные последовательные действия инженера. Нет тут никакой умственной магии.
Данные инструкции разнесены для того, чтобы находиться в разных try-блоках. Почему сделано именно так, я не знаю. Вероятно, это был единственный способ обрабатывать исключения так, чтобы они полностью соответствовали спецификации языка.

Если посмотреть на реализацию MethodAccessorGenerator, то будет видно, что generateMethod() и generateConstructor() — обёртки над методом generate() и генерируемые классы отличаются лишь в деталях.


Вот, к примеру, как будет выглядеть класс для доступа к PrintStream::println(String):


package sun.reflect;

import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
    public Object invoke(Object var1, Object[] var2) throws InvocationTargetException {
        if (var1 == null) {
            throw new NullPointerException();
        } else {
            PrintStream var10000;
            String var10001;
            try {
                var10000 = (PrintStream)var1;
                if (var2.length != 1) {
                    throw new IllegalArgumentException();
                }

                var10001 = (String)var2[0];
            } catch (NullPointerException | ClassCastException var4) {
                throw new IllegalArgumentException(var4.toString());
            }

            try {
                var10000.println(var10001);
                return null;
            } catch (Throwable var3) {
                throw new InvocationTargetException(var3);
            }
        }
    }
}

Здесь видно, что в блоке catch (NullPointerException | ClassCastException) делается проверка того, что метод вызывается на объекте подходящего типа, в метод передаётся ровно столько параметров, сколько нужно и они верных типов.
Если в этом блоке что-то пойдёт не так, то наружу будет проброшено IllegalArgumentException.


В блоке catch (Throwable) производится уже непосредственный вызов метода и если в процессе его работы будет сгенерирована исключительная ситуация, то она будет обёрнута в InvocationTargetException и так же проброшена наружу.


Единственное отличие при вызове конструктора, а не метода ровно одно — в самом начале catch (NullPointerException | ClassCastException) добавляется инструкция new, а в качестве вызываемого метода выступит <init> (конструктор).


Другими словами, метод invoke() сгенерированного класса ведёт себя аналогично методам java.lang.reflect.Method::invoke() и java.lang.reflect.Constructor::invoke().

Это никак нельзя назвать созданием инстанса java.lang.Class. Вы аллоцировали обычный java.lang.Object, и испортили весь лейаут хипа, перезаписав в нём поле klass. Чтобы это было хоть чуточку похоже на правильный инстанс, надо было выделить объект нужного размера. Меньше нельзя, потому что иначе при доступе к полям своего объекта перезапишите соседнюю память. Но и больше тоже нельзя, потому что при итерировании по хипу JVM использует поле klass, чтобы узнать размер объекта и найти, где начинается следующий.

Согласен с вами, лейаут хипа испорчен. Объект ClassLoader, переданный в конструктор, записался в чью-то чужую память и стал бомбой замедленного действия.
Но даже если бы я выделил правильное количество байт, всё равно почти любые манипуляции с этим объектом приводили бы к краху. В JVM ведь по факту никакого нового класса нет, а методы в java.lang.Class почти все нативные.


Понятное дело, что приведённая задача решения не имеет. Вся 6-я часть может восприниматься как шутка.

Как говорится, "Жесть, читать до конца". Цинизм прохождения первого уровня насмешил. А на пятом вспомнился анекдот про "За использование неопределённого поведения в особо крупных размерах приговаривается к 40 годам обратной совместимости", ещё подумалось: "А как же в исходниках JVM поковыряться?" — и тут уровень 6...

Вы описали аж 6 заходов на то, чтобы вызвать конструктор java.lang.Class, но так и не обозначили ЗАЧЕМ вы это задумали. Вот лишь некоторые из вопросов, на которые статья даже не пытается ответить:
— Какое будет имя у класса, который инстанцируете (что вернет getName())?
— Какие у него будут методы и поля?
— Как он будет связан с класс-лоадером (просто установка приватного поля в самом инстансе класса очевидно недостаточно)?

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

Если вы это все делаете просто из желания «обмануть систему» — тогда Ok, как говорится, «каждый борется со скукой по-своему».
Но если вы хотели вручную создать экземпляр класса, еще не загруженного JVM, то для этого есть нормальный путь:
1. Загрузить байткод (т.е. файл my/package/MyClass.class) в массив байт
2. Вызвать ClassLoader.defineClass(...) в том класслоадере, в котором хотите иметь вновь загруженный класс.

Конструктор java.lang.Class — это лишь предлог, для любого другого класса изложение закончилось бы уже в первой части.
Настоящая цель должна быть понятна из содержания — исследование способов динамического инстанцирования объектов. Откинув абсурдную шестую часть, остаются реально используемые варианты:


  • JNI вызов;
  • MagicAccessorImpl;
  • Unsafe.
Для «динамического инстанцирования объектов» в JDK есть масса способов.
А вы-то инстанцируете не просто «объекты» а конкретно java.lang.Class — особый объект, не предназначенный для прямого инстанцирования.
Но в целом понятно — видимо, для вас это просто fun :)

Если я упустил какой-то из способов, то расскажите :)

ClassLoader.defineClass(String name, byte[] b, int off, int len)

Sign up to leave a comment.

Articles

Change theme settings