Por que é permitido executar código Java em comentários com certos caracteres Unicode?

1356

O código a seguir produz a saída "Hello World!" (realmente não, tente).

public static void main(String... args) {

   // The comment below is not a typo.
   // \u000d System.out.println("Hello World!");
}

A razão para isso é que o compilador Java analisa o caractere Unicode \u000dcomo uma nova linha e é transformado em:

public static void main(String... args) {

   // The comment below is not a typo.
   //
   System.out.println("Hello World!");
}

Assim, resultando em um comentário sendo "executado".

Como isso pode ser usado para "ocultar" códigos maliciosos ou o que um programador mal possa conceber, por que é permitido nos comentários ?

Por que isso é permitido pela especificação Java?

Reg
fonte
44
"Por que isso é permitido" parece ser muito baseado em opiniões para mim. Os designers da linguagem tomaram uma decisão, o que mais é preciso saber? A menos que você encontre uma declaração da pessoa que está tomando essa decisão, podemos apenas especular.
Ingo Bürk
194
Uma coisa interessante é, pelo menos que IDE do OP, obviamente, erra e exibe destaque incorreta,
dhke
14
Possivelmente relacionado: stackoverflow.com/questions/4448180/…
dhke
47
@Tobb Mas os designers de Java estão visitando o SO, portanto é possível obter respostas de um deles. Também podem existir recursos que já respondem a essa pergunta.
Pshemo
41
A resposta simples é que o código não está em um comentário, de acordo com as regras da linguagem, portanto a pergunta está mal formada.
Marquês de Lorne

Respostas:

741

A decodificação Unicode ocorre antes de qualquer outra tradução lexical. O principal benefício disso é que torna trivial a alternância entre ASCII e qualquer outra codificação. Você nem precisa descobrir onde os comentários começam e terminam!

Conforme declarado na Seção 3.3 do JLS, isso permite que qualquer ferramenta baseada em ASCII processe os arquivos de origem:

[...] A linguagem de programação Java especifica uma maneira padrão de transformar um programa escrito em Unicode em ASCII que altera um programa em um formulário que pode ser processado por ferramentas baseadas em ASCII. [...]

Isso fornece uma garantia fundamental para a independência da plataforma (independência dos conjuntos de caracteres suportados), que sempre foi um objetivo principal da plataforma Java.

Ser capaz de escrever qualquer caractere Unicode em qualquer lugar do arquivo é um recurso interessante e especialmente importante nos comentários ao documentar o código em idiomas não latinos. O fato de poder interferir com a semântica de maneiras tão sutis é apenas um efeito colateral (infeliz).

Existem muitas dicas sobre esse tema e os Java Puzzlers de Joshua Bloch e Neal Gafter incluíram a seguinte variante:

Este é um programa Java legal? Se sim, o que é impresso?

\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020\u0020
\u0063\u006c\u0061\u0073\u0073\u0020\u0055\u0067\u006c\u0079
\u007b\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020
\u0020\u0020\u0020\u0020\u0073\u0074\u0061\u0074\u0069\u0063
\u0076\u006f\u0069\u0064\u0020\u006d\u0061\u0069\u006e\u0028
\u0053\u0074\u0072\u0069\u006e\u0067\u005b\u005d\u0020\u0020
\u0020\u0020\u0020\u0020\u0061\u0072\u0067\u0073\u0029\u007b
\u0053\u0079\u0073\u0074\u0065\u006d\u002e\u006f\u0075\u0074
\u002e\u0070\u0072\u0069\u006e\u0074\u006c\u006e\u0028\u0020
\u0022\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u0022\u002b
\u0022\u006f\u0072\u006c\u0064\u0022\u0029\u003b\u007d\u007d

(Este programa acaba sendo um programa simples "Hello World".)

Na solução para o quebra-cabeças, eles apontam o seguinte:

