etc./StackOverFlow

특정 유니코드 문자가 포함된 주석에서 Java 코드를 실행하는 것이 허용되는 이유는 무엇입니까?

청렴결백한 만능 재주꾼 2022. 3. 22. 11:04
반응형

질문자 :Reg


다음 코드는 "Hello World!" 출력을 생성합니다. (아니 정말, 그것을 시도).

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

그 이유는 Java 컴파일러가 유니코드 문자 \u000d 를 새 줄로 구문 분석하고 다음과 같이 변환되기 때문입니다.

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

따라서 주석이 "실행"됩니다.

이것은 악성 코드나 악의적인 프로그래머가 생각할 수 있는 모든 것을 "숨기기" 위해 사용될 수 있기 때문에 주석에서 허용되는 이유는 무엇입니까?

이것이 Java 사양에서 허용되는 이유는 무엇입니까?



유니코드 디코딩은 다른 어휘 번역보다 먼저 발생합니다. 이것의 주요 이점은 ASCII와 다른 인코딩 간에 쉽게 왔다 갔다 한다는 것입니다. 댓글의 시작과 끝이 어디인지 알 필요도 없습니다!

JLS 섹션 3.3에 명시된 바와 같이 이것은 모든 ASCII 기반 도구가 소스 파일을 처리할 수 있도록 합니다.

[...] Java 프로그래밍 언어는 유니코드로 작성된 프로그램을 ASCII 기반 도구로 처리할 수 있는 형식으로 프로그램을 변경하는 ASCII로 변환하는 표준 방법을 지정합니다. [...]

이것은 항상 Java 플랫폼의 핵심 목표였던 플랫폼 독립성(지원되는 문자 세트의 독립성)에 대한 근본적인 보장을 제공합니다.

파일의 아무 곳에나 유니코드 문자를 쓸 수 있는 것은 깔끔한 기능이며, 비라틴 언어로 코드를 문서화할 때 주석에서 특히 중요합니다. 이러한 미묘한 방식으로 의미론을 방해할 수 있다는 사실은 (불행한) 부작용일 뿐입니다.

이 테마에는 많은 문제가 있으며 Joshua Bloch와 Neal Gafter의 Java 퍼즐 에는 다음과 같은 변형이 포함되어 있습니다.

합법적인 Java 프로그램입니까? 그렇다면 무엇을 인쇄합니까?

 \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

(이 프로그램은 평범한 "Hello World" 프로그램으로 판명되었습니다.)

수수께끼 풀이에서 그들은 다음을 지적합니다.

더 심각하게, 이 퍼즐은 이전 세 가지의 교훈을 강화하는 역할을 합니다. 유니코드 이스케이프는 프로그램에 다른 방식으로 표현할 수 없는 문자를 삽입해야 할 때 필수적입니다. 다른 모든 경우에는 피하십시오.


출처: Java: 주석에서 코드 실행?!


aioobe

이것이 아직 해결되지 않았기 때문에 여기 설명이 있습니다. 유니코드 이스케이프 변환이 다른 소스 코드 처리보다 먼저 발생하는 이유는 다음과 같습니다.

그 배후의 아이디어는 서로 다른 문자 인코딩 간에 Java 소스 코드의 무손실 번역을 허용한다는 것입니다. 오늘날 유니코드 지원이 널리 퍼져 있어 문제가 없어 보이지만 당시에는 서구 국가의 개발자가 아시아계 동료로부터 아시아계 문자가 포함된 일부 소스 코드를 받아 약간의 변경( 컴파일 및 테스트 포함) 및 결과를 다시 보내는 것, 모두 손상 없이.

따라서 Java 소스 코드는 모든 인코딩으로 작성할 수 있으며 식별자, 문자 및 String 리터럴 및 주석 내에서 광범위한 문자를 허용합니다. 그런 다음 손실 없이 전송하기 위해 대상 인코딩에서 지원하지 않는 모든 문자는 유니코드 이스케이프로 대체됩니다.

이것은 되돌릴 수 있는 프로세스이며 흥미로운 점은 번역 규칙이 Java 소스 코드 구문에 종속되지 않으므로 Java 소스 코드 구문에 대해 알 필요가 없는 도구로 번역을 수행할 수 있다는 것입니다. 이것은 컴파일러 내부의 실제 유니코드 문자로의 변환이 Java 소스 코드 구문과도 독립적으로 발생하므로 작동합니다. 이는 소스 코드의 의미를 변경하지 않고 양방향으로 임의의 수의 번역 단계를 수행할 수 있음을 의미합니다.

이것은 언급조차 하지 않은 또 다른 이상한 기능에 대한 이유입니다: \uuuuuuxxxx 구문:

번역 도구가 문자를 이스케이프하고 이미 이스케이프된 시퀀스인 시퀀스를 만나면 시퀀스에 추가 u \ucafe\uucafe 로 변환해야 합니다. 의미는 변경되지 않지만 다른 방향으로 변환할 때 도구는 하나의 u u 를 포함하는 시퀀스만 유니코드 문자로 교체해야 합니다. 이렇게 하면 앞뒤로 변환할 때 유니코드 이스케이프도 원래 형식으로 유지됩니다. 아무도 그 기능을 사용하지 않았나 봅니다...


Holger

나는 스스로를 도울 수 없고 아직 만들어지는 것을 보지 못했기 때문에 그 질문이 잘못된 숨겨진 전제, 즉 코드가 댓글!

Java 소스 코드에서 \u000d는 모든 면에서 ASCII CR 문자와 동일합니다. 그것은 어디에서나 발생하는 단순하고 단순한 줄 끝입니다. 질문의 형식은 오해의 소지가 있습니다. 해당 문자 시퀀스가 실제로 구문적으로 해당하는 내용은 다음과 같습니다.

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

따라서 IMHO가 가장 정확한 대답은 다음과 같습니다. 코드가 주석에 없기 때문에 실행됩니다. 다음 줄에 있습니다. "주석에서 코드 실행"은 예상대로 Java에서 허용되지 않습니다.

대부분의 혼란은 구문 형광펜과 IDE가 이러한 상황을 고려할 만큼 정교하지 않다는 사실에서 비롯됩니다. javac 처럼 이전 대신에 코드를 구문 분석한 후에 수행합니다.


Pepijn Schmitz

\u000d 이스케이프는 프로그램이 토큰화 되기 전에 \u 이스케이프가 해당 유니코드 문자로 균일하게 변환되기 때문에 주석을 종료합니다. // 대신 \u0057\u0057 동등하게 사용하여 주석을 시작할 수 있습니다.

\u000d 가 주석을 끝낸다는 것을 명확히 하기 위해 행을 구문 강조 표시해야 합니다.

이것은 또한 언어의 설계 오류입니다. 그것은 그것에 의존하는 프로그램을 깨뜨릴 것이기 때문에 지금 수정할 수 없습니다. \u 이스케이프는 "이치에 맞는" 상황에서만 컴파일러에 의해 해당 유니코드 문자로 변환되어야 합니다(문자열 리터럴 및 식별자, 그리고 아마도 다른 곳에서는 없을 것입니다). 그렇지 않으면 U+0000–에서 문자 생성이 금지되어야 합니다. 007F 범위 또는 둘 다. 어느 그 의미의 종료되는 것을 코멘트를 막았을 것 \u000d 사례를 방해하지 않고, 탈출 \u 이스케이프는 그 사용 포함 유용 노트입니다 \u 비의 인코딩 설명하는 방법으로 내부 의견을 탈출을 \u 이스케이프가 중요한 위치를 더 넓게 볼 수 있기 때문입니다. (하지만 모든 \u 이스케이프를 해당 문자로 표시하는 편집기나 IDE를 알지 못합니다.)

C 계열에는 유사한 설계 오류가 있습니다. 1 주석 경계가 결정되기 전에 백슬래시 줄바꿈이 처리됩니다.

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

컴파일러 프로그래머가 생각하는 방식으로 토큰화 및 구문 분석에 대해 생각하는 데 익숙하다면 이 특정 설계 오류를 범하기 쉽고 너무 늦을 때까지 오류라는 것을 깨닫지 못한다는 것을 설명하기 위해 이 문제를 제기했습니다. 토큰화 및 구문 분석에 대해. 기본적으로 이미 형식 문법을 정의한 다음 누군가가 구문적 특수 사례(삼중자, 백슬래시 줄 바꿈, ASCII로 제한된 소스 파일의 임의의 유니코드 문자 인코딩 등)를 생각해낸다면 토크나이저를 재정의하는 것보다 토크나이저 앞에 변환 패스를 추가하여 해당 특수한 경우를 사용하는 것이 합리적인 위치에 주의를 기울이십시오.

1 pedants의 경우: C의 이 측면이 100% 의도적이라는 것을 알고 있습니다. 근거는 — 저는 이것을 만들지 않습니다 — 구멍이 뚫린 카드에 임의로 긴 줄이 있는 코드를 기계적으로 강제 맞출 수 있다는 근거를 가지고 있습니다. 여전히 잘못된 설계 결정이었습니다.


zwol

이것은 Java의 원래 디자인으로 거슬러 올라가는 의도적인 디자인 선택이었습니다.

"댓글에서 유니코드 이스케이프를 원하는 사람은 누구입니까?"라고 묻는 사람들에게 나는 그들이 모국어가 라틴 문자 집합을 사용하는 사람들이라고 생각합니다. 다시 말해서, 사람들이 Java 프로그램에서 합법적인 곳이면 어디든지, 가장 일반적으로 주석과 문자열에서 임의의 유니코드 문자를 사용할 수 있다는 것이 Java의 원래 설계에 내재되어 있습니다.

소스 텍스트를 보는 데 사용되는 프로그램(예: IDE)에서 이러한 프로그램이 유니코드 이스케이프를 해석하고 해당 글리프를 표시할 수 없다는 것은 틀림없이 단점입니다.


Jonathan Gibbons

나는 이것이 디자인 실수라는 @zwol의 말에 동의합니다. 그러나 나는 그것에 대해 더 비판적입니다.

\u 이스케이프는 문자열 및 문자 리터럴에서 유용합니다. 그리고 그것이 존재해야 할 유일한 장소입니다. \n 과 같은 다른 이스케이프와 같은 방식으로 처리해야 합니다. "\u000A" 는 정확히 "\n" 의미해야 합니다 .

\uxxxx 가 있는 것은 전혀 의미가 없습니다. 아무도 읽을 수 없습니다.

마찬가지로 프로그램의 다른 부분에서 \uxxxx 를 사용할 필요가 없습니다. 유일한 예외는 ASCII가 아닌 일부 문자를 포함하도록 강제된 공개 API일 것입니다. 마지막으로 본 것이 언제입니까?

디자이너들은 1995년에 나름의 이유가 있었지만 20년이 지난 지금 이것은 잘못된 선택으로 보입니다.

(독자에 대한 질문 - 이 질문이 계속 새로운 투표를 받는 이유는 무엇입니까? 이 질문은 인기 있는 어딘가에서 연결되어 있습니까?)


ZhongYu

왜 유니코드 이스케이프가 그대로 구현되었는지 답할 수 있는 사람은 사양을 작성한 사람뿐입니다.

그럴듯한 이유는 전체 BMP를 Java 소스 코드의 가능한 문자로 허용하려는 욕구가 있었기 때문입니다. 하지만 다음과 같은 문제가 있습니다.

  • 모든 BMP 문자를 사용할 수 있기를 원합니다.
  • 당신은 합리적으로 쉽게 모든 BMP 문자를 입력할 수 있기를 원합니다. 이를 수행하는 방법은 유니코드 이스케이프를 사용하는 것입니다.
  • 당신은 인간이 읽고 쓰기 쉬운 어휘 명세를 유지하고 합리적으로 구현하기를 원합니다.

이것은 유니코드 이스케이프가 싸움에 들어갈 때 매우 어렵습니다. 새로운 렉서 규칙의 전체 로드를 생성합니다.

쉬운 방법은 두 단계로 렉싱을 수행하는 것입니다. 먼저 모든 유니코드 이스케이프를 검색하여 해당 문자가 나타내는 문자로 바꾼 다음 결과 문서를 마치 유니코드 이스케이프가 존재하지 않는 것처럼 구문 분석합니다.

이것의 장점은 명시하기 쉽기 때문에 명세를 단순화하고 구현하기도 쉽다는 것이다.

단점은 당신의 예입니다.


Martijn

"그 이유는 Java 컴파일러가 유니코드 문자 \u000d를 새 줄로 구문 분석하기 때문입니다."

사실이라면 바로 그 지점에서 오류가 발생합니다.

Java 컴파일러는 이 소스를 컴파일하는 것을 거부해야 합니다. 왜냐하면 (Java 소스 코드로서) 형식이 잘못되어 시작하기에 좋지 않거나, 도중에 변조되거나, 변환을 이해하지 못하는 도구 체인의 무언가에 의해 변형되기 때문입니다. 규칙. 맹목적으로 변형해서는 안됩니다.

문제의 편집기가 ASCII 전용 도구인 경우 해당 편집기는 올바른 일을 하고 있는 것입니다. 즉, 유니코드 이스케이프 시퀀스를 (잘못된) 주석의 무의미한 문자열로 처리하는 것입니다.

문제의 편집기가 유니코드 인식 도구인 경우 유니코드 이스케이프 시퀀스를 "있는 그대로" 유지하고 (잘못된 형식의) 주석에서 의미 없는 문자열로 처리하는 올바른 작업도 수행하고 있습니다.

무손실 가역 변환에는 1-1을 매핑하는 변환이 필요하므로 두 세트의 교차점은 비어 있어야 합니다. (000-07F) 범위의 이스케이프 처리된 유니코드가 이미 입력 스트림에 있을 수 있기 때문에 올바르게 구현된 이스케이프 처리 변환에 의해 문자가 수정되지 않은 경우에도 문제의 두 세트가 겹칠 수 있습니다.

목표가 유니코드와 ASCII 간의 무손실 가역 변환인 경우 ASCII로/에서 변환하기 위한 요구 사항은 16진수 007F보다 큰 모든 유니코드 문자를 이스케이프-화/재인코딩하고 나머지는 그대로 두는 것입니다.

그렇게 하면 유니코드를 인식하는 언어는 이스케이프된 유니코드 문자를 주석이나 문자열 내부가 아닌 다른 모든 위치에서 오류로 취급합니다. 주석 내에서는 변환되지 않아야 하지만 문자열 내에서는 변환되어야 합니다. 없는 일이 어휘 분석 전환이 형태 보증 된 방식으로 수행 할 수 있도록 (즉, 어휘) 토큰으로 소스를 설정 한 후까지.


Jim Sawyer

출처 : http:www.stackoverflow.com/questions/30727515/why-is-executing-java-code-in-comments-with-certain-unicode-characters-allowed

반응형