comments - لماذا يتم تنفيذ تعليمات Java البرمجية في التعليقات مع بعض أحرف Unicode المسموح بها؟




(8)

ينتج التعليمة البرمجية التالية الإخراج "Hello World!" (لا حقا ، حاول ذلك).

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

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

السبب في ذلك هو أن المحول البرمجي لـ Java يوزع حرف Unicode كـ سطر جديد ويتم تحويله إلى:

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

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

مما يؤدي إلى تعليق "تنفيذها".

بما أنه يمكن استخدام هذا "لإخفاء" كود خبيث أو ما يمكن لمبرمج شرير أن يتصور ، فلماذا يسمح به في التعليقات ؟

لماذا هذا يسمح به مواصفات جافا؟


Answers

الأشخاص الوحيدون الذين يمكنهم الإجابة عن سبب تنفيذ عمليات الهروب من Unicode كما كانوا هم الأشخاص الذين كتبوا المواصفات.

سبب معقول لهذا هو أن هناك رغبة للسماح BMP بأكمله كأحرف ممكنة من شفرة مصدر جافا. هذا يمثل مشكلة على الرغم من:

  • تريد أن تكون قادراً على استخدام أي حرف BMP.
  • كنت تريد أن تكون قادرة على إدخال أي BMP سهل بشكل معقول. طريقة للقيام بذلك هي مع يهرب Unicode.
  • أنت تريد أن تبقي المواصفة المعجمية سهلة على البشر للقراءة والكتابة ، ومن السهل تنفيذها بشكل معقول كذلك.

هذا أمر صعب للغاية عندما ينجو Unicode من الدخول في النزاع: فهو يخلق عبءًا كاملاً من القواعد اللغوية الجديدة.

الطريقة السهلة هي القيام بالخطوات التالية: البحث الأول واستبدال كل منافذ Unicode بالحرف الذي تمثله ، ثم تحليل المستند الناتج كما لو أن Unicode escapes غير موجود.

الاتجاه الصعودي لهذا هو أنه من السهل تحديده ، لذلك يجعل المواصفات أبسط ، وهو سهل التطبيق.

الجانب السلبي ، حسنا ، على سبيل المثال الخاص بك.


يحدث فك رموز Unicode قبل أي ترجمة معجمية أخرى. وتتمثل الميزة الرئيسية في ذلك في أنه يجعل الأمر هينًا بالنسبة للتنقل بين ASCII وأي تشفير آخر. لا تحتاج حتى إلى معرفة أين تبدأ التعليقات وتنتهي!

كما هو مذكور في القسم JLS 3.3 ، يسمح ذلك لأي أداة تستند إلى ASCII بمعالجة الملفات المصدر:

[...] تحدد لغة برمجة Java طريقة قياسية لتحويل برنامج مكتوب في Unicode إلى ASCII الذي يغير أحد البرامج إلى نموذج يمكن معالجته بواسطة الأدوات المستندة إلى ASCII. [...]

وهذا يعطي ضمانة أساسية لاستقلالية المنصة (استقلال مجموعات الأحرف المدعومة) التي لطالما كانت هدفًا أساسيًا لمنصة Java.

أن تكون قادراً على كتابة أي حرف Unicode في أي مكان في الملف هي ميزة أنيقة ، وهي مهمة بشكل خاص في التعليقات ، عند توثيق التعليمات البرمجية بلغات غير لاتينية. حقيقة أنه يمكن أن تتداخل مع علم الدلالة في مثل هذه الطرق الرقيقة هو مجرد تأثير جانبي (مؤسف).

هناك العديد من gotchas حول هذا الموضوع و Java Puzzlers بواسطة Joshua Bloch و Neal Gafter متضمنان الشكل التالي:

هل هذا برنامج جافا قانوني؟ إذا كان الأمر كذلك ، فما الذي تطبعه؟

\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" عادي.)

في حل اللغز ، يشيرون إلى ما يلي:

