Eu queria dormir hoje, mas novamente falhei. Apareceu uma mensagem no Telegram de que alguém não estava indo para Java ... e acordamos apenas depois de algumas horas, cansados e felizes.

Quem pode usar esta postagem? Sim, provavelmente para qualquer pessoa, exceto aqueles que também colecionam JDK8 ou adoram ler horrores de pesadelos. Em geral, eu o avisei, feche o artigo com urgência.
Três problemas:
- Não vai ( nível um )
Uma parte muito chata para pular. Necessário apenas para quem deseja restaurar completamente a história dos eventos; - Não vai ( nível dois )
É mais interessante porque existem alguns erros típicos, necromancia e necrofilia, que o BSD é melhor que o GNU / Linux e por que vale a pena mudar para novas versões do JDK. - Mesmo indo, cai na crosta
Mais interessante. Yahuuu, a JVM caiu na crosta, vamos chutá-la!
Sob o gato mostra uma solução detalhada para os problemas, com diferentes pensamentos laterais sobre a vida.
Haverá muito C ++, não haverá código Java. Qualquer javist no final começa a escrever apenas em C ++ ...
Não vai
Quem construiu o Java pelo menos uma vez sabe que ele se parece com isso:
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
(Todos os meus usuários são simplesmente chamados de "eu", para que você possa entregar a máquina virtual a qualquer pessoa a qualquer momento e não criar uma rejeição ao usar seu próprio nome de usuário)
O problema, é claro, é que isso não funciona. E de uma maneira bastante cínica.
Primeiro nível de mergulho
Vamos tentar executar:
/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) { ^~~~~~~~~
Primeiro, para você entender, eu instalei isso:
$ g++ --version g++ (Ubuntu 7.3.0-16ubuntu3) 7.3.0 Copyright (C) 2017 Free Software Foundation, Inc.
O compilador não é o primeiro frescor, não 8.2, mas este também deve funcionar.
Os desenvolvedores de C ++ gostam de testar o software apenas na versão do compilador que eles instalaram. Normalmente, o desejo de testar em plataformas diferentes termina em algum ponto da região entre a diferença entre gcc e clang em um sentido geral. Portanto, é bastante normal executar primeiro -Werror
("tratar avisos como erros") e depois escrever um código que em todas as outras versões será considerado vorings.
Esse é um problema conhecido e está claro como resolvê-lo. Você precisa definir sua variável de ambiente CXX_FLAGS, na qual definir o nível correto de erros.
export CXX_FLAGS=-Wno-error=deprecated-declarations -Wno-error-deprecated-declarations
E então vemos o maravilhoso:
Ignoring CXXFLAGS found in environment. Use --with-extra-cxxflags
Ok, construa o sistema, o que você quiser! Substituímos o configure por este:
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
E o erro permanece o mesmo!
Passamos para a artilharia pesada: o código fonte.
grep -rl "Werror" .
Uma enorme quantidade de qualquer chapéu gerado automaticamente cai, entre os quais há vislumbres de arquivos significativos:
./common/autoconf/flags.m4 ./hotspot/make/bsd/makefiles/gcc.make ./hotspot/make/solaris/makefiles/gcc.make ./hotspot/make/aix/makefiles/xlc.make
No flags.m4
, encontramos facilmente a mensagem anterior sobre “Ignorando CXXFLAGS” e o sinalizador codificado mais maduro CCXX_FLGS
(sim, duas letras C), que atua imediatamente em vez de CFLAGS
e em vez de XX_FLAGS
. Convenientemente! Dois fatos são interessantes:
- Este sinalizador não é passado pelos parâmetros de configuração;
- No valor padrão são significativos e suspeitosamente semelhantes a estes parâmetros:
Essa pergunta parece muito legal nos comentários - mas o que são as bandeiras comuns? Certo?
Não jogaremos a democracia e a codificamos autoritariamente nela - -w
("não mostra nenhum erro"):
CCXXFLAGS_JDK="$CCXXFLAGS $CCXXFLAGS_JDK -w -ffreestanding -fno-builtin -Wno-parentheses -Wno-unused -Wno-unused-parameter -Wformat=2 \
E - Saúde! - o primeiro erro que passamos. Ela não está mais relatando e, em geral, está tudo bem. Parece.
Segundo nível de mergulho
Mas agora está caindo em uma pilha de outros novos lugares!
Acontece que nosso -w
funciona, mas não é encaminhado para todas as partes da montagem. Lemos cuidadosamente os makefiles e não entendemos como exatamente esse parâmetro pode ser encaminhado. Esqueceu mesmo dele?
Conhecendo a pergunta correta do Google ("por que o cxx não chega ao build?!"), Rapidamente chegamos à página de erros com o ditado "configure --with-extra-cxxflags não afeta o hotspot" ( JDK-8156967 ).
Qual promessa será corrigida no JDK 12. Talvez. Maravilhoso - o parâmetro de construção mais importante não é usado na montagem!
A primeira idéia é, bem, vamos arregaçar as mangas e corrigir os erros!
Erro 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]
Bem, provavelmente precisamos ampliar a região. Cem libras, alguém calculou o buffer clicando no botão "Tenho sorte!" no google.
Mas como você entenderia o quanto precisa? Há outro tipo de refinamento abaixo:
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 ());
A posição 12 parece algo que vale a pena, com o qual agora você pode entrar na fonte com os pés sujos.
Subimos para dependencies.cpp
e observamos a seguinte figura:
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()); } }
Preste atenção à linha problemática:
char xn[10]; sprintf(xn, "x%d", j);
Mudamos de 10 para 12, remontamos e ... a montagem acabou!
Mas eu sou o único tão inteligente e consertou o bug de todos os tempos? Sem dúvida, novamente direcionamos nosso megapatch para o Google: char xn[12];
E nós vemos ... sim, está certo. O bug JDK-8184309 , banido por Vladimir Ivanov, contém exatamente a mesma correção.
Mas o ponto principal é que ele é corrigido apenas no JDK 10 e o nifiga não é suportado para o jdk8u. Essa é a questão de por que novas versões do Java são necessárias.
Erro 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; }
Ensinado pela experiência amarga anterior, vamos imediatamente ver o que há neste local no JDK 11. E ... esse arquivo não está lá. A estrutura de diretórios também passou por alguma refatoração.
Mas você não pode simplesmente ficar longe de nós!
Qualquer javista é um pouco necromante em sua alma, e talvez até um necrófilo. Portanto, agora haverá NECROMÂNCIA EM AÇÃO!
Primeiro você precisa apelar para a alma dos mortos e descobrir quando ele morreu:
$ 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
Agora você precisa descobrir a causa de sua morte:
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
Então, nós temos um assassino: gziemski . Vamos descobrir por que ele pregou esse arquivo infeliz.
Para fazer isso, vá para fat no ticket especificado no resumo da confirmação. Este é o JDK-8173715 :
Remova o FlatProfiler:
Assumimos que essa tecnologia não está mais em uso e é uma fonte de varredura raiz para o GC.
Para shih bis. De fato, agora somos convidados a reparar o cadáver apenas para que a compilação esteja em andamento. Que se decompôs tanto que até nossos colegas necromantes do OpenJDK o abandonaram.
Vamos ressuscitar o morto e tentar perguntar a ele o que ele lembrava por último. Ele já estava morto na revisão 47106, o que significa que há um a menos na revisão - isto é "um segundo antes":
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
Infelizmente, nada sobre o return strcmp(name, _name) == 0;
no diff não. O paciente morreu de um golpe com um objeto pontiagudo (utilidade rm), mas na hora da morte ele já estava em estado terminal.
Vamos nos aprofundar na essência do erro.
Aqui está o que o autor do código gostaria de nos dizer:
const char *name() const { return _name; } bool is_compiled() const { return true; } bool vm_match(const char* name) const { return strcmp(name, _name) == 0; }
Agora um pouco de filosofia.
O padrão C11 na cláusula 7.1.4, “Uso de funções da biblioteca”, diz explicitamente:
Cada uma das seguintes instruções se aplica, a menos que explicitamente indicado de outra forma nas descrições detalhadas a seguir: Se um argumento para uma função tiver um valor inválido (como [...] um ponteiro nulo [...]) [...], o comportamento é indefinido.
Ou seja, agora toda a questão é se há algum "explicitamente declarado o contrário" . Nada desse tipo está escrito na descrição do strcmp
na seção 7.24.4 e não tenho outras seções para você.
Ou seja, temos um comportamento indefinido aqui.
Obviamente, você pode pegar e reescrever esse pedaço de código, envolvendo-o com a verificação. Mas não tenho certeza absoluta de que entendi corretamente a lógica das pessoas que usam o UB onde não deveria estar. Por exemplo, alguns sistemas geram SIGSERV para desreferenciação zero e um amante de hackers pode tirar proveito disso, mas esse comportamento não é obrigatório e pode iniciar em outra plataforma.
Sim, é claro, alguém dirá que você é um tolo, que está usando o GCC 7.3, mas no GCC 4 tudo teria se reunido. Mas comportamento indefinido! = Não especificado! = Implementação definida. Isso nos últimos dois pode ser definido para funcionar no compilador antigo. E UB na sexta versão foi UB.
Em resumo, fiquei completamente triste com essa complexa questão filosófica (devo entrar no código com minhas suposições) quando subitamente percebi que poderia ser diferente.
Existe outra maneira
Como você sabe, bons heróis sempre andam por aí.
Mesmo que ignoremos nossa filosofia sobre o UB, há uma quantidade incrível de problemas por lá. Não é o fato de que eles podem ser reparados até de manhã. Não é o fato de que não posso fazê-lo com minhas mãos tortas. Ainda menos é o fato de que isso será aceito no upstream: o último patch no jdk8u foi há 6 semanas e essa foi a mesclagem global da nova tag.
Imagine que o código acima está realmente escrito corretamente. Tudo o que está entre nós e sua execução é um aviso, que foi percebido como erro devido a um bug no sistema de compilação. Mas podemos construir um sistema de compilação.
O Witcher Geralt de Rivia disse uma vez:
"O mal é mau, Stregobor", o bruxo disse gravemente, levantando-se. - Menor, maior, média - tudo é igual, as proporções são arbitrárias e as bordas são borradas. Eu não sou um eremita sagrado, não apenas fiz um bem na minha vida. Mas se você tiver que escolher entre um mal e outro, prefiro não escolher.
- Zło a 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, também escreve para wybierać wcale.
Esta é uma citação de The Last Wish, uma história chamada Lesser Evil. Todos nós sabemos que Geralt quase nunca poderia desempenhar o papel de um personagem verdadeiramente neutro, e até morreu devido a outro comportamento clássico caótico.
Então, vamos fazer uma rápida demonstração do mal menor. Vamos sobre o sistema de compilação.
No começo, já vimos esse escape:
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
Comparando esses dois arquivos, eu quebrei o rosto inteiro com o facespalm e percebi a diferença na cultura das duas plataformas:
BSD é uma história sobre liberdade e escolha:
# Compiler warnings are treated as errors ifneq ($(COMPILER_WARNINGS_FATAL),false) WARNINGS_ARE_ERRORS = -Werror endif
GNU / Linux é um regime purista autoritário:
Bem, seria encaminhado para o linux via XX_FLAGS
, essa variável não é levada em consideração ao calcular WARNINGS_ARE_ERRORS
! Na compilação para GNU / Linux, simplesmente não temos escolha a não ser seguir os padrões que foram lançados de cima.
Bem, ou você pode facilitar e alterar o valor de WARNINGS_ARE_ERRORS
para um curto, mas não menos poderoso -w
. Como você gosta disso, Elon Musk?
Como você deve ter adivinhado, isso resolve completamente esse problema de compilação.
Quando o código é montado, você vê um monte de problemas estranhos e terríveis que passam voando. Às vezes, era tão assustador que eu realmente queria pressionar ctrl + C e tentar descobrir. Mas não, você não pode, você não pode ...
Parece que tudo se reuniu e não trouxe problemas adicionais. Embora, é claro, não ousei começar a testar. Ainda assim, noite, meus olhos começam a ficar juntos, e de alguma forma eu não quero ir para o último recurso - quatro latas de energia da geladeira.
Cai na crosta
A montagem passou, os executáveis foram gerados, somos ótimos.
E assim chegamos à linha de chegada. Ou não veio?
Nossa montagem está da seguinte maneira:
export JAVA_HOME=~/git/jdk8u/build/linux-x86_64-normal-server-fastdebug/jdk export PATH=$JAVA_HOME/bin:$PATH
Quando você tenta executar o executável java
, ele falha instantaneamente. Para aqueles que não estão familiarizados - é algo como isto:

Ao mesmo tempo, Alex possui o Debian 9.5 e eu tenho o Ubuntu. Duas versões diferentes do GCC, duas crostas de aparência diferentes. Tenho brincadeiras inocentes com o patch manual strcmp e mais alguns lugares, Alex não. Qual é o problema?
Esta história é digna de uma história separada, mas aqui vamos direto para as conclusões secas, caso contrário, nunca adicionarei este post.
O problema é que nossos pogromists favoritos de C ++ usaram novamente comportamento indefinido.
(Além disso, onde de alguma maneira desconhecida depende da implementação do compilador. No entanto, devemos lembrar que UB é sempre UB, mesmo em uma versão conhecida do compilador, é impossível colocá-lo)
Em um lugar, nos voltamos para o campo de uma classe sub-projetada lá, e tudo quebra. Não pergunte como aconteceu, tudo é complicado.
É muito difícil para um javista imaginar como se pode recorrer a uma classe subconstruída, exceto emitindo um link para ela diretamente do construtor. Felizmente, a maravilhosa linguagem C ++ pode fazer tudo ou quase tudo. Vou escrever um exemplo com um certo pseudocódigo:
class A { A() { _b.Show(); } private: static B _b; }; A a; BA::_b; int main() { }
Tenha um bom debug!
Se você observar o C ++ 98 [class.cdtor]:
Para um objeto do tipo de classe não POD ... antes que o construtor inicie a execução ... a referência a qualquer membro não estático ou classe base do objeto resulta em comportamento indefinido
Começando com o GCC de alguma versão (e eu tenho o 7.3), apareceu uma otimização da "eliminação da vida útil de lojas mortas", que acredita que nos referimos a um objeto apenas durante o tempo de vida e tudo tosse fora do tempo de vida.
A solução é desativar as novas otimizações e retornar como estavam no antigo GCC:
CFLAGS += -fno-strict-aliasing -fno-lifetime-dse -fno-delete-null-pointer-checks
Há uma discussão sobre isso aqui .
Por alguma razão, os participantes da discussão decidiram que isso não seria incluído no montante. Mas você ainda precisa tentar enviá-lo.
Adicione essas opções ao nosso ./hotspot/make/linux/makefiles/gcc.make
, ./hotspot/make/linux/makefiles/gcc.make
tudo novamente e veja as linhas queridas:
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)
Conclusão
Você provavelmente pensou que a conclusão seria: “Java é algum tipo de inferno, há lixo no código, não há suporte, tudo está ruim.”
Isto não é assim! Pelo contrário, os exemplos acima mostram que mal terrível nossos amigos, necromantes do OpenJDK, estão nos impedindo.
E apesar do fato de que eles precisam viver e usar C ++, tremer com cada UB e alterar a versão do compilador e aprender as sutilezas das plataformas, o código de usuário final em Java é incrivelmente estável e em construções postadas em sites oficiais de empresas como Azul, A Red Hat e a Oracle dificilmente podem se deparar com a crosta em um caso simples.
A única coisa triste é que, provavelmente, os erros encontrados provavelmente não serão aceitos no jdk8u. Pegamos o JDK 8 simplesmente porque é mais fácil corrigi-lo aqui e agora, e teremos que lidar com o JDK 11. No entanto, usar o JDK 8 em 2018 é IMHO, essa é uma prática muito ruim, e não fazemos isso de uma vida boa. Talvez nossa vida melhore no futuro, e você lerá muitas outras histórias incríveis do mundo do JDK 11 e JDK 12.
Obrigado pela atenção prestada a um texto tão chato sem fotos :-)
Minuto de publicidade. A Conferência Joker 2018 será realizada muito em breve, onde haverá muitos especialistas de destaque em Java e JVM. Veja a lista completa de palestrantes e relatórios no site oficial . Eu também estarei lá, será possível conhecer e trabalhar por toda a vida e pelo OpenJDK.