Mais seriamente, esse quebra-cabeça serve para reforçar as lições dos três anteriores: escapamentos Unicode são essenciais quando você precisa inserir caracteres que não podem ser representados de nenhuma outra maneira no seu programa. Evite-os em todos os outros casos.


Fonte: Java: Executando código nos comentários ?!

aioobe
fonte
84
Em suma, Java intencionalmente permite: o "bug" está no IDE do OP?
Bathsheba
60
@Bathsheba: É mais na cabeça das pessoas. As pessoas não tentam entender como a análise de Java funciona; portanto, os IDEs às vezes exibem o código de maneira errada. No exemplo acima, o comentário deve terminar com \u000de a parte após ter destaque do código.
Aaron Digulla
62
Outro erro comum é colar os caminhos do Windows no código, o // C:\user\...que leva a um erro de compilação, pois \usernão é uma sequência de escape Unicode válida.
Aaron Digulla
50
No eclipse, o Código depois \u000dé destacado parcialmente. Depois de pressionar Ctrl + Shift + F o carácter é substituído com nova linha e o resto da linha é enrolada
bluelDe
20
@TheLostMind Se entendi a resposta corretamente, você também pode reproduzi-la com comentários em bloco. \u002A/deve terminar o comentário.
Taemyr
141

Como isso ainda não foi abordado, aqui está uma explicação de por que a tradução de Unicode escapa ocorre antes de qualquer outro processamento de código-fonte:

A idéia por trás disso era que ela permite traduções sem perdas do código-fonte Java entre diferentes codificações de caracteres. Hoje, há amplo suporte a Unicode, e isso não parece um problema, mas naquela época não era fácil para um desenvolvedor de um país ocidental receber algum código-fonte de seu colega asiático contendo caracteres asiáticos, fazer algumas alterações ( incluindo compilar e testá-lo) e enviar o resultado de volta, tudo sem danificar nada.

Portanto, o código-fonte Java pode ser escrito em qualquer codificação e permite uma ampla variedade de caracteres dentro de identificadores, caracteres e Stringliterais e comentários. Em seguida, para transferi-lo sem perdas, todos os caracteres não suportados pela codificação de destino são substituídos por seus escapes Unicode.

Esse é um processo reversível e o ponto interessante é que a tradução pode ser feita por uma ferramenta que não precisa saber nada sobre a sintaxe do código-fonte Java, pois a regra de tradução não depende dela. Isso funciona como a tradução para seus caracteres Unicode reais dentro do compilador também acontece independentemente da sintaxe do código-fonte Java. Isso implica que você pode executar um número arbitrário de etapas de tradução em ambas as direções sem alterar o significado do código-fonte.

Este é o motivo de outro recurso estranho que nem sequer mencionou: a \uuuuuuxxxxsintaxe:

Quando uma ferramenta de tradução está escapando de caracteres e encontra uma sequência que já é uma sequência de escape, ela deve inserir um adicional una sequência, convertendo \ucafepara \uucafe. O significado não muda, mas ao converter para outra direção, a ferramenta deve apenas remover umau e substituir apenas as seqüências que contêm uma única upor seus caracteres Unicode. Dessa forma, até as fugas Unicode são mantidas em sua forma original ao converter para frente e para trás. Eu acho que ninguém nunca usou esse recurso ...

