Хотел сегодня поспать, но опять не удалось. В Телеграме появилось сообщение, что у кого-то не собирается Java… и мы очнулись только через пару часов, уставшие и довольные.




Кому этот пост может быть полезен? Да, наверное, никому, кроме тех, кто тоже собирает JDK8 или просто любит почитать кошмарные ужасы. В общем, я вас предупредил, закрывайте статью срочно.

Проблемы три:


  • Не собирается (уровень первый)
    Очень скучная часть, которую можно пропустить. Нужна только для тех, кто хочет полностью восстановить историю событий;
  • Не собирается (уровень второй)
    Интересней, потому что там есть пара типичных ошибок, некромантия, некрофилия, в чём BSD лучше GNU/Linux и почему стоит переходить на новые версии JDK.
  • Даже если собирается, падает в корку
    Более интересно. Йахууу, JVM упала в корку, давайте пинать её ногами!

Под катом показан подробный ход решения проблем, с разными побочными мыслями о жизни.


Будет много C++, кода на Java не будет вообще. Любой джавист в конце концов начинает писать только на C++…


Не собирается


Кто хоть раз собирал Java, знает, что это выглядит как-то так:


hg clone http://hg.openjdk.java.net/jdk8u/jdk8u
cd jdk8u
sh ./get_source.sh

sh ./configure \ 
--with-debug-level=fastdebug \ 
--with-target-bits=64 \ 
--with-native-debug-symbols=internal \ 
--with-boot-jdk=/home/me/opt/jdk1.8.0_161

make images

(У меня все пользователи называются просто «me», чтобы виртуалку можно было в любой момент отдать любому человеку и не создать отторжения от пользования не своим юзернеймом)


Проблема, конечно, в том, что это не работает. Причём довольно циничным образом.



Первый уровень погружения


Давайте попробуем запустить:


/home/me/git/jdk8u/hotspot/src/os/linux/vm/os_linux.inline.hpp:127:18: warning: ‘int readdir_r(DIR*, dirent*, dirent**)’ is deprecated [-Wdeprecated-declarations]
   if((status = ::readdir_r(dirp, dbuf, &p)) != 0) {
                  ^~~~~~~~~

Вначале, чтобы вы понимали, у меня установлено вот такое:


$ g++ --version
g++ (Ubuntu 7.3.0-16ubuntu3) 7.3.0
Copyright (C) 2017 Free Software Foundation, Inc.

Компилятор не первой свежести, не 8.2, но и этот должен бы подойти.


C++ разработчики любят тестировать софт только на той версии компилятора, что установлена у них. Обычно желание тестировать на разных платформах заканчивается где-то в районе разницы между gcc и clang в общем смысле. Поэтому вполне нормально вначале впендюрить -Werror («считать предупреждения ошибками») и потом писать такой код, который во всех остальных версиях будет считаться ворнингами.


Проблема это известная, и ясно, как её решать. Нужно установить свою переменную окружения CXX_FLAGS, в которой прописать верный уровень ерроров.


export CXX_FLAGS=-Wno-error=deprecated-declarations -Wno-error-deprecated-declarations

И тут же видим чудесное:


Ignoring CXXFLAGS found in environment. Use --with-extra-cxxflags

Хорошо, система сборки, всё, что хочешь! Подменяем configure на вот такой:


hg clone http://hg.openjdk.java.net/jdk8u/jdk8u
cd jdk8u

sh ./configure \ 
--with-extra-cflags='-Wno-cpp -Wno-error=deprecated-declarations' \ 
--with-extra-cxxflags='-Wno-cpp -Wno-error=deprecated-declarations' \ 
--with-debug-level=fastdebug \ 
--with-target-bits=64 \ 
--with-native-debug-symbols=internal \ 
--with-boot-jdk=/home/me/opt/jdk1.8.0_161

make images

И ошибка остаётся той же самой!
Переходим к тяжелой артиллерии: грепу исходников.


grep -rl "Werror" .

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


./common/autoconf/flags.m4
./hotspot/make/bsd/makefiles/gcc.make
./hotspot/make/solaris/makefiles/gcc.make
./hotspot/make/aix/makefiles/xlc.make

Во flags.m4 легко находим предыдущее сообщение про «Ignoring CXXFLAGS» и более матёрый захардкоженый флаг CCXX_FLGS (да, две буквы C), который сразу действует и вместо CFLAGS, и вместо СXX_FLAGS. Удобно! Интересно два факта:


  • Этот флаг никак не передаётся через параметры configure;
  • В дефолтном значении находятся осмысленные и подозрительно похожие на настоящие параметры:

  # Setup compiler/platform specific flags to CFLAGS_JDK,
  # CXXFLAGS_JDK and CCXXFLAGS_JDK (common to C and CXX?)
  if test "x$TOOLCHAIN_TYPE" = xgcc; then
    # these options are used for both C and C++ compiles
    CCXXFLAGS_JDK="$CCXXFLAGS $CCXXFLAGS_JDK -Wall -Wno-parentheses -Wextra -Wno-unused -Wno-unused-parameter -Wformat=2         -pipe -D_GNU_SOURCE -D_REENTRANT -D_LARGEFILE64_SOURCE"

Очень мило смотрится в комментариях этот вопрос — а что, флаги общие? Правда?


Не будем играть в демократию и авторитарно захардкодим туда -w («не показывать никаких ошибок»):


    CCXXFLAGS_JDK="$CCXXFLAGS $CCXXFLAGS_JDK -w -ffreestanding -fno-builtin -Wno-parentheses -Wno-unused -Wno-unused-parameter -Wformat=2 \

И — ура! — первую ошибку мы прошли. Она больше не репортится, и вообще всё отлично. Казалось бы.



Второй уровень погружения


Но теперь оно падает в куче других новых мест!


Получается, что наш -w работает, но пробрасывается не во все части сборки. Аккуратно вычитываем мейкфайлы и не понимаем, как именно этот параметр вообще может проброситься. Неужто о нём забыли?


Зная верный вопрос к гуглу («почему cxx не доходит до сборки?!»), быстренько попадаем на страницу бага с говорящим названием «configure --with-extra-cxxflags doesn't affect hotspot» (JDK-8156967).


Который обещают пофиксить в JDK 12. Может быть. Чудесно — самый важный параметр сборки не используется в сборке!


Первая идея — ну что ж, давайте засучим рукава и поправим ошибки!


Ошибка 1. xn[12]


dependencies.cpp: In function ‘static void Dependencies::write_dependency_to(xmlStream*, Dependencies::DepType, GrowableArray<Dependencies::DepArgument>*, Klass*)’:

dependencies.cpp:498:6: error: ‘%d’ directive writing between 1 and 10 bytes into a region of size 9 [-Werror=format-overflow=]
 void Dependencies::write_dependency_to(xmlStream* xtty,
      ^~~~~~~~~~~~

dependencies.cpp:498:6: note: directive argument in the range [0, 2147483647]

Хорошо, нам, наверное, нужно увеличить регион. Сто пудов кто-то вычислил буфер нажатием кнопки «Мне Повезёт!» в гугле.


Но как бы понять, сколько надо? Ниже есть уточнение другого рода:


stdio2.h:34:43: note: ‘__builtin___sprintf_chk’ output between 3 and 12 bytes into a destination of size 10
       __bos (__s), __fmt, __va_arg_pack ());

Позиция 12 выглядит как что-то стоящее, с чем теперь можно вломиться грязными ногами в исходник.


Лезем в dependencies.cpp и наблюдаем следующую картину:


DepArgument arg = args->at(j);
if (j == 1) {
  if (arg.is_oop()) {
    xtty->object("x", arg.oop_value());
  } else {
    xtty->object("x", arg.metadata_value());
  }
} else {
  char xn[10]; sprintf(xn, "x%d", j);
  if (arg.is_oop()) {
    xtty->object(xn, arg.oop_value());
  } else {
    xtty->object(xn, arg.metadata_value());
  }
}

Обратите внимание на проблемную строчку:


char xn[10]; sprintf(xn, "x%d", j);

Меняем 10 на 12, пересобираем и… сборка пошла!


Но неужели я один такой умный и починил багу всех времён и народов? Не вопрос, опять вбиваем в гугл наш мегапатч: char xn[12];


И видим… да, всё верно. Баг JDK-8184309, заревьюенный Владимиром Ивановым, содержит точно такое же исправление.


Но суть в том, что он поправлен только в JDK 10 и нифига не бэкпортирован в jdk8u. Это к вопросу о том, зачем нужны новые версии джавы.


Ошибка 2. strcmp


fprofiler.cpp: In member function ‘void ThreadProfiler::vm_update(TickPosition)’:
/home/me/git/jdk8ut/hotspot/src/share/vm/runtime/fprofiler.cpp:638:56: error: argument 1 null where non-null expected [-Werror=nonnull]
   bool vm_match(const char* name) const { return strcmp(name, _name) == 0; }

Наученные предыдущим горьким опытом, сразу же идём смотреть, что в этом месте находится в JDK 11. И… этого файла там нет. Структура каталогов тоже подверглась некоторому рефакторингу.


Но от нас так просто не уйдёшь!


Любой джавист — в душе немного некромант, а может даже и некрофил. Поэтому сейчас будет НЕКРОМАНТИЯ В ДЕЙСТВИИ!


Вначале нужно воззвать к душе мёртвого и узнать, когда он умер:


$ hg log --template "File(s) deleted in rev {rev}: {file_dels % '\n  {file}'}\n\n" -r 'removes("**/fprofiler.cpp")'

File(s) deleted in rev 47106: 
  hotspot/src/share/vm/runtime/fprofiler.cpp
  hotspot/src/share/vm/runtime/fprofiler.hpp
  hotspot/test/runtime/MinimalVM/Xprof.java

Теперь нужно выяснить причину его гибели:


hg log -r 47106
changeset:   47106:bed18a111b90
parent:      47104:6bdc0c9c44af
user:        gziemski
date:        Thu Aug 31 20:26:53 2017 -0500
summary:     8173715: Remove FlatProfiler

Итак, у нас есть убийца: gziemski. Давайте выясним, зачем он прибил этот несчастный файл.


Для этого надо пройти в жиру в тикет, указанный в summary коммита. Это JDK-8173715:


Remove FlatProfiler:
We assume that this technology is no longer in use and is a source of root scanning for the GC.


За-ши-бись. По сути, сейчас нам предлагается починить труп просто для того, чтобы билд собрался. Который разложился настолько, что даже наши коллеги-некроманты из OpenJDK забросили.


Давайте воскресим мертвеца и попробуем расспросить его, что он запомнил последним. Он был уже мёртв в ревизии 47106, значит в ревизии на единичку меньше — это «за секунду до»:


hg cat "~/git/jdk11/hotspot/src/share/vm/runtime/fprofiler.cpp" -r 47105 > ~/tmp/fprofiler_new.cpp

cp ~/git/jdk8u/hotspot/src/share/vm/runtime/fprofiler.cpp ~/tmp/fprofiler_old.cpp

cd ~/tmp

diff fprofiler_old.cpp fprofiler_new.cpp

К сожалению, совершенно ничего, касающегося return strcmp(name, _name) == 0; в диффе нет. Поциент умер от удара тупым острым предметом (утилитой rm), но на момент смерти уже был неизлечимо болен.


Давайте копнем в суть ошибки.


Вот что как бы хотел сказать нам автор кода:


  const char *name()    const { return _name; }
  bool is_compiled()    const { return true; }

  bool vm_match(const char* name) const { return strcmp(name, _name) == 0; }

Теперь немного философии.


Стандарт C11 в пункте 7.1.4, «Use of library functions», явным образом говорит:


Each of the following statements applies unless explicitly stated otherwise in the detailed descriptions that follow: If an argument to a function has an invalid value (such as [...] a null pointer [...]) [...], the behavior is undefined.

То есть теперь весь вопрос в том, есть ли некое «explicitly stated otherwise». В описании strcmp в разделе 7.24.4 ничего подобного не написано, а других разделов у меня для вас нет.


То есть мы здесь имеем undefined behavior.


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


Да, конечно, кто-то скажет, что это ты сам себе дурак, что используешь GCC 7.3, а вот в GCC 4 точно бы все собралось. Но ведь undefined behavior != unspecified != implementation defined. Это для последних двух можно закладываться на работу в старом компиляторе. А UB и в шестой версии был UB.


Короче, я совсем взгрустнул над этим сложным философским вопросом (лезть ли со своими предположениями в код), когда внезапно осознал — можно и по-другому.


Есть другой путь


Как известно, хорошие герои всегда идут в обход.


Даже если отвлечься от нашей философии про UB, проблем там невероятное количество. Не факт, что их можно починить до утра. Не факт, что я своими кривыми руками не накосячу. Еще менее факт, что это примут в апстрим: последний патч в jdk8u был 6 недель назад, и это был глобальный мердж нового тэга.


Просто представим, что код выше на самом деле написан правильно. Всё, что стоит между нами и его выполнением, — некий warning, который был воспринят как error по причине бага в системе сборки. Но ведь мы можем похачить систему сборки.


Ведьмак Геральт из Ривии говорил когда-то:


— Зло — это зло, Стрегобор, — серьёзно сказал ведьмак, вставая. — Меньшее, бо?льшее, среднее — всё едино, пропорции условны, а границы размыты. Я не святой отшельник, не только одно добро творил в жизни. Но если приходится выбирать между одним злом и другим, я предпочитаю не выбирать вообще.

— Zlo to zlo, Stregoborze — rzekl powaznie wiedzmin wstajac. — Mniejsze, wieksze, srednie, wszystko jedno, proporcje sa umowne a granice zatarte. Nie jestem swiatobliwym pustelnikiem, nie samo dobro czynilem w zyciu. Ale jezeli mam wybierac pomiedzy jednym zlem a drugim, to wole nie wybierac wcale.

Это цитата из книги «Последнее желание», рассказ «Меньшее зло». Мы-то знаем, что Геральт почти никогда не мог до конца сыграть роль истинно-нейтрального персонажа, и даже умер по причине очередного классического хаотически-доброго поведения.


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


В самом начале мы уже видели вот такой выхлоп:


grep -rl "Werror" .

./common/autoconf/flags.m4
./hotspot/make/linux/makefiles/gcc.make
./hotspot/make/bsd/makefiles/gcc.make
./hotspot/make/solaris/makefiles/gcc.make
./hotspot/make/aix/makefiles/xlc.make

Сравнивая два этих файла, я разбил всё лицо фейспалмом и осознал разницу в культуре двух платформ:


BSD — это история о свободе и возможностях выбора:


# Compiler warnings are treated as errors
ifneq ($(COMPILER_WARNINGS_FATAL),false)
  WARNINGS_ARE_ERRORS = -Werror
endif

GNU/Linux — это авторитарный режим пуристов:


# Compiler warnings are treated as errors
WARNINGS_ARE_ERRORS = -Werror

Ну ещё бы оно пробрасывалось в linux через ССXX_FLAGS, эта переменная при вычислении WARNINGS_ARE_ERRORS близко не учитывается! В билде для GNU/Linux у нас просто нет выбора, кроме как следовать спущенным свыше дефолтам.


Ну или можно сделать проще и поменять значение WARNINGS_ARE_ERRORS на краткое, но не менее мощное -w. Как тебе такое, Илон Маск?


Как вы могли догадаться, это полностью решает данную проблему сборки.


Когда код собирается, вы видите пролетающую мимо кучу странных, жутко выглядящих проблем. Иногда бывало так страшно, что очень хотелось нажать ctrl+C и попробовать разобраться. Но нет, нельзя, нельзя…


Вроде бы всё собралось и не принесло никаких дополнительных проблем. Хотя я, конечно, не решился начать заниматься тестированием. Все-таки ночь, глаза начинают слипаться, а переходить к последнему средству — четырем банкам энергетика из холодильника — как-то пока не хочется.



Падает в корку


Сборка прошла, сгенерированы экзешники, мы молодцы.


И вот мы пришли к финишу. Или не пришли?


Наша сборка лежит по следующему пути:


export JAVA_HOME=~/git/jdk8u/build/linux-x86_64-normal-server-fastdebug/jdk

export PATH=$JAVA_HOME/bin:$PATH

При попытке запустить исполняемый файл java он мгновенно падает в корку. Для тех, кто не знаком — это выглядит примерно так:




При этом у Алекса — Debian 9.5, а у меня — Ubuntu. Две разных версии GCC, две по-разному выглядящие корки. У меня есть невинные шалости с ручным патчем strcmp и еще нескольких мест, у Алекса — нет. В чём же проблема?


Эта история достойна отдельного рассказа, но тут давайте сразу перейдём к сухим выводам, иначе я этот пост не допишу никогда.


Проблема в том, что наши любимые C++-погромисты опять использовали undefined behavior.


(Причем там, где оно неизвестным способом зависит от реализации компилятора. Впрочем, надо помнить, что UB — всегда UB, даже на известной версии компилятора закладываться на него нельзя)


В одном месте мы там обращаются к полю недосконструированного класса, и всё ломается. Не спрашивайте, как так вышло, всё сложно.


Для джависта очень сложно представить, как можно обратиться к недосконструированному классу, кроме как выпустив ссылку на него прямо из конструктора. К счастью, чудесный язык C++ может всё или практически всё. Запишу пример неким псевдокодом:


class A
{
  A()
  {
    _b.Show();
  }
private:

  static B _b; 
};

A a;
B A::_b;

int main()
{
}

Have a nice debug!


Если поглядеть на C++98 [class.cdtor]:


For an object of non-POD class type… before the constructor begins execution… referring to any non-static member or base class of the object results in undefined behavior

Начиная с GCC какой-то версии (а у меня 7.3) появилась оптимизация «lifetime dead store elimination», которая считает, что к объекту мы обращаемся только в ходе его лайфтайма, а вне лайфтайма всё выкашивает.


Решение — отключить новые оптимизации и вернуть как было в старых GCC:


CFLAGS += -fno-strict-aliasing -fno-lifetime-dse -fno-delete-null-pointer-checks

Про это здесь есть обсуждение.
По какой-то причине участники дискуссии решили, что в апстрим это не включат. Но всё равно надо попробовать заслать.


Добавляем эти опции в наш ./hotspot/make/linux/makefiles/gcc.make, всё пересобираем ещё раз и видим заветные строчки:


t$ ~/git/jdk8u/build/linux-x86_64-normal-server-fastdebug/jdk/bin/java -version
openjdk version "1.8.0-internal-fastdebug"
OpenJDK Runtime Environment (build 1.8.0-internal-fastdebug-me_2018_09_10_08_14-b00)
OpenJDK 64-Bit Server VM (build 25.71-b00-fastdebug, mixed mode)

Заключение


Вы, наверное, подумали, что вывод будет следующий: «Java — это какой-то ад, в коде мусор, поддержки нет, всё плохо».


Это не так! Напротив, примеры выше показывают, от какого страшного зла хранят нас наши друзья, некроманты из OpenJDK.


И несмотря на то, что им приходится жить и пользоваться C++, дрожать от каждого UB и изменения версии компилятора и изучать тонкости платформ, финальный пользовательский код на языке Java — безумно стабильный, а на билдах, выложенных на официальных сайтах компаний, таких как Azul, Red Hat и Oracle, вряд ли можно напороться на корку в простом случае.


Единственная печальная штука — скорей всего, найденные ошибки вряд ли примут в jdk8u. Мы взяли JDK 8 просто потому, что нам проще его запатчить прямо здесь и сейчас, а с JDK 11 придется разбираться. Тем не менее, использовать в 2018 году JDK 8 — имхо, это очень плохая практика, и мы делаем это не от хорошей жизни. Возможно, в будущем наша жизнь улучшится, и вы прочитаете ещё множество невероятных историй из мира JDK 11 и JDK 12.


Спасибо за внимание, уделённое столь занудному тексту без картинок :-)


