Почему Java допускает управляющие символы в своих идентификаторах? - PullRequest
50 голосов
/ 29 января 2011

Mystery

Изучая, какие именно символы были разрешены в идентификаторах 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, и все они дают одинаковые результаты.

Long Demo

Вот тестовая программа, которая покажет все эти неожиданные кодовые точки, которые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
# tchrist@perl.com
# 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.Но слишком много всего необъяснимо, по крайней мере, мной.

Ответы [ 3 ]

15 голосов
/ 29 января 2011

Раздел Спецификация языка Java 3.8 имеет значение Character.isJavaIdentifierStart () и Character.isJavaIdentifierPart () .Последний, среди прочих условий, имеет Character.isIdentifierIgnorable () , что позволяет использовать непробельные управляющие символы (включая весь диапазон C1, см. Ссылку для списка).

8 голосов
/ 29 января 2011

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

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

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

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

0 голосов
/ 30 января 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...