Holger
fonte
1
Curiosamente, native2asciinão parece usar a \uu...xxxxsintaxe,
ninjalj
5
Sim, native2asciipretendia ajudar a preparar pacotes de recursos convertendo-os em iso-latin-1, como Properties.loadfoi corrigido para ler somente latin-1. E lá, as regras são diferentes, sem \uuu…sintaxe e sem estágio inicial de processamento. Nos arquivos de propriedades, property=multi\u000alineé realmente o mesmo que property=multi\nline. (Contrariando a frase "usando escapes Unicode, conforme definido na seção 3.3 da especificação da linguagem Java ™" da documentação)
Holger
10
Observe que esse objetivo de projeto poderia ter sido alcançado sem nenhuma das verrugas; a maneira mais fácil seria proibir \uescapes para gerar caracteres no intervalo U + 0000–007F. (Todos esses caracteres podem ser representados de forma nativa por todas as codificações nacionais que foram relevantes na década de 1990, bem, talvez exceto alguns dos caracteres de controle, mas você não precisa aqueles para escrever Java qualquer maneira.)
Zwol
3
@ zwol: bem, se você excluir caracteres de controle que não são permitidos no código-fonte Java de qualquer maneira, você está certo. No entanto, isso implicaria em tornar as regras mais complicadas. E hoje, é muito tarde para discutir a decisão ...
Holger
ah o problema de salvar um documento em utf8 e não em latim ou em outra coisa. Todos os meus bancos de dados foram quebrados, mas também porque deste absurdo ocidental
David天宇Wong
106

Vou acrescentar de maneira completamente ineficaz o argumento, só porque não posso me ajudar e ainda não o vi fazer, que a pergunta é inválida, pois contém uma premissa oculta que está errada, ou seja, que o código está dentro um comentário!

No código-fonte Java, \ u000d é equivalente em todos os aspectos a um caractere ASCII CR. É um final de linha, puro e simples, onde quer que ocorra. A formatação na pergunta é enganosa, ao que essa sequência de caracteres realmente sintaticamente corresponde é:

public static void main(String... args) {
   // The comment below is no typo. 
   // 
 System.out.println("Hello World!");
}

IMHO, a resposta mais correta é, portanto: o código é executado porque não está em um comentário; está na próxima linha. "Executar código nos comentários" não é permitido em Java, como você esperaria.

Grande parte da confusão decorre do fato de que marcadores de sintaxe e IDEs não são sofisticados o suficiente para levar essa situação em consideração. Eles não processam os escapes unicode ou o fazem depois de analisar o código em vez de antes, como o javacfazem.

Pepijn Schmitz
fonte
6
Eu concordo, este não é um "erro de design" em java, mas é um bug do IDE.
bvdb
3
A questão é bastante sobre por que o código que parece um comentário para alguém não familiarizado com esse aspecto específico da linguagem e talvez sem referência ao realce da sintaxe, na verdade não é um comentário. Objetar com base na premissa de que a pergunta é inválida é falso.
Phil
@ Phil: parece apenas um comentário quando visto com ferramentas específicas, outros mostram o contrário.
jmoreno
1
@jmoreno não é preciso ter nada além de um editor de texto para ler o código. No mínimo, viola o princípio da menor surpresa, ou seja, que os comentários de estilo // continuem até o próximo caractere \ n - e não para qualquer outra sequência que seja substituída por \ n eventualmente. Os comentários nunca devem ser outra coisa senão despojados. Pré-processador incorreto.
7269 Phil
69

A \u000dfuga termina um comentário porque as \ufugas são convertidas uniformemente nos caracteres Unicode correspondentes antes que o programa seja tokenizado. Você poderia igualmente usar \u0057\u0057em vez de //para começar um comentário.

Este é um erro no seu IDE, que deve destacar a linha na sintaxe para deixar claro que o \u000dfinal do comentário é finalizado.

Este também é um erro de design no idioma. Não pode ser corrigido agora, porque isso interromperia os programas que dependem dele. \uescapes devem ser convertidos no caractere Unicode correspondente pelo compilador apenas em contextos em que isso "faz sentido" (literais e identificadores de string e provavelmente em nenhum outro lugar) ou devem ter sido proibidos de gerar caracteres no intervalo U + 0000–007F , ou ambos. Qualquer uma dessas semânticas teria impedido que o comentário fosse encerrado pela \u000dfuga, sem interferir nos casos em que \uescapes são úteis - observe que isso inclui o uso de \uescapes nos comentários como uma maneira de codificar comentários em um script não latino. editor de texto poderia ter uma visão mais ampla de onde\uescapes são significativos do que o compilador. (Não conheço nenhum editor ou IDE que exiba \uescapes como os caracteres correspondentes em qualquer contexto.)

