Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Annotation Inference in Idea 14

Annotation Inference in Idea 14

Inference of @NotNull/@Nullable/@Contract annotations from java bytecode in IDEA 14. See http://blog.jetbrains.com/idea/2014/10/automatic-notnullnullablecontract-inference-in-intellij-idea-14/.
The video is http://www.youtube.com/watch?v=1Z3Sk91FP0g
The talk given for my JetBrains colleagues.

Avatar for Ilya Klyuchnikov

Ilya Klyuchnikov

October 29, 2014
Tweet

More Decks by Ilya Klyuchnikov

Other Decks in Programming

Transcript

  1. 2 Предупреждение • Разбираются архитектура/дизайн • Самый Rocket science –

    за кадром (в статьях и отдельных проектах на github) – https://github.com/ilya-klyuchnikov/faba – https://github.com/ilya-klyuchnikov/kanva-micro
  2. 4 Мотивация • @NotNull/@Nullable/@Contract аннотации для библиотек: • Специфицируют код

    • Инспекции обеспечивают более null-safe код клиента • Вывод аннотаций • Писать вручную – скучно (и сложно поддерживать)
  3. 5 Что может IDEA 14 • Вывод @NotNull параметров •

    Вывод @NotNull методов (результатов) • Вывод @Nullable параметров • Вывод @Nullable методов (по умолчанию выключен) • Вывод простых @Contract: – “null->...” – “!null->...” • Все это – по байткоду
  4. 9 Архитектура (“frontend”) Frontend Indices Solver Get equation with all

    dependencies Converter Psi to internal id PSI Client ID EQs Solve equations SOLs Convert to PSI PSI
  5. 11 Источники вдохновения • KAnnotator • OW2 asm lib •

    Суперкомпиляция • SAT-решатели
  6. 12 IDEA vs KAnnotator • KAnnotator обрабытывает JDK7 rt.jar за

    ~25мин. • IDEA обрабытывает JDK7 – за ~20 сек • IDEA обрабатывает все библиотеки IDEA ultimate в пределах полутора минут.
  7. 13 Требования/ограничения • Корректность (soundness) аннотаций – Понятная и прозрачная

    семантика выведенной аннотации – Не должно быть ad-hoc эвристик (ad-hoc эвристики есть в R#) • Разумная скорость работы • Масштабируемость на очень большие проекты (масштаба IDEA ultimate edition)
  8. 14 Требования/ограничения • Корректность (soundness) аннотаций – Понятная и прозрачная

    семантика выведенной аннотации – Не должно быть ad-hoc эвристик (ad-hoc эвристики есть в R#) • Разумная скорость работы • Масштабируемость на очень большие проекты (масштаба IDEA ultimate edition)
  9. 16 @NotNull параметр void foo(@NotNull Object x, …) Если в

    @NotNull параметр передать null, то на любом возможном пути исполнения выполнится одно из: 1.NPE на этом самом null 2.Assertion • if (x == null) throw … 3.Loop (тонкое место) Плюс есть путь на котором выполняется 1 или 2.
  10. 17 Примеры - 1 void loadConfig(@NotNull View view) { File

    f = getConfigFile(); if (f != null) view.loadConfigFromFile(f); else view.loadDefaultConfig(); }
  11. 18 Примеры - 2 void saveConfig(@NotNull View view) { File

    f = getConfigFile(); if (f != null) view.saveConfigToFile(f); }
  12. 19 Примеры - 3 void saveConfig(@NotNull View view) { while

    (!getLock().isReady()) sleep(timeout); view.saveConfigToFile(myFile); }
  13. 20 @Nullable параметр void foo(@Nullable Object x, …) Если в

    @NotNull параметр передать null, то на любом возможном пути исполнения никогда не выполнится: 1.NPE на этом самом null 2.Assertion • if (x == null) throw …
  14. 21 Примеры - 1 void saveConfig(@Nullable File file) { if

    (file != null) view.saveConfigToFile(file); }
  15. 22 Примеры - 2 void saveConfig(@Nullable File file) { this.currentFile

    = file; this.view.saveConfigToFile(this); } class View { void saveConfigToFile(Foo foo) { if(!foo.getCurrentFile().exists()) ... }
  16. 23 @NotNull метод @NotNull Object foo(Object x, …) Самая простая

    семантика :) Метод не может вернуть null (Но может кинуть исключение или зациклиться)
  17. 24 @Nullable метод @Nullable Object foo(Object x, …) Самая неоднозначная

    (дурацкая) аннотация для вывода Метод может вернуть null (А может и не вернуть :)
  18. 25 @Contract @Сontract(“null,_->null”) Object foo(int[] x, …) { if (…)

    return x; return x.clone(); } Тоже есть неоднозначность (неинтуитивность) при возможных NEP и других исключениях.
  19. 26 Семантика - Выводы • @Nullable и @NotNull параметры –

    интуитивная семантика • @NotNull метод – интуитивная семантика • @Nullable метод – дурацкая семантика (дает false positives при использовании из code inspection) • @Contract(“null->null”) - тонкое место с ислючениями
  20. 28 Основные принципы • Много маленьких анализов – Отдельный анализ

    на @Nullable параметр (xN) – Отдельный анализ на @NotNull параметр (xN) – Отдельный анализ на @NotNull метод – Отдельный анализ на @Nullable метод – Отдельный анализ на каждый @Contract clause • Сложные контракты вида null,!null->!null не выводятся – Отдельный анализ на @Contract(pure=true) – Отдельный анализ на @Contract(“false->fail”)
  21. 29 Основные принципы • Быстрые вспомогательные анализы – Leaking parameters

    analysis – Result origins analysis • Суперкомпиляция, а не classical data flow analysis
  22. 31 Вывод @NotNull параметров • Вначале рассмотрим вывод через суперкомпиляцию,

    но без индексов (идейно похоже на Kannotator) – https://github.com/ilya-klyuchnikov/kanva-micro – Статья: Nullness Analysis of Java Bytecode via Supercompilation over Abstract Values http://bit.ly/ZAILZU • Затем перейдем на индексы/уравнения – https://github.com/ilya-klyuchnikov/faba Статья: Индексирование библиотек Java для вывода контрактов http://pat.keldysh.ru/~ilya/faba.pdf
  23. 32 Основная идея • Рассматриваем каждый параметр отдельно • Предположим,

    что параметр - null • Если все конечные пути исполнения ведут к NPE или assertion - @NotNull
  24. 33 Задача • Как перечислить все пути исполнения? • Граф

    конфигураций! • Корректное аппроксимирующее предположение – Все ветвления в коде реализуемы
  25. 34 Java Bytecode in a nutshell • Стековая виртуальная машина

    • Фрейм: – • > 100 инструкций • Но! Интересных для анализа инструкций немного: – GETFIELD, PUTFIELD – INVOKESTATIC, INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE = INVOKE
  26. 35 Распознавание NPE • Abstraction: all values are just PARAM

    (x) and VALUE (_, any value) • Direct NPE – _ _ _ | x GETFIELD “foo” – _ _ _ | x _ PUTFIELD “foo” – _ _ _ | x _ _ INVOKE “foo” • Indirect NPE – _ _ _ | _ _ x INVOKE “foo” void foo(int i, @NotNull Object o)
  27. 36 Supercompilation in a nutshell 1, _ x _ _

    2, _ x _ _ 1, _ x _ _ 3, _ x _ _ • Пара (insnIndex, Frame) – конфигурация (состояние) • Если в слотах фрейма только _ и x – конфигураций конечное число (экспоненциально большое в общем случае) • NB: граф конфигураций != граф потока управления (control flow graph)
  28. 37 Простой алгоритм вывода • Параметр - null • Строим

    граф конфигураций • Размечаем все пути (листья графа) – RETURN (нет разыменовывания) – NPE (разыменовываем) – CYCLE • Соединяем метки в стартовой вершине (next slide) • Если метка NPE, @NotNull параметр
  29. 38 Join operation RETURN CYCLE NPE RETURN RETURN RETURN RETURN

    CYCLE RETURN CYCLE NPE NPE RETURN NPE NPE
  30. 39 Многопроходное аннотирование • Граф зависимостей между методами • Топологическая

    сортировка графа • Итеративный вывод внутри компоненты (свойство монотонности) – Если вывели новую аннотацию для метода - “переаннотируем” зависимые методы – Процесс сходится • Реализация - проект kanva-micro – https://github.com/ilya-klyuchnikov/kanva-micro
  31. 40 Недостатки многопроходного анализа • Не масштабируется – В общем

    случае метод анализируется несколько раз • Нужно хитро кэшировать байткод • Решение – Индексы, уравнения, ленивый вывод
  32. 42 Основная идея • Причина многопроходности – использование результатов для

    зависимостей void m(Object o) { bar1(o); bar2(o); … } • Основная идея – стадирование анализа (staged inference) – (m_1=NN) = (bar1_1=NN) || (bar2_1 = NN)
  33. 43 Выигрыш • Однопроходный анализ • Решение булевых уравнений –

    очень быстро • При ленивом решении уравнений – (практически бесконечная) масштабируемость • Каждый jar индексируется ровно один раз per IDEA application • Не нужно переиндексировать. – Lib1.jar; Lib2.jar => Lib1-1.jar; Lib2.jar
  34. 44 Сижу за решеткой… • Вывод @NotNull/@Nullable параметров можно легко

    сформулировать в виде булевых уравнений • Вывод @NotNull результатов – уже сложнее • Поэтому внутри – обобщенные уравнения над решетками – Единый решатель для конечных решеток
  35. 47

  36. 48 Масштабируемость • #M – number of methods • #P

    – number of reference parameters • IT – total time • IT0 - “supercompilation” time • ST – solving time • #@ - number of inferred @NotNull annotations
  37. 50 Почему много небольших анализов? • Так быстрее, чем “все

    в одном флаконе” • В подавляющем большинстве случаев – ранний (отрицательный) результат анализа. • Модульнее – легче дописать еще один анализ. • На самом деле комбинирование анализов приводит к экспоненте.
  38. 51 Почему суперкомпиляция? • Во всех анализах – рассматривается множество

    путей выполнения. • В графе конфигураций пути выполнения представлены явно – с ними легче/понятней работать.
  39. 52 Resolve • Во время индексации/решения уравнений – no resolve

    – Вместо этого – sound approximation в случае нескольких методов (конфликтов) – (Такой же подход в случае конфликтов индексов) – No resolve → быстрота
  40. 53 Хаки с индексированием • Тяжелые дескрипторы методов. – Сохранять

    как есть – раздуваются индексы. • Вариант: PersistentStringEnumerator – Заметно замедляет индексирование – Хрупкая конструкция • Corrupted mapping file – нужно перезапустить индексирование, сбросить кэши • Нужно синхронизировать mapping file version → index version
  41. 54 Хаки с индексированием • Решение – сжатие дескрипторов через

    md5 – Отдельно сжимаем class name (10 bytes) – Отдельно сжимаем method descriptor (4 bytes) – Если в результате сжатия – конфликт, то делаем sound approximation.
  42. 55 Обзор остальных анализов • Reducibility analysis – Приводимость графов

    потока управления. Если граф приводим – можно сделать умный sharing в графе конфигураций. – Javac делает приводимые графы. Eclipse compiler имеет склонность делать неприводимые графы (пока что такие методы пропускаются)
  43. 56 Обзор остальных анализов • Leaking parameters analysis – Прежде

    чем запускать соответсвующий (тяжелый) анализ, - легкий преданализ, даст ли тяжелый анализ результат – Преданализ – на порядок быстрее
  44. 57 Обзор остальных анализов • Result origin analysis – Проблема

    во время вывода @NotNull метода: распространять или нет информацию – Решение – быстрый предварительный анализ, попадает ли значение, порожденное данной инструкцией в результат – Профит – учитываем во время анализа только то, что действительно нужно
  45. 58 Итог • Разделяй и властвуй – Отдельные маленькие анализы

    – Быстрые легковесные преданализы – Стадирование анализа: уравнения при индексировании и ленивое получение решения – Вывод по байткоду может быть быстрым
  46. 60 Что дальше? • Более точный вывод @NotNull методов –

    Адаптация алгоритма с распространением негативной информации – Challenge: эвристики, обеспечивающие скорость • Ускорение для @Contract(“null|!null->”) – Отсекающие эвристики? – Offline processing?
  47. 61 Что дальше? • Вывод @NotNull полей (static final и

    final) – Выведется больше @NotNull методов (транзитивность) • @ReadOnly, @Write аннотации? – Выведется больше @Contract(pure=true) • @UiThread • @ReadAction, @WriteAction? - эффекты
  48. 62 Что дальше? • Mining аннотаций – Для интерфейсов и

    переопределяемых методов (похоже на code recommenders) • Адаптация подхода (индексирование после компиляции) для исходников – Mining (Никаких технических проблем) – Inference (синхронизация исходников и байткода)