بشكل أكثر جدية ، هذا اللغز يعمل على تعزيز دروس الثلاثة السابقة: تعتبر عمليات الهروب من Unicode ضرورية عندما تحتاج إلى إدراج أحرف لا يمكن تمثيلها بأي طريقة أخرى في برنامجك. تجنبها في جميع الحالات الأخرى.

المصدر: جافا: تنفيذ التعليمات البرمجية في التعليقات ؟!


\u000d الهروب \u000d تعليقًا لأنه يتم تحويل \u \u000d بشكل منتظم إلى أحرف Unicode المطابقة قبل أن يتم برمجة البرنامج. يمكنك أيضًا استخدام \u0057\u0057 بدلاً من // لبدء تعليق.

هذا خطأ في IDE ، والذي يجب أن يسلط الضوء على السطر لتوضيح أن \u000d ينهي التعليق.

هذا أيضًا خطأ في التصميم في اللغة. لا يمكن تصحيحه الآن ، لأن ذلك من شأنه أن يكسر البرامج التي تعتمد عليه. \u يهرب إما إلى حرف Unicode المقابل بواسطة المترجم فقط في السياقات حيث يكون ذلك "منطقيًا" (حرفيّات سلسلة ومعرّفات ، وربما في أي مكان آخر) أو كان يجب حظرها لتوليد حروف في U + 0000– نطاق 007F ، أو كليهما. كان من شأن أي من هذه الألفاظ أن تمنع التعليق من الإنهاء عن طريق الهروب ، دون التدخل في الحالات التي يكون فيها \u escapes مفيدة — لاحظ أن ذلك يشمل استخدام \u escapes داخل التعليقات كطريقة لترميز التعليقات في غير -نصوص لاتيني ، لأن محرر النص يمكن أن يأخذ عرضًا أوسع من حيث \u escapes كبيرًا من المحول البرمجي. (لست على علم بأي محرر أو IDE الذي سيعرض \u يهرب مثل الأحرف المقابلة في أي سياق ، على الرغم من ذلك.)

يوجد خطأ تصميم مماثل في عائلة C ، حيث تتم معالجة الخط المائل للخلف (Backslash-newline) قبل تحديد حدود التعليقات ، على سبيل المثال

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

أحمل هذا لتوضيح أنه من السهل أن تصنع هذا الخطأ في تصميم معين ، ولا تدرك أنه خطأ حتى يفوتك تصحيحه ، إذا كنت معتادًا على التفكير في التوكيز وتحليل طريقة التفكير في المبرمجين حول التوكيد والتحليل. بشكل أساسي ، إذا كنت قد حددت قواعد اللغة الرسمية الخاصة بك ومن ثم يأتي شخص ما مع الحالة الخاصة النحوية - مثل trigraphs ، الخط المائل للخلف - الخط الجديد ، ترميز أحرف Unicode العشوائية في ملفات المصدر مقتصرة على ASCII ، أيا كان - الذي يجب أن يتم تثبيته ، من الأسهل إضافة مسار تحويل قبل أداة tokenizer بدلاً من إعادة تعريف tokenizer لإيلاء الاهتمام إلى المكان الذي يكون فيه من المنطقي استخدام هذه الحالة الخاصة.

1 ﺑﺎﻟﻨﺴﺒﺔ ﻟﻠﻤﺘﻌﺎﻗﺪﻳﻦ: أدرك أن هﺬا اﻟﺠﺎﻧﺐ ﻣﻦ اﻟﺠﺎﻧﺐ C آﺎن 100٪ ﻣﺘﻌﻤﺪ ، ﻣﻊ اﻷﺳﺎس اﻟﻤﻨﻄﻘﻲ - ﻟﺴﺄﻧﻲ هﺬا اﻷﻣﺮ - أﻧﻪ ﺳﻮف ﻳﺴﻤﺢ ﻟﻚ ﺑﺈدﺧﺎل رﻣﺰ ﻣﻄﺎﺑﻖ ﻣﻴﻜﺎﻧﻴﻜﻴﺎً ﻣﻊ ﺧﻄﻮط ﻃﻮﻳﻠﺔ ﺗﻌﺴﻔﻴﺔ ﻋﻠﻰ ﺑﻄﺎﻗﺎت ﻣﺜﻘﺒﺔ. كان لا يزال قرار تصميم غير صحيح.