Há um erro de design semelhante na família C, 1 em que a barra invertida é processada antes que os limites do comentário sejam determinados, por exemplo,

// this is a comment \
   this is still in the comment!

Trago isso para ilustrar que é fácil cometer esse erro de design específico, e não percebo que é um erro até que seja tarde demais para corrigi-lo, se você está acostumado a pensar em tokenização e analisar a maneira como os programadores de compilador pensam sobre tokenização e análise. Basicamente, se você já definiu sua gramática formal e alguém aparece com um caso sintático especial - trigraphs, contrabarra de nova linha, codificando caracteres Unicode arbitrários em arquivos de origem limitados a ASCII, qualquer que seja - que precisam ser inseridos, é mais fácil adicione uma passagem de transformação antes do tokenizer para redefinir o tokenizer para prestar atenção ao local onde faz sentido usar esse caso especial.

1 Para pedantes: estou ciente de que esse aspecto de C era 100% intencional, com a lógica - não estou inventando isso - de que lhe permitiria aplicar mecanicamente o código de ajuste forçado com linhas arbitrariamente longas em cartões perfurados. Ainda foi uma decisão incorreta do projeto.

zwol
fonte
17
Eu não chegaria ao ponto de dizer que é um erro de design . Eu posso concordar com você que foi uma má escolha de design, ou uma escolha com conseqüências infelizes, mas ainda acho que funciona como os projetistas de linguagem pretendiam: permite que você use qualquer caractere unicode em qualquer lugar do arquivo, mantendo a codificação ASCII do arquivo.
precisa saber é
12
Dito isto, acho que a escolha do estágio de processamento para \ufoi menos absurda do que a decisão de seguir a liderança de C no uso de zeros à esquerda para notação octal. Embora a notação octal às vezes seja útil, ainda não ouvi ninguém articular um argumento sobre por que um zero à esquerda é uma boa maneira de indicá-lo.
Supercat
3
@supercat As pessoas que lançaram esse recurso no C89 estavam generalizando o comportamento do pré-processador K&R original, em vez de projetar um recurso do zero. Duvido que eles estejam familiarizados com as práticas recomendadas de cartões perfurados, e também duvido que o recurso já tenha sido usado para sua finalidade declarada, exceto talvez para um ou dois exercícios de retrocomputação.
Zwol
8
@ supercat Eu não teria problemas com Java \ucomo transformação de pré-tokenização se fosse proibido produzir caracteres na faixa U ​​+ 0000..U + 007F. É a combinação de "isso funciona em todos os lugares" e "esse nome alternativo de caracteres ASCII com significado sintático" que o despromete de algo estranho e totalmente errado.
Zwol
4
No seu "para pedantes": é claro que naquela época o //comentário em uma única linha não existia . E como C possui um terminador de instrução que não é uma nova linha, ele seria usado principalmente para cadeias longas, exceto que, até onde eu possa determinar, a " K&R " concatenação literal de cadeia " estava presente.
Mark Hurd
22

Essa foi uma escolha de design intencional que remonta ao design original do Java.

Para aquelas pessoas que perguntam "quem quer que o Unicode escape nos comentários?", Presumo que sejam pessoas cujo idioma nativo usa o conjunto de caracteres latinos. Em outras palavras, é inerente ao design original do Java que as pessoas possam usar caracteres Unicode arbitrários onde quer que sejam legais em um programa Java, geralmente em comentários e strings.

É indiscutivelmente uma falha nos programas (como IDEs) usados ​​para exibir o texto de origem que esses programas não podem interpretar as fugas de Unicode e exibir o glifo correspondente.

Jonathan Gibbons
fonte
8
Atualmente, usamos UTF-8 para o nosso código-fonte e podemos usar os caracteres Unicode diretamente, sem necessidade de escape.
Paŭlo Ebermann 14/06
21

