Недоступні в мові можливості байткода java

Після того, як я досить тривалий час працював з байткод Java і проводив деякі дослідження в цій області, я хочу поділитися висновками, які я зробив.

Виконання в конструкторі коду до виклику суперконструктора або допоміжного конструктора

В Java як мовою програмування (далі - JPL, від Java Programming Language) виклик super () або this () повинен бути першим виразом в конструкторі, але в байткод Java (далі - JBC, від Java Byte Code) це не так. Ви можете додати код перед цими викликами, якщо виконуються наступні умови:

  • інший конструктор все-таки викликаний далі;
  • він викликаний не всередині умовного;
  • до виклику конструктора НЕ зчитуються поля і не викликаються методи конструируемого об'єкта. З цього відбувається наступне питання.

Робота з полями суті до виклику super () або this ()

До шостої версії в Java був наступний експлоїт, який дозволяв зробити вищеописане:

Тепер в JPL така робота з полями неможлива, але все ще можлива засобами JBC.

Вибір викликається конструктора (до Java 7u23)

Java не дозволяє створювати конструктори на зразок такого:

Проте, до Java 7u23 верифікатор HotSpot VM's пропускав цю перевірку. Тепер це пофіксити.

Створення класу взагалі без будь-якого конструктора

У JPL це не можливо - хоч один конструктор, але завжди успадковується. За допомогою JBC можна зробити так, що створити екземпляр класу буде неможливо, навіть якщо використовувати рефлексію (правда, sun.misc.unsafe все одно дозволяє зробити це).

Створення методів з однаковими сигнатурами, але різними повертаються типами

У JPL методи ідентифікуються по їх імені та набору параметрів, а в JBC - ще й що повертається типу.

Виклик перевіряються (checked) винятків без вказівки throws або конструкції try. catch

Перевірку того, чи всі перевіряються винятку спіймані (або вказані за допомогою throws), здійснює компілятор, це не залежить від Java Runtime або JBC.

Використання динамічного виклику методів поза лямбда-виразів

Так званий динамічний виклик методів можуть бути використаний для чого завгодно, а не тільки для лямбда-виразів. Його використання дозволяє, наприклад, змінювати логіку програми під час виконання. Багато динамічні мови програмування, які ґрунтуються на JBC, покращилися за рахунок цієї інструкції. В JBC ви можете також використовувати лямбда-вирази в Java 7, коли компілятор ще не міг обробляти динамічний виклик методів, а JVM - могла.

Використання ідентифікаторів, які зазвичай заборонені

Хотіли додати в ім'я вашого методу пробіл або перенесення рядка? Створіть свій JBC, і вдалою налагодження! Заборонені тільки символи «.», «;», «[» І «/». До того ж, якщо метод називається не або . його ім'я не може містити символів «<» и «>».

Перепризначення final полів, final параметрів і this

Параметра final в JBC не існує, і будь-який параметр, включаючи this (в нульовому індексі), може бути змінений. Константне поле також може бути змінено, якщо воно вже було визначено в конструкторі (для static полів цього не потрібно).

Виклик методу у null

В JBC ви можете зателефонувати за одним нестатичних метод у null. і він буде відмінно працювати, якщо в ньому немає виклику this.

Використання конструкторів і ініціалізаторів, ніби це методи

В JBC конструктори і ініціалізатор нічим не відрізняються від методів, єдине - вони повинні мати імена і відповідно, - щоб JVM могла перевіряти, чи викликає один конструктор інший коректний конструктор.

Невіртуальний виклик методу з того ж класу

У JPL виклик new Bar :: foo () завжди буде викликати Runtime Exception. Не можна зробити так, щоб метод Foo :: foo () завжди викликав bar () зі свого класу. Але ви можете реалізувати таку поведінку на JBC за допомогою опкода INVOKESPECIAL. який зазвичай використовується для виклику батьківських методів.

Призначення довільного мета-атрибута

У JPL ви можете додавати анотації тільки до полів, методів і класів. В JBC ви можете вбудувати будь-яку інформацію в клас. Правда, для її використання вам доведеться витягати її самостійно, не покладаючись на механізм завантаження класів Java.

Перенаповнення і неявне визначення значень byte. short. char і boolean

Технічно, всередині байткода існують тільки типи int. float. long і double. що дає простір для багатьох неприпустимих для JPL операцій.

Рекурсивний блок catch

У байткод Java ви можете зробити щось на зразок

Виклик будь-якого «методу за замовчуванням»

В JBC можна викликати default-метод, навіть якщо він був перевизначений.

Виклик методу батька у об'єкта, який не є this

У JPL можна викликати тільки методи свого найближчого батька або default-методи інтерфейсів. В JBC можливий код на зразок:

Доступ до синтетичних членам

У байткод до синтетичних членам можна звертатися безпосередньо.

Справедливо також для синтетичних полів, методів і класів.

Додавання некоректної інформації про дженериках

Інформація про дженериках зберігається в класі у вигляді рядків. Верификатор не відчуває її, а значить, можна зробити так, що наступні АССЕРТ будуть вірними:

Виклик неіснуючого методу і краш JVM

Ви можете зателефонувати за одним метод у будь-якого примірника. Зазвичай верифікатор розпізнає це, але я помітив, що іноді, коли ви викликаєте метод у (елемента) масиву, деякі версії JVM не розпізнає, що його не існує. Це, звичайно, сумнівна можливість, але за допомогою коду, який пройшов через javac. ви цього точно зробити не зможете. Зрозуміло, коли JVM дійде до цього місця, на неї чекає краш.