كان هذا اختيار تصميم متعمد يعود بالكامل إلى التصميم الأصلي لـ Java.

لأولئك الناس الذين يسألون "من يريد Unicode يهرب في التعليقات؟" ، أفترض أنهم من الناس الذين تستخدم لغتهم الأم مجموعة الأحرف اللاتينية. وبعبارة أخرى ، فإنه من الطبيعي في التصميم الأصلي لـ Java أن يتمكن الأشخاص من استخدام أحرف Unicode العشوائية حيثما كانت قانونية في برنامج Java ، معظمها عادةً في التعليقات والسلاسل.

يمكن القول إن هناك نقصًا في البرامج (مثل IDEs) المستخدمة لعرض النص المصدر الذي لا تستطيع مثل هذه البرامج تفسير عمليات الهروب من Unicode وعرض الصورة الرمزية المقابلة.


بما أن هذا لم يعالج بعد ، هنا تفسير ، لماذا تحدث ترجمة Unicode قبل أي معالجة أخرى لشفرة المصدر:

كانت الفكرة وراء ذلك أنها تسمح بترجمات بدون شفرة لشفرة مصدر Java بين ترميزات الأحرف المختلفة. اليوم ، هناك دعم يونيكود واسع الانتشار ، وهذا لا يبدو وكأنه مشكلة ، ولكن في ذلك الوقت لم يكن من السهل على مطور من دولة غربية أن يحصل على بعض الكود المصدري من زميله الآسيوي الذي يحتوي على أحرف آسيوية ، وإجراء بعض التغييرات ( بما في ذلك تجميع واختبارها) وإرسال النتيجة مرة أخرى ، كل ذلك دون الإضرار بشيء ما.

لذلك ، يمكن كتابة شفرة مصدر جافا في أي ترميز وتسمح بنطاق واسع من الأحرف داخل المعرفات والحرف والقيم الحرفية والتعليقات. ثم ، من أجل نقله بدون خسارة ، يتم استبدال كل الحروف غير المدعومة بترميز الهدف برموز Unicode الخاصة بها.

هذه عملية قابلة للانعكاس والنقطة المثيرة للاهتمام هي أن الترجمة يمكن أن تتم عن طريق أداة لا تحتاج إلى معرفة أي شيء عن بنية شفرة مصدر Java حيث أن قاعدة الترجمة لا تعتمد عليها. يعمل هذا عند ترجمة أحرف Unicode الفعلية الخاصة بهم داخل المحول البرمجي يحدث بشكل مستقل إلى بناء جملة شفرة مصدر Java أيضًا. وهذا يعني أنه يمكنك تنفيذ عدد عشوائي من خطوات الترجمة في كلا الاتجاهين دون تغيير معنى شفرة المصدر.

هذا هو سبب ميزة غريبة أخرى لم تذكر حتى: بناء الجملة \uuuuuuxxxx :

عندما تخرج أداة الترجمة من الحروف وتصادف تسلسلاً متسلسلًا بالفعل ، يجب إدخال u إضافية في التسلسل ، تحويل \ucafe إلى \uucafe . لا يتغير المعنى ، ولكن عند التحويل إلى الاتجاه الآخر ، يجب أن تقوم الأداة فقط بإزالة u واستبدال التسلسلات التي تحتوي على u بأحرف Unicode الخاصة بها. بهذه الطريقة ، يتم الاحتفاظ حتى يهرب يونيكود في شكلها الأصلي عند تحويل ذهابا وإيابا. أعتقد ، لا أحد استخدم هذه الميزة من أي وقت مضى ...


المترجم لا يترجم فقط يهرب يونيكود إلى الأحرف التي يمثلونها قبل أن يوزع برنامج في الرموز ، لكنه يفعل ذلك قبل تجاهل التعليقات والمساحة البيضاء.