Минутка рекламы. Совсем скоро пройдёт конференция Joker 2018, на которой будет множество видных специалистов по Java и JVM. Посмотреть полный список спикеров и докладов можно на официальном сайте. Я там тоже буду, можно будет встретиться и перетереть за жизнь и OpenJDK.

Комментарии (65)


  1. Nourepide
    10.09.2018 18:12
    +1

    Вы какие-то монстры. Встретив бы вас ночью в серверной я бы молил о безболезненной смерти… а теперь я еще и узнал что вы некроманты.
    Как теперь жить зная что меня могут оружать люди которое делают такое просто ради развлечения?

    Спасибо за легкое чтиво, я теперь не усну


    1. olegchir Автор
      10.09.2018 18:35

      Раз-два, OpenJDK заберет тебя (...) никогда не спите, дети!

      Правильно, нечего спать, пришло время собирать! Попробуй повторить манипуляции и собрать все у себя на локальной системе новым компилятором :)


  1. mspain
    10.09.2018 18:27
    +1

    Кровавая плата за то, что жабка самая быстрая из всех языков второго сорта (за первый примем си и плюсы).


    1. olegchir Автор
      10.09.2018 18:34

      а как же макроассемблер?


      1. Antervis
        10.09.2018 18:44
        +1

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


    1. Fedcomp
      10.09.2018 23:45
      +2

      Rust же еще.


    1. ZimM
      11.09.2018 12:23

      C#, не?


  1. Antervis
    10.09.2018 18:43
    +1

    еще раз убеждаемся, что если бы джависты умели писать на плюсах, на них бы они и писали


  1. apangin
    10.09.2018 18:45
    +1

    В README к сборке OpenJDK указано, какими версиями тулов собирается официальный билд. Там же отмечено, что

    Compilation problems with newer or different C/C++ compilers is a common problem.

    Понятно, что поддержка новый версий компилятора — большая работа, сопряжённая с риском для уже проверенного кода, и никто не будет её делать для устаревающей JDK 8, поскольку это ни разу не критичная проблема.

    Кстати, ещё один хинт: java -Xinternalversion скажет, каким компилятором собиралась данная конкретная версия JDK. Например, для стандартного пакета в Ubuntu это будет gcc 5.4.0. Можно сэкономить кучу времени и нервов, просто выбрав для сборки gcc-5.

    $ java -Xinternalversion
    OpenJDK 64-Bit Server VM (25.181-b13) for linux-amd64 JRE (1.8.0_181-8u181-b13-0ubuntu0.16.04.1-b13), built on Jul 30 2018 21:06:27 by "buildd" with gcc 5.4.0 20160609


    1. olegchir Автор
      10.09.2018 19:03

      Ну, наши-то патчи (ты помнишь какие) сейчас работают только с JDK 8, поэтому придется поддерживать все что есть, пока не портируем вперед


    1. nafgne
      10.09.2018 19:03
      +2

      хм

      $ java -Xinternalversion
      OpenJDK 64-Bit Server VM (25.181-b13) for linux-amd64 JRE (1.8.0_181-8u181-b13-0ubuntu0.18.04.1-b13), built on Jul 23 2018 19:14:48 by «buildd» with gcc 7.3.0
      $ cat /etc/issue
      Ubuntu 18.04.1 LTS


      1. apangin
        10.09.2018 19:26
        +1

        Во как! Стало быть, в свежей Убунте весь этот путь уже прошли. Тогда, чтобы играться с OpenJDK на Ubuntu, логичней брать исходники не с openjdk.java.net, а с launchpad.net, где все необходимые для компиляции патчи уже сделаны.


        1. asm0dey
          10.09.2018 22:04

          В Manjaro и вовсе gcc 8.2.0.


          1. olegchir Автор
            10.09.2018 22:33

            Так и у меня GCC 8 на целевой тачке. На убунте и 7.3 запустил просто чтобы покопаться кой в чем на интеле, это не конечная цель.


    1. olegchir Автор
      10.09.2018 19:22

      И кстати, вот этот образчик еще бы обсудить


      char xn[10]; sprintf(xn, "x%d", j);

      • magic number,
      • отсутствие проверки на размер буфера (man snprintf),
      • отсутствие проверки на возвращаемый результат,
      • использование неправильного формата
      • и вообще использование универсальной библиотечной функции там, где должна быть специальная (itoa).

      WTF. Кажется, проблема тут не с ворнингом в компиляторе, а с тем что код написан странновато.


      1. apangin
        10.09.2018 20:15
        +1

        Я понимаю твоё негодование после уймы потраченного времени ;) но по сути здесь нет никаких проблем, кроме предупреждений слишком «умного» компилятора.

        • Переменная j (индекс аргумента) на практике всегда от 0 до 3.
        • Заводить где-то отдельную константу BUFFER_SIZE для буфера, который заполняется лишь в той же строке, полагаю, перебор. Boilerplate, ничуть не улучшающий читабельность.
        • И что же делать с возвращаемым значением? Проверять, что 10 байт хватит на запись трёх символов? А если нет, то что? :) По мне так тоже перебор.
        • Что не так с форматом? И чем поможет itoa, чтобы напечатать x2?

        Я ни в коем случае не хочу оспаривать паттерны написания корректного кода, но в данном конкретном примере сразу понятно, что хотел выразить автор, и что никаких багов с переполнением буфера здесь быть не может. То, что более свежий gcc стал выдавать предупреждение — да, стоит поправить, но делать из этого выводы, что написана какая-то фигня, будет преждевременно.


        1. olegchir Автор
          10.09.2018 20:18

          Времени, кстати, было потрачено минимум :-) Вот написание статьи — это да, чертова уйма времени. А еще на комментарии отвечать.


        1. Kobalt_x
          10.09.2018 20:51
          +1

          >Переменная j (индекс аргумента) на практике всегда от 0 до 3.

          Тогда вообще непонятно зачем sprintsf можно было static const char* Val's[3]{«0x0»,«0x1»,«0x2»}; завести

          >Заводить где-то отдельную константу BUFFER_SIZE для буфера, который заполняется лишь в той же строке, полагаю, перебор. Boilerplate, ничуть не улучшающий читабельность.

          Тем что непонятно при чем тут 10 если значения 0x1,0x2,0x3 (вроде как 4 charа), код пахнет…

          >Что не так с форматом? И чем поможет itoa, чтобы напечатать x2?

          Тем что любая ошибка в строке форматирования это потенциальный риск stack corruption и привет rce.(я не про данный случай а вобщем).
          Поэтому в современном c++ их стараются избегать.
          Поэтому лучше уж либо const char xn[10]=«x»; itoa(j,xn+1,0x10); либо стримы с std::hex


        1. olegchir Автор
          11.09.2018 13:48
          +2

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

          Что я понял из мира веб-программирования: код прявят ЛЮБЫЕ люди. Вполне возможно, этот человек буквально позавчера был поваром или менеджером по продажам телефонов, вчера выучил синтаксис языка и сегодня правит. Может быть это какой-то инженер, но ему очень лень разбираться, а баг надо было пофиксить ещё завтра.

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

          Для этого нужно использовать не «паттерны написания корректного кода», а паттерны написания кода для последующего чтения и модификацией идиотами.

          Например, если у тебя есть функция «умножь на два», то очевидно нужно использовать именно её, а не какую-то идиому состоящую из нескольких понятий. Это касается не только больших вещей вроде архитектуры, но и самых маленьких вещей.

          Когда код не написан в таком стиле, мне становится плохо и больно. (В том числе и потому, что я сам ленивый идиот).


          1. yleo
            11.09.2018 14:22
            +2

            Не «паттерны написания корректного кода», а паттерны написания кода для последующего чтения и модификацией идиотами.

            Просится в подзаголовок книги о Java ;)


            1. olegchir Автор
              11.09.2018 15:33
              +1

              Я серьезно собираюсь написать такую книгу или серию статей. Похоже, люди совсем обленились и не понимают, как правильно писать говнокод :-)


              1. MooNDeaR
                13.09.2018 13:14

                Подобным образом писать можно код на Java, но не на С++. Если уж ты пишешь на С++ будь добр, прочти несколько книг по некромантии, прежде чем делать что-то. Если писать «для идиотов» на С++, смысл в существовании С++ отпадает. Это язык грязных хаков и скорости, здесь не место идиотам.


                1. Antervis
                  13.09.2018 15:04
                  -1

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


            1. WinPooh73
              12.09.2018 18:21

              Только не идиотами, а "… склонным к насилию психопатом, который знает, где вы живёте" (с)


          1. apangin
            11.09.2018 14:59

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


    1. Antervis
      10.09.2018 20:07
      +1

      одно дело если бы проблемы были вызваны багами разных компиляторов тех или иных версий, а другое — баги из за того что UB в коде по разному ими интерпретируется


    1. PastorGL
      10.09.2018 23:12
      +1

      PS C:\Users\x> java -Xinternalversion
      Java HotSpot(TM) 64-Bit Server VM (25.181-b13) for windows-amd64 JRE (1.8.0_181-b13), built on Jul 7 2018 04:01:33 by "java_re" with MS VC++ 10.0 (VS2010)

      И в самом деле, некроманты.


    1. mspain
      11.09.2018 06:52
      +1

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

      Ибо, в Арче:

      OpenJDK 64-Bit Server VM (25.181-b13) for linux-amd64 JRE (1.8.0_181-b13), built on Aug 10 2018 18:36:50 by «builduser» with gcc 8.2.0


      1. Prototik
        11.09.2018 12:06
        +2

        Вообще да, странно, на арче openjdk8 собирается без всяких патчей:

        Скрытый текст
        build() {
          cd jdk8u-${_repo_ver}
        
          unset JAVA_HOME
          # http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=1346
          export MAKEFLAGS=${MAKEFLAGS/-j*}
        
          # We filter out -O flags so that the optimization of HotSpot is not lowered from O3 to O2
          export CFLAGS="${CFLAGS//-O2/-O3} ${CPPFLAGS} -Wno-error=deprecated-declarations -Wno-error=stringop-overflow= -Wno-error=return-type -Wno-error=cpp -fno-lifetime-dse -fno-delete-null-pointer-checks"
          export CXXFLAGS="${CXXFLAGS} ${CPPFLAGS}"
        
          install -d -m 755 "${srcdir}/${_prefix}/"
          sh configure     --prefix="${srcdir}/${_prefix}"     --with-update-version="${_jdk_update}"     --with-build-number="b${_jdk_build}"     --with-milestone="fcs"     --enable-unlimited-crypto     --with-zlib=system     --with-extra-cflags="${CFLAGS}"     --with-extra-cxxflags="${CXXFLAGS}"     --with-extra-ldflags="${LDFLAGS}"
        
          # TODO OpenJDK does not want last version of giflib (add 'giflib' as dependency once fixed)
          #--with-giflib=system 
          # These help to debug builds: LOG=trace HOTSPOT_BUILD_JOBS=1
          # Without 'DEBUG_BINARIES', i686 won't build: http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-July/019203.html
          make
          make docs
        
          # FIXME sadly 'DESTDIR' is not used here!
          make install
        
          cd ../${_imgdir}
        
          # A lot of build stuff were directly taken from
          # http://pkgs.fedoraproject.org/cgit/java-1.8.0-openjdk.git/tree/java-1.8.0-openjdk.spec
        
          # http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=1437
          find . -iname '*.jar' -exec chmod ugo+r {} \;
          chmod ugo+r lib/ct.sym
        
          # remove redundant *diz and *debuginfo files
          find . -iname '*.diz' -exec rm {} \;
          find . -iname '*.debuginfo' -exec rm {} \;
        }


  1. chemtech
    10.09.2018 20:01
    +2

    olegchir Можно вам или сообществу задать несколько вопросов по Java:
    — Насколько сейчас отличается OpenJDK и OracleJDK?
    — Ваше мнение о будущем одна OracleJDK?
    — Например, возможно ли объединение OpenJDK + OracleJDK?
    — Если да, сообщество говорит какие-либо сроки?
    — Что вы думаете о использовать JDK на CentOS vs Ubuntu?
    — Когда стоит переводить тест/прод на Java 11?
    Спасибо


    1. olegchir Автор
      10.09.2018 20:04

      Так вроде ж нет больше никакого OracleJDK? Есть оракловская сборка с расширенной поддержкой и подпиской, для использования которй в проде нужен саппорт контракт. С тем же успехом можно взять сборочку от Azul или RedHat, которые тоже будут оказывать поддержку патчами, но на халяву.

      Какой линукс используется для джавы, в целом, наплевать. Если нравится все новое — то Ubuntu, если все протухшее но стабильное — CentOS.

      Переводить тест на JDK 11 нужно было вчера.


      1. ExplosiveZ
        10.09.2018 20:06
        +1

        Заменит ли GraalVM OpenJDK?


        1. olegchir Автор
          10.09.2018 20:14
          +1

          Нет, GraalVM — это совершенно отдельный продукт со своими целями, разрабатываемый в Oracle Labs.

          Существует проект Metropolis: Java-on-Java. Пока он не сильно продвинулся. Полагаю, потому, что все заинтересованные (типа Джона Роуза) сейчас заняты другими делами, выпуском новой LTS версии, например :)

          Вот Metropolis скорей всего будет очень сильно зависеть от того, какие открытия произойдут в ходе разработки GraalVM. В частности, в OpenJDK уже интегрирован Graal Compiler из GraalVM, являющийся единственной на данный момент поддерживаемой реализацией JVMCI в виде Java-on-Java, благодаря нему же появился AOT. Некоторые совсем новые идеи и даже драфты jep (такие как в моей прошлой статье про lazy static final поля) делаются с GraalVM в уме — lazy поля нужны в SubstrateVM, ибо новая фича по оттягиванию инициализации классов до рантайма по факту работает совсем не так как хотелось бы.

          Иначе говоря, имхо, GraalVM сыграет свою роль к общей картине всего, в общем историческом процессе. Но скорей всего есть и останется отдельным специализированным продуктом.


      1. chemtech
        10.09.2018 20:15
        +1

        Т.е. вот эта www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
        в коде не отличается от OpenJDK, кроме торговых знаков?


        1. olegchir Автор
          10.09.2018 20:20

          Я говорил про JDK 11. Чем отличаются OracleJDK и OpenJDK в версиях меньше 11, скорей всего, легко гуглится. Когда я интересовался вопросом, то быстро нагуглил) Насколько помню, там разница в шрифтах и патентах на них, рендерерах в свинге и джаваэффектсах, сановских криптографических алгоритмах и прочей такой фигне. Если ты просто пишешь вебсервисы на спринге, ничего из этого тебе, скорей всего, совершенно неинтересно. Бесятся люди корпоративной шизофренией, залочиваясь на всякие платные закрытые штуки — ну вот и ладненько, лишь бы самим в этот пир духа не вписаться.


        1. apangin
          10.09.2018 20:23
          +1

          В JDK 8 отличий было больше. Вот здесь можно почитать:
          stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk
          stackoverflow.com/questions/44335605/openjdk-vs-java-hotspotvm


      1. turbanoff
        11.09.2018 01:27
        +1

        Вот тут jdk.java.net/11 написано Oracle JDK builds. Значит, пока есть)


        1. lany
          11.09.2018 05:19

          Это ea, релиза же нету ещё. Релизной oracle jdk не будет бесплатно.


          1. olegchir Автор
            11.09.2018 13:55
            +1

            ну тут речь о том, будут ли они отличаться, будет ли «Oracle JDK» как некая отдельная сущность с дополнительными фичами. То что оно будет отличаться маркетинговой дурью, стоимостью продажи колбасы и длиной очереди — можно было не сомневаться :)))


  1. Siemargl
    10.09.2018 22:35

    Разве gcc7 стал стабильным? Даже 6 под вопросом.

    Так что решение собирать 5.4 вполне себе оправданно. Это к habr.com/company/jugru/blog/422861/#comment_19093509

    Интересно, с CLang что то подобное наблюдается или нет


    1. olegchir Автор
      10.09.2018 22:50

      На целевой конфигурации вообще 8.2. Версия 7.3 тут только потому, что она идет из коробки в Ubuntu LTS, и лень было заморачиваться. Заморачиваться с поддержкой 8.2 будем чуть позже.


    1. khim
      11.09.2018 01:33
      +1

      Интересно, с CLang что то подобное наблюдается или нет
      Это от разработчиков больше зависит, чем от компиляторщиков. Android сейчас пользует clang 6.0.2 (притом что вышел пока только 6.0.1), но 7го пока боится…


    1. 0xd34df00d
      11.09.2018 18:43

      У меня в генте 7.3 уже штабильный.

      Сам свой софт тоже gcc 7 собираю, на доступных платформах перешёл на gcc 8 (на gcc 7 есть забавный баг с ворнингами на linkage в лямбдах в некоторых крайних случаях, который меня печалил). clang у меня вообще из транка последние года четыре, полёт нормальный, брат жив, единственные баги, которые я находил — ICE на наркоманском коде.


  1. saag
    11.09.2018 07:28
    +1

    Любой джавист в конце концов начинает писать только на C++…

    Ностальгия что ли? Вот так начинаешь изучение с С++, тебя бесят указатели, вручную память выделять надо и освобождать тож, а потом берешь Java и счастье то какое — не надо всем этим заморачиваться, хотя с другой стороны получение строки с ввода выливалось в три строки кода…


    1. Antervis
      11.09.2018 10:22

      вопрос скорее в назначении джавы как языка. Если ограничиваться одной технологией для всего стека задач — java хороший выбор. Но он уступает связке python/с++ (и подобным) — в качестве языка для написания performance critical кусков java в сравнении с плюсами — полумера, в качестве языка общего назначения он не проще питона.


      1. olegchir Автор
        11.09.2018 22:02
        -1

        думаю что Java — это про высокопроизводительный безопасный код, работающий в условиях высокой конкарренси и за shared mutable state. То есть, это системные вещи типа написания баз данных, про кластера, обработку Big Data, про Machine Learning, и так далее.

        у Python совершенно другая ниша. Там есть некое пересечение по Machine Learning, но оно в основном работает на нативном (C/C++) коде, на питоне там только некая лайтовая обвязка. Да, Java потеряла в направлении ML преимущество, но это всё ещё починится. В отличие от питона, числодробилки можно писать на самой Java, переходя в натив только по случаю какого-то большого горя, — и это огромное конкурентное преимущество


        1. Antervis
          11.09.2018 23:21
          -1

          думаю что Java — это про высокопроизводительный безопасный код, работающий в условиях высокой конкарренси и за shared mutable state

          "And I write sleek performant low-overhead scala code with higher order functions that will run on anything" © Динеш, Кремниевая долина.

          Сейчас 2018-й год и баззвордами никого не удивишь. А вот индуса из стереотипов они напоминают

          То есть, это системные вещи типа написания баз данных, про кластера, обработку Big Data, про Machine Learning, и так далее.

          и много баз данных/ML фреймворков и обработчиков Big Data написано на джаве?

          В отличие от питона, числодробилки можно писать на самой Java

          А зачем, если есть питон, на котором эти числодробилки уже написаны, и под капотом у них не интерпретируемый, а нативный код на си или с++?


          1. olegchir Автор
            11.09.2018 23:41

            ну как бы Hadoop — это почти синоним BigData. Говоришь BigData — подразумеваешь Hadoop. Говоришь Hadoop — подразумеваешь BigData. Spark, Tez, Hive, Impala, HBase… Есть и ML штуки вроде Mahout и MLlib, но в ML как уже говорил пока есть временные пробелы и проблемы (которые потихоньку, надеюсь, будут рассасываться).

            > баззвордами

            это не баззворды, а осмысленные вещи

            > и под капотом у них не интерпретируемый, а нативный код на си или с++?

            2018 год на дворе, Java уже давно насквозь нативная, сейчас даже генерацию exe файлов пилят


          1. olegchir Автор
            11.09.2018 23:46

            более того, нативный компилятор Java переписывают на Java, чтобы там была «Java до самого низу». Всё что мы тут обсуждаем с какими-то правками UB в C++ будет потихоньку исчезать, и надеюсь через 10 лет кода на C/C++ мы в компиляторе почти уже не увидим. В новом компиляторе нативный код приемлемо писать на чистом Си.


            1. Antervis
              12.09.2018 09:48

              Похоже, я погорячился с BigData, а вы с ML.

              более того, нативный компилятор Java переписывают на Java, чтобы там была «Java до самого низу»

              Да, потому что java community проще поддерживать java-код, а не потому что «быстрее, выше, сильнее».


    1. iboltaev
      11.09.2018 11:44
      +2

      берешь Java и счастье то какое

      да тоже то еще счастье)


    1. 0xd34df00d
      11.09.2018 18:45

      вручную память выделять надо

      Сами менеджер хипа писали? Респееект! Но вообще можно было и как в джаве, просто new.

      освобождать тож

      Уже 7 лет как не рекомендуется почти что официально, и не рекомендовалось долгое время и до того.

      хотя с другой стороны получение строки с ввода выливалось в три строки кода…

      Ну, надо признать, iostreams и sstreams — одна из самых ненавидимых лично мной частей С++.


      1. iboltaev
        12.09.2018 13:55

        Уже 7 лет как не рекомендуется почти что официально, и не рекомендовалось долгое время и до того

        ага, в плюсах RAII — это, в общем-то, наше все. Кто в плюсах напишет явно delete (ну кроме очень узкого количества случаев), тот, в общем, нехорошо сделает


  1. haoNoQ
    11.09.2018 12:25
    +4

    For an object of non-POD class type… before the constructor begins execution… referring to any non-static member or base class of the object results in undefined behavior

    Чуть тупил в этом месте, поэтому хочу разжевать, вдруг ещё кому пригодится.


    В нашем примере (оригинальный код не осиливал) переменная _b является как раз static. Также она определена после a (форвард-объявление в классе не считается), находясь в той же самой единице трансляции (.cpp-файле). Следовательно, _b всегда будет строиться после a, это вполне определено, и именно поэтому у нас срабатывает оптимизация.


    Более того, поскольку _b у нас статитическая, в ней будет не мусор, а нули. Это тоже гарантировано.


    Соответственно, если метод .Show() действительно детерминированно не падает на залитом нулями объекте, то без оптимизации оно могло "стабильно" работать, пусть и неправильно.


    Ну и, казалось бы, если компилятор знает, что здесь точно 100% UB, то почему он не кинет предупреждение? Поспекулирую, что основывать логику предупреждений на логике оптимизации — это общепризнанно плохая идея. Разные проходы оптимизации могут очень нетривиально взаимодействовать друг с другом, и будет получаться так, что предупреждения будут зависеть от флагов оптимизации совершенно непредсказуемым образом, что дико. Поэтому ворнинг надо реализовывать отдельно, и, видимо, ни у кого руки не дошли пока.


    1. olegchir Автор
      11.09.2018 15:35

      Большое спасибо за разбор! А я думал, никто до туда не дочитает. Хабр торт.


    1. Wilk
      12.09.2018 10:04

      По сути — вариация на тему static initialisation order fiasco.


    1. iboltaev
      12.09.2018 14:03

      del


  1. yleo
    11.09.2018 12:48
    +1

    CFLAGS += -fno-strict-aliasing -fno-lifetime-dse -fno-delete-null-pointer-checks

    C++ плохой, потому что даже разработчики JDK не умею им пользоваться.
    olegchir, давай подписывайся под тезисом и можно будет расходится ;)


    1. olegchir Автор
      11.09.2018 13:13
      +1

      Это так толсто, что даже тонко :-)
      А про флаги… Любовь зла — полюбишь и козла!


  1. ZaMaZaN4iK
    11.09.2018 13:42
    +2

    Что-то мне подсказывает, что если собрать OpenJDK с санитайзерами и погонять нормально, откроется немало таких интересных мест.


    1. olegchir Автор
      11.09.2018 15:36

      А вот для этого у нас даже специальный человек имеется — Andrey2008 из PVS-Studio


  1. mOlind
    11.09.2018 15:53

    Прочитал с удовольствием. Следующий раз разгребая чужой код и бубня «твою-мать-ну-кто-так-пишет», буду думать еще «а не написать ли мне об этом на хабре».


  1. nRenaissance
    11.09.2018 16:05
    -2

    Любой джавист — в душе немного некромант, а может даже и некрофил.


    ebanoe.it/2018/01/31/java-necromancer

    </несмешно>


  1. AlexMal
    11.09.2018 16:43

    Сборка прошла, сгенерированы экзешники, мы молодцы.

    Я надеюсь, что это метафора?
    Потому что на кроссплатформенную компиляцию не похоже.


    1. olegchir Автор
      11.09.2018 16:46

      Это сборка не прикладного java-приложения, а самой джавы — виртуальной машины итп. Логично, что исполняемые файлы виртуальной машины не будут переносимыми между платформами.