Concordo com @zwol que este é um erro de design; mas sou ainda mais crítico.

\uescape é útil em literais de string e char; e esse é o único lugar que deveria existir. Deve ser tratado da mesma maneira que outras fugas, como \n; e "\u000A" deve significar exatamente "\n".

Não há absolutamente nenhum ponto em ter \uxxxxcomentários - ninguém pode ler isso.

Da mesma forma, não há por que usar \uxxxxem outra parte do programa. A única exceção é provavelmente nas APIs públicas que são coagidas a conter alguns caracteres não-ascii - qual foi a última vez que vimos isso?

Os designers tiveram suas razões em 1995, mas 20 anos depois, essa parece ser uma escolha errada.

(pergunta aos leitores - por que essa pergunta continua recebendo novos votos? esta questão está vinculada de algum lugar popular?)

ZhongYu
fonte
5
Eu acho que você não está por aí, onde caracteres não ASCII são usados ​​nas APIs. Há pessoas usando (não eu), por exemplo, em países asiáticos. E quando você está usando caracteres não ASCII nos identificadores, proibi-los nos comentários da documentação faz pouco sentido. No entanto, permitir que eles entrem em um token e permitir que eles alterem o significado ou o limite de um token são coisas diferentes.
Holger
15
eles podem usar a codificação de arquivo adequada. por que escrever int \u5431quando você pode fazerint 整
ZhongYu
3
O que você vai fazer quando você tem que compilar o código contra a sua API e não pode usar a codificação adequada (supor que não havia generalizado UTF-8apoio em 1995). Você só precisa chamar um método e não deseja instalar o pacote de suporte ao idioma asiático do seu sistema operacional (lembre-se, anos 90) para esse método único ...
Holger
5
O que é muito mais claro agora do que 1995 é que você conhece melhor o inglês se quiser programar. A programação é uma interação internacional e quase todos os recursos estão em inglês.
ZhongYu
8
Eu não acho que isso mudou. A documentação de Java também era em inglês na maioria das vezes. Houve uma tradução em japonês mantida por um tempo, mas a manutenção de dois idiomas não confirma realmente a idéia de mantê-la para todos os locais do mundo (ao contrário, refutou). E antes disso, não havia linguagem mainstream com suporte a Unicode nos identificadores. Então, eu acho que alguém pensou que o código fonte localizado era a próxima grande novidade. Eu diria felizmente que não decolou.
Holger
11

As únicas pessoas que podem responder por que as fugas Unicode foram implementadas como foram foram as pessoas que escreveram a especificação.

Uma razão plausível para isso é que havia o desejo de permitir todo o BMP como possíveis caracteres do código-fonte Java. Isso apresenta um problema:

  • Você deseja usar qualquer caractere BMP.
  • Você deseja inserir qualquer caractere BMP razoavelmente fácil. Uma maneira de fazer isso é com escapes Unicode.
  • Você deseja manter a especificação lexical fácil para os humanos lerem e escreverem, e razoavelmente fácil de implementar também.

Isso é incrivelmente difícil quando o escape do Unicode entra em conflito: ele cria uma carga completa de novas regras de lexer.

A saída mais fácil é fazer lexing em duas etapas: primeiro pesquise e substitua todos os escapes Unicode pelo caractere que representa e, em seguida, analise o documento resultante como se os escapes Unicode não existissem.

A vantagem disso é que é fácil especificar, por isso torna a especificação mais simples e fácil de implementar.

A desvantagem é, bem, o seu exemplo.

Martijn
fonte
2
Ou restrinja o uso de \ uxxxx a identificadores, literais de seqüência de caracteres e constantes de caracteres. Qual é o que C11 faz.
Njalj
isso realmente complica as regras do analisador, porque é isso que define essas coisas, e é isso que estou especulando que faz parte da razão pela qual é desse jeito.
Martijn