يحتوي هذا البرنامج على نظام Unicode منفرد (\ u000d) ، موجود في تعليقه الوحيد. كما يخبرك التعليق ، يمثل هذا الهروب حرف تغذية ، ويترجم المجمع على النحو الواجب قبل تجاهل التعليق .

إنه يعتمد على النظام الأساسي ، في بعض نماذج plat ، مثل UNIX ، فإنه يعمل على الآخرين ، مثل Windows ، لن يعمل. على الرغم من أن الإخراج قد يبدو بنفس الشكل للعين المجردة ، إلا أنه يمكن أن يسبب مشاكل بسهولة إذا تم حفظه في ملف أو توجيهه إلى برنامج آخر للمعالجة اللاحقة.


أتفق معzwol في أن هذا خطأ في التصميم ؛ لكنني أكثر انتقادا من ذلك.

\u Escape مفيد في السلسلة و char charmes؛ وهذا هو المكان الوحيد الذي يجب أن يكون موجودًا فيه. يجب التعامل معها بنفس الطريقة التي تتم بها عمليات الهروب الأخرى مثل \n ؛ و "\u000A" يجب أن تعني "\n" بالضبط.

لا يوجد أي نقطة على الإطلاق من وجود \uxxxx في التعليقات - لا أحد يستطيع قراءة ذلك.

وبالمثل ، ليس هناك فائدة من استخدام \uxxxx في الجزء الآخر من البرنامج. الاستثناء الوحيد هو على الأرجح في واجهات برمجة التطبيقات العامة التي يتم فرضها على احتواء بعض الأحرف غير التابعة لـ ascii - ما هي آخر مرة رأيناها؟

كان للمصممين أسبابهم في عام 1995 ، ولكن بعد 20 سنة ، يبدو أن هذا خيار خاطئ.

(سؤال للقراء - لماذا يستمر هذا السؤال في الحصول على أصوات جديدة؟ هل يرتبط هذا السؤال من مكان ما؟)


سأتركها هنا فقط أي شخص لديه الكثير من الوقت (وحدة المعالجة المركزية) لتجنيب ، لا تتردد في التجربة :) أيضا ، إذا كنت قد أتقن بعض شوكة الانضمام فو لجعل هذا الشيء حرق جميع نواة وحدة المعالجة المركزية (مجرد خيوط ممل ، أليس كذلك؟) ، يرجى حصة كودك أنا في غاية الامتنان.

public static void main(String[] args) {
    long time = System.currentTimeMillis();
    generate("stack");
    generate("over");
    generate("flow");
    generate("rulez");

    System.out.println("Took " + (System.currentTimeMillis() - time) + " ms");
}

private static void generate(String goal) {
    long[] seed = generateSeed(goal, Long.MIN_VALUE, Long.MAX_VALUE);
    System.out.println(seed[0]);
    System.out.println(randomString(seed[0], (char) seed[1]));
}

public static long[] generateSeed(String goal, long start, long finish) {
    char[] input = goal.toCharArray();
    char[] pool = new char[input.length];
    label:
    for (long seed = start; seed < finish; seed++) {
        Random random = new Random(seed);

        for (int i = 0; i < input.length; i++)
            pool[i] = (char) random.nextInt(27);

        if (random.nextInt(27) == 0) {
            int base = input[0] - pool[0];
            for (int i = 1; i < input.length; i++) {
                if (input[i] - pool[i] != base)
                    continue label;
            }
            return new long[]{seed, base};
        }

    }

    throw new NoSuchElementException("Sorry :/");
}

public static String randomString(long i, char base) {
    System.out.println("Using base: '" + base + "'");
    Random ran = new Random(i);
    StringBuilder sb = new StringBuilder();
    for (int n = 0; ; n++) {
        int k = ran.nextInt(27);
        if (k == 0)
            break;

        sb.append((char) (base + k));
    }

    return sb.toString();
}

انتاج:

-9223372036808280701
Using base: 'Z'
stack
-9223372036853943469
Using base: 'b'
over
-9223372036852834412
Using base: 'e'
flow
-9223372036838149518
Using base: 'd'
rulez
Took 7087 ms




java unicode comments