company_banner

О сборке JDK 8 на Ubuntu, качестве кода Hotspot и почему всё валят на C++

    Хотел сегодня поспать, но опять не удалось. В Телеграме появилось сообщение, что у кого-то не собирается 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 по причине бага в системе сборки. Но ведь мы можем похачить систему сборки.


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


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

    — Zło to zło, Stregoborze — rzekł poważnie wiedźmin wstając. — Mniejsze, większe, średnie, wszystko jedno, proporcje są umowne a granice zatarte. Nie jestem świątobliwym pustelnikiem, nie samo dobro czyniłem w życiu. Ale jeżeli mam wybierać pomiędzy jednym złem a drugim, to wolę nie wybierać 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.
    JUG.ru Group
    711.72
    Конференции для программистов и сочувствующих. 18+
    Share post

    Comments 71

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

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

        Правильно, нечего спать, пришло время собирать! Попробуй повторить манипуляции и собрать все у себя на локальной системе новым компилятором :)
        0
        Кровавая плата за то, что жабка самая быстрая из всех языков второго сорта (за первый примем си и плюсы).
          0
          а как же макроассемблер?
            +10
            куски кода общего назначения компилятор скорее всего оптимизнет лучше человека
            +3
            Rust же еще.
              0
              C#, не?
              +14
              еще раз убеждаемся, что если бы джависты умели писать на плюсах, на них бы они и писали
                +7
                В 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
                  Ну, наши-то патчи (ты помнишь какие) сейчас работают только с JDK 8, поэтому придется поддерживать все что есть, пока не портируем вперед
                  • UFO just landed and posted this here
                      +3
                      Во как! Стало быть, в свежей Убунте весь этот путь уже прошли. Тогда, чтобы играться с OpenJDK на Ubuntu, логичней брать исходники не с openjdk.java.net, а с launchpad.net, где все необходимые для компиляции патчи уже сделаны.
                        0
                        В Manjaro и вовсе gcc 8.2.0.
                          0
                          Так и у меня GCC 8 на целевой тачке. На убунте и 7.3 запустил просто чтобы покопаться кой в чем на интеле, это не конечная цель.
                      +3

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


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

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

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

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

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

                        Я ни в коем случае не хочу оспаривать паттерны написания корректного кода, но в данном конкретном примере сразу понятно, что хотел выразить автор, и что никаких багов с переполнением буфера здесь быть не может. То, что более свежий gcc стал выдавать предупреждение — да, стоит поправить, но делать из этого выводы, что написана какая-то фигня, будет преждевременно.
                          0
                          Времени, кстати, было потрачено минимум :-) Вот написание статьи — это да, чертова уйма времени. А еще на комментарии отвечать.
                            0
                            >Переменная 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
                              +2
                              Большая проблема в том, что люди думают в рамках некой «элитарной логики»: что вот этот код будут править люди, которые понимают и разбираются, и исходя из этого тут всё очевидно.

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

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

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

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

                              Когда код не написан в таком стиле, мне становится плохо и больно. (В том числе и потому, что я сам ленивый идиот).
                                +2
                                Не «паттерны написания корректного кода», а паттерны написания кода для последующего чтения и модификацией идиотами.

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

                                  +2
                                  Я серьезно собираюсь написать такую книгу или серию статей. Похоже, люди совсем обленились и не понимают, как правильно писать говнокод :-)
                                    +1
                                    Подобным образом писать можно код на Java, но не на С++. Если уж ты пишешь на С++ будь добр, прочти несколько книг по некромантии, прежде чем делать что-то. Если писать «для идиотов» на С++, смысл в существовании С++ отпадает. Это язык грязных хаков и скорости, здесь не место идиотам.
                                      –1
                                      вы, полагаю, удивитесь узнав, что быстродействия можно достигать и без грязных хаков, которые в свою очередь являются лишь плодом работы тех самых идиотов
                                    0
                                    Только не идиотами, а "… склонным к насилию психопатом, который знает, где вы живёте" (с)
                                    0
                                    Полностью согласен с этими тезисами, хотя и не вижу, как они относятся к данной ситуации. Как следовало написать код вместо фрагмента, который тебе так не понравился?
                                  0
                                  и вообще использование универсальной библиотечной функции там, где должна быть специальная (itoa).

                                  itoa() не является стандартной функцией. Так что её неиспользование вполне оправдано.
                                    +1

                                    А вот std::to_string уже 7 лет как является стандартной.

                                      0
                                      Ничего не имею против std::to_string(), если проект позволяет этот стандарт.
                                      Я имел ввиду, что старый добрый sprintf() тоже нормальное решение:
                                      надежно, си-совместимо (кому это актуально), везде будет работать.
                                  +2
                                  одно дело если бы проблемы были вызваны багами разных компиляторов тех или иных версий, а другое — баги из за того что UB в коде по разному ими интерпретируется
                                    +2
                                    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
                                      Полагаю, что убунтоиды взяли пакет из Дебиана. А у дебиана их фирменная штабильность головного мозга :)

                                      Ибо, в Арче:

                                      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

                                        +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 {} \;
                                        }

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

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

                                        Переводить тест на JDK 11 нужно было вчера.
                                          +1
                                          Заменит ли GraalVM OpenJDK?
                                            +2
                                            Нет, 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
                                            Т.е. вот эта www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
                                            в коде не отличается от OpenJDK, кроме торговых знаков?
                                              0
                                              Я говорил про JDK 11. Чем отличаются OracleJDK и OpenJDK в версиях меньше 11, скорей всего, легко гуглится. Когда я интересовался вопросом, то быстро нагуглил) Насколько помню, там разница в шрифтах и патентах на них, рендерерах в свинге и джаваэффектсах, сановских криптографических алгоритмах и прочей такой фигне. Если ты просто пишешь вебсервисы на спринге, ничего из этого тебе, скорей всего, совершенно неинтересно. Бесятся люди корпоративной шизофренией, залочиваясь на всякие платные закрытые штуки — ну вот и ладненько, лишь бы самим в этот пир духа не вписаться.
                                                +4
                                                В JDK 8 отличий было больше. Вот здесь можно почитать:
                                                stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk
                                                stackoverflow.com/questions/44335605/openjdk-vs-java-hotspotvm
                                                +1
                                                Вот тут jdk.java.net/11 написано Oracle JDK builds. Значит, пока есть)
                                                  +1

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

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

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

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

                                                0
                                                На целевой конфигурации вообще 8.2. Версия 7.3 тут только потому, что она идет из коробки в Ubuntu LTS, и лень было заморачиваться. Заморачиваться с поддержкой 8.2 будем чуть позже.
                                                  +1
                                                  Интересно, с CLang что то подобное наблюдается или нет
                                                  Это от разработчиков больше зависит, чем от компиляторщиков. Android сейчас пользует clang 6.0.2 (притом что вышел пока только 6.0.1), но 7го пока боится…
                                                    +1
                                                    У меня в генте 7.3 уже штабильный.

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

                                                    Ностальгия что ли? Вот так начинаешь изучение с С++, тебя бесят указатели, вручную память выделять надо и освобождать тож, а потом берешь Java и счастье то какое — не надо всем этим заморачиваться, хотя с другой стороны получение строки с ввода выливалось в три строки кода…
                                                      0
                                                      вопрос скорее в назначении джавы как языка. Если ограничиваться одной технологией для всего стека задач — java хороший выбор. Но он уступает связке python/с++ (и подобным) — в качестве языка для написания performance critical кусков java в сравнении с плюсами — полумера, в качестве языка общего назначения он не проще питона.
                                                        0
                                                        думаю что Java — это про высокопроизводительный безопасный код, работающий в условиях высокой конкарренси и за shared mutable state. То есть, это системные вещи типа написания баз данных, про кластера, обработку Big Data, про Machine Learning, и так далее.

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

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

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

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

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

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

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

                                                                Да, потому что java community проще поддерживать java-код, а не потому что «быстрее, выше, сильнее».
                                                              +1
                                                              Я бы не стал писать высокопроизводительный машиннообучаемый (или обучающий?) код на джаве. Я знаю плюсы, умею выжимать из плюсов, ну, много, и по памяти оно работает заведомо лучше (потому что у GC есть оверхед, и чем меньше лишней памяти ему доступно, тем больше ресурсов он жрёт).

                                                              А не настолько требовательный к производительности код я предпочту писать на каком-нибудь хаскеле. Там всё куда безопаснее и надёжнее. Тем более, что есть Frege и Eta, если хочется прям на JVM бегать.
                                                            +2
                                                            берешь Java и счастье то какое

                                                            да тоже то еще счастье)
                                                              +2
                                                              вручную память выделять надо

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

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

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

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

                                                              Ну, надо признать, iostreams и sstreams — одна из самых ненавидимых лично мной частей С++.
                                                                0
                                                                Уже 7 лет как не рекомендуется почти что официально, и не рекомендовалось долгое время и до того

                                                                ага, в плюсах RAII — это, в общем-то, наше все. Кто в плюсах напишет явно delete (ну кроме очень узкого количества случаев), тот, в общем, нехорошо сделает
                                                              +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, то почему он не кинет предупреждение? Поспекулирую, что основывать логику предупреждений на логике оптимизации — это общепризнанно плохая идея. Разные проходы оптимизации могут очень нетривиально взаимодействовать друг с другом, и будет получаться так, что предупреждения будут зависеть от флагов оптимизации совершенно непредсказуемым образом, что дико. Поэтому ворнинг надо реализовывать отдельно, и, видимо, ни у кого руки не дошли пока.

                                                                0
                                                                Большое спасибо за разбор! А я думал, никто до туда не дочитает. Хабр торт.
                                                                  0
                                                                  По сути — вариация на тему static initialisation order fiasco.
                                                                    0
                                                                    del
                                                                    +1
                                                                    CFLAGS += -fno-strict-aliasing -fno-lifetime-dse -fno-delete-null-pointer-checks

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

                                                                      +1
                                                                      Это так толсто, что даже тонко :-)
                                                                      А про флаги… Любовь зла — полюбишь и козла!
                                                                      +2
                                                                      Что-то мне подсказывает, что если собрать OpenJDK с санитайзерами и погонять нормально, откроется немало таких интересных мест.
                                                                        0
                                                                        А вот для этого у нас даже специальный человек имеется — Andrey2008 из PVS-Studio
                                                                        0
                                                                        Прочитал с удовольствием. Следующий раз разгребая чужой код и бубня «твою-мать-ну-кто-так-пишет», буду думать еще «а не написать ли мне об этом на хабре».
                                                                          –3
                                                                          Любой джавист — в душе немного некромант, а может даже и некрофил.


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

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

                                                                            Я надеюсь, что это метафора?
                                                                            Потому что на кроссплатформенную компиляцию не похоже.
                                                                              0
                                                                              Это сборка не прикладного java-приложения, а самой джавы — виртуальной машины итп. Логично, что исполняемые файлы виртуальной машины не будут переносимыми между платформами.
                                                                              0
                                                                              Спасибо большое) Долго искал куда там воткнуть правильно эти флаги) Только баги нашёл) Наконец-то собралось на родной системе) Ура-ура!
                                                                                0
                                                                                Не увидел в комментариях. В сборке JDK9+ добавили флаг
                                                                                --disable-warnings-as-errors
                                                                                Который делает именно это: не включает режим «предупреждения-как-ошибки».

                                                                                Ну а в целом — да. Продвинутые джава-программисты в будущем станут еще и С++-программистами. Graal, Panama и всё вокруг будет приятно располагать. Нет, С++ сейчас совсем не такой страшный, каким был во времена создания Java. Это будет просто новый уровень погружения в технологию, который многие захотят пройти.

                                                                                Only users with full accounts can post comments. Log in, please.