Возможно ли создать поведение undefined при разыменовании `null` в Java?

Я только узнал, что разыменование null в C и С++ иногда может давать undefined результаты. Это очень интригует ко мне, как и все причудливые программные поведения (однажды я сказал кому-то, что они отлаживают "коррумпированную RAM-программу, не запускается так, как написано" в законной производственной среде). Поскольку я в первую очередь разработчик Java, мне было интересно, возможно ли это, чтобы это произошло и на этом языке?

JLS не зависит от реализации ссылки null (3.10.7, 4.1, 15.8.1), поэтому я не совсем уверен. Но я думал, что это возможно, напрямую манипулируя адресом памяти с помощью небезопасного API. К сожалению, мне не хватает знаний о внутренней работе JVM, чтобы узнать, возможно ли это или нет.

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

Итак: возможно ли, что Java имеет поведение undefined при разыменовании null, а не просто бросает NullPointerException?

Ответ 1

Вы не можете получить поведение undefined из null в чистой Java (если в JVM нет серьезной ошибки!). JLS указывает, что любая попытка явно или неявно разыменовать a null приведет к NullPointerException. Существует не пространство для изгибов, которое допускает любое поведение undefined, связанное с обработкой null.

Однако, если ваше приложение включает в себя... или использует методы... native, один из этих методов может неправильно распознать null таким образом, что это приведет к поведению undefined. Вы также можете получить поведение undefined, используя класс Unsafe. Но оба этих сценария означают, что вы не используете чистую Java. (Когда вы выходите за пределы чистой Java, гарантии JLS больше не обязательно применяются!)

(Одна область, где могут возникнуть непредсказуемые события, - многопоточность, но даже тогда определен набор возможных вариантов поведения. Например, если вы не синхронизируете совместное использование состояния, вы можете увидеть устаревшие значения в полях. Но вы не увидите абсолютно случайных значений... или плохих адресов, которые приводят к нарушениям сегментации.)


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

Вредоносная программа может делать почти все. Но правильным способом справиться с этим является выполнение кода, которому вы не доверяете (то есть, возможно, вредоносного кода) в изолированной программной среде. Типичная песочница запрещает вызывать Unsafe или загружать собственную библиотеку... и многое другое, что может использовать вредоносная программа.

Ответ 2

JLS не зависит от того, как реализована нулевая ссылка, но определяет ее поведение. Другими словами, нет никакого неуказанного поведения. Если вы сталкиваетесь с поведением, отличным от указанного в JLS, его ошибка.

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

"Неопределенное поведение" означает, что сама спецификация позволяет найти место для различий в результате поведения. Это не относится к Java.

Ответ 3

Поведение определено в 15.12.4.4 Найти метод для вызова:

В противном случае должен быть вызван метод экземпляра, и есть цель Справка. Если целевой ссылкой является null, исключение NullPointerException брошенный в этой точке. В противном случае целевая ссылка называется ссылкой к целевому объекту и будет использоваться как значение ключевого слова this в вызываемом методе. Остальные четыре возможности для вызова режим считаются.

Точка разыменования должна вызывать исключение NullPointerException.

Ответ 4

Сама концепция языковой функции, имеющей поведение undefined, является тем, что используют авторы стандартов C и С++, чтобы дать понять, что стандарт не требует какого-либо конкретного поведения. Это дает возможность различным исполнителям C и С++ делать то, что наиболее эффективно или удобно для конкретного оборудования или операционной системы, для реализации. Это связано с тем, что C всегда обеспечивает высокую производительность по сравнению с переносимостью. Но у Java есть противоположные приоритеты; его ранний лозунг был "пишите один раз, бегите куда угодно". Поэтому спецификация языка Java не говорит о поведении undefined и стремится определить поведение всех функций языка.

Кажется, вы думаете, что использование нулевой ссылки может каким-то образом испортить память в некоторых случаях. Я думаю, вы запутываете указатели C/С++ с ссылками на Java. Указатель по существу является адресом памяти: путем отбрасывания его на void * и разыменования его у вас есть неограниченная способность испортить содержимое памяти. Ссылка на Java не похожа на адрес памяти, потому что сборщик мусора должен иметь возможность перемещать объекты в разные места в памяти. Перевод ссылки на Java на адрес памяти - это то, что может сделать только JVM; он никогда не может быть тем, что может сделать сама программа Java. Поскольку этот перевод полностью контролируется JVM, JVM может гарантировать, что перевод всегда действителен и всегда указывает на объект, который он должен и нигде больше.