Почему Java разрешает управлять символами в своих идентификаторах?

Тайна

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

Ожидалось, что идентификатор Java найдет, что идентификаторы Java соответствуют требованию, чтобы они начинались с символов, имеющих свойство Unicode ID_Start, и за ними следуют те, у которых есть свойство ID_Continue, с исключением, предоставленным для ведущих подчеркиваний и знаков доллара, Это не подтвердилось, и то, что я обнаружил, находится в крайнем разбросе с той или иной идеей обычного идентификатора, о котором я слышал.

Короткая демонстрация

Рассмотрим следующую демонстрацию, подтверждающую, что символ ASCII ESC (восьмеричный 033) разрешен в идентификаторах Java:

$ perl -le 'print qq(public class escape { public static void main(String argv[]) { String var_\033 = "i am escape: \033"; System.out.println(var_\033); }})' > escape.java
$ javac escape.java
$ java escape | cat -v
i am escape: ^[
Тем не менее, даже хуже. На самом деле почти бесконечно хуже. Разрешены даже NULL! И тысячи других кодовых точек, которые даже не являются символами идентификатора. Я тестировал это на Solaris, Linux и Mac, работающем с Darwin, и все они дают одинаковые результаты.

Длинная демонстрация

Вот тестовая программа, которая покажет все эти неожиданные кодовые точки, которые Java довольно возмутительно разрешает как часть имени юридического идентификатора.

#!/usr/bin/env perl
# 
# test-java-idchars - find which bogus code points Java allows in its identifiers
# 
#   usage: test-java-idchars [low high]
#   e.g.:  test-java-idchars 0 255
#
# Without arguments, tests Unicode code points
# from 0 .. 0x1000.  You may go further with a
# higher explicit argument.
#
# Produces a report at the end.
#
# You can ^C it prematurely to end the program then
# and get a report of its progress up to that point.
#
# Tom Christiansen
# [email protected]
# Sat Jan 29 10:41:09 MST 2011

use strict;
use warnings;

use encoding "Latin1";
use open IO => ":utf8";

use charnames ();

$| = 1;

my @legal;

my ($start, $stop) = (0, 0x1000);

if (@ARGV != 0) {
    if (@ARGV == 1) {
        for (($stop) = @ARGV) { 
            $_ = oct if /^0/;   # support 0OCTAL, 0xHEX, 0bBINARY
        }
    }
    elsif (@ARGV == 2) {
        for (($start, $stop) = @ARGV) { 
            $_ = oct if /^0/;
        }
    } 
    else {
        die "usage: $0 [ [start] stop ]\n";
    } 
} 

for my $cp ( $start .. $stop ) {
    my $char = chr($cp);

    next if $char =~ /[\s\w]/;

    my $type = "?";
    for ($char) {
        $type = "Letter"      if /\pL/;
        $type = "Mark"        if /\pM/;
        $type = "Number"      if /\pN/;
        $type = "Punctuation" if /\pP/;
        $type = "Symbol"      if /\pS/;
        $type = "Separator"   if /\pZ/;
        $type = "Control"     if /\pC/;
    } 
    my $name = $cp ? (charnames::viacode($cp) || "<missing>") : "NULL";
    next if $name eq "<missing>" && $cp > 0xFF;
    my $msg = sprintf("U+%04X %s", $cp, $name);
    print "testing \\p{$type} $msg...";
    open(TESTPROGRAM, ">:utf8", "testchar.java") || die $!;

print TESTPROGRAM <<"End_of_Java_Program";

public class testchar { 
    public static void main(String argv[]) { 
        String var_$char = "variable name ends in $msg";
        System.out.println(var_$char); 
    }
}

End_of_Java_Program

    close(TESTPROGRAM) || die $!;

    system q{
        ( javac -encoding UTF-8 testchar.java \
            && \
          java -Dfile.encoding=UTF-8 testchar | grep variable \
        ) >/dev/null 2>&1
    };

    push @legal, sprintf("U+%04X", $cp) if $? == 0;

    if ($? && $? < 128) {
        print "<interrupted>\n";
        exit;  # from a ^C
    } 

    printf "is %s in Java identifiers.\n",  
        ($? == 0) ? uc "legal" : "forbidden";

} 

END { 
    print "Legal but evil code points: @legal\n";
}

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

$ perl test-java-idchars 0 0x20
testing \p{Control} U+0000 NULL...is LEGAL in Java identifiers.
testing \p{Control} U+0001 START OF HEADING...is LEGAL in Java identifiers.
testing \p{Control} U+0002 START OF TEXT...is LEGAL in Java identifiers.
testing \p{Control} U+0003 END OF TEXT...is LEGAL in Java identifiers.
testing \p{Control} U+0004 END OF TRANSMISSION...is LEGAL in Java identifiers.
testing \p{Control} U+0005 ENQUIRY...is LEGAL in Java identifiers.
testing \p{Control} U+0006 ACKNOWLEDGE...is LEGAL in Java identifiers.
testing \p{Control} U+0007 BELL...is LEGAL in Java identifiers.
testing \p{Control} U+0008 BACKSPACE...is LEGAL in Java identifiers.
testing \p{Control} U+000B LINE TABULATION...is forbidden in Java identifiers.
testing \p{Control} U+000E SHIFT OUT...is LEGAL in Java identifiers.
testing \p{Control} U+000F SHIFT IN...is LEGAL in Java identifiers.
testing \p{Control} U+0010 DATA LINK ESCAPE...is LEGAL in Java identifiers.
testing \p{Control} U+0011 DEVICE CONTROL ONE...is LEGAL in Java identifiers.
testing \p{Control} U+0012 DEVICE CONTROL TWO...is LEGAL in Java identifiers.
testing \p{Control} U+0013 DEVICE CONTROL THREE...is LEGAL in Java identifiers.
testing \p{Control} U+0014 DEVICE CONTROL FOUR...is LEGAL in Java identifiers.
testing \p{Control} U+0015 NEGATIVE ACKNOWLEDGE...is LEGAL in Java identifiers.
testing \p{Control} U+0016 SYNCHRONOUS IDLE...is LEGAL in Java identifiers.
testing \p{Control} U+0017 END OF TRANSMISSION BLOCK...is LEGAL in Java identifiers.
testing \p{Control} U+0018 CANCEL...is LEGAL in Java identifiers.
testing \p{Control} U+0019 END OF MEDIUM...is LEGAL in Java identifiers.
testing \p{Control} U+001A SUBSTITUTE...is LEGAL in Java identifiers.
testing \p{Control} U+001B ESCAPE...is LEGAL in Java identifiers.
testing \p{Control} U+001C INFORMATION SEPARATOR FOUR...is forbidden in Java identifiers.
testing \p{Control} U+001D INFORMATION SEPARATOR THREE...is forbidden in Java identifiers.
testing \p{Control} U+001E INFORMATION SEPARATOR TWO...is forbidden in Java identifiers.
testing \p{Control} U+001F INFORMATION SEPARATOR ONE...is forbidden in Java identifiers.
Legal but evil code points: U+0000 U+0001 U+0002 U+0003 U+0004 U+0005 U+0006 U+0007 U+0008 U+000E U+000F U+0010 U+0011 U+0012 U+0013 U+0014 U+0015 U+0016 U+0017 U+0018 U+0019 U+001A U+001B

И вот еще одно демо:

$ perl test-java-idchars 0x600 0x700 | grep -i legal
testing \p{Control} U+0600 ARABIC NUMBER SIGN...is LEGAL in Java identifiers.
testing \p{Control} U+0601 ARABIC SIGN SANAH...is LEGAL in Java identifiers.
testing \p{Control} U+0602 ARABIC FOOTNOTE MARKER...is LEGAL in Java identifiers.
testing \p{Control} U+0603 ARABIC SIGN SAFHA...is LEGAL in Java identifiers.
testing \p{Control} U+06DD ARABIC END OF AYAH...is LEGAL in Java identifiers.
Legal but evil code points: U+0600 U+0601 U+0602 U+0603 U+06DD

Вопрос

Может кто-нибудь объяснить это, казалось бы, безумное поведение? Есть много, много и много других необъяснимых разрешенных кодовых пунктов повсюду, начиная с U + 0000, что, пожалуй, самое странное из всех. Если вы запустили его в первых кодовых точках 0x1000, вы увидите, как появляются определенные шаблоны, такие как разрешение всех и всех кодовых точек с свойством Current_Symbol. Но слишком многое другое совершенно необъяснимо, по крайней мере, мной.

Ответ 2

Другой вопрос: почему Java не должен содержать управляющие символы в своих идентификаторах?

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

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

Конечно, это можно было бы злоупотреблять, но это не работа разработчиков языков, чтобы защитить программистов от самих себя таким образом, не более, чем заставляя правильные отступы или хорошо выбранные имена переменных.

Ответ 3

Я не понимаю, в чем дело. Как это повлияет на вас?

Если разработчик хочет запутать свой код, он может сделать это с помощью ASCII.

Если разработчик хочет сделать свой код понятным, он будет использовать lingua franca в отрасли: английский. Не только идентификаторы - это только ASCII, но и общие английские слова. В противном случае никто не будет использовать или читать свой код, он может использовать любые сумасшедшие персонажи, которые ему нравятся.