في الإصدار السابع من محلل ثابت PVS-Studio ، أضفنا الدعم للغة Java. حان الوقت للتحدث قليلاً عن كيف بدأنا في تقديم الدعم للغة Java وماذا فعلنا وما هي الخطط المستقبلية. وبالطبع ، ستُظهر المقالة الاختبارات الأولى للمحلل في المشروعات المفتوحة.
PVS-Studio
لمطوري Java الذين لم يسمعوا عن أداة PVS-Studio من قبل ، سأقدم وصفًا موجزًا لها.
PVS-Studio هي أداة للكشف عن الأخطاء ونقاط الضعف المحتملة في الكود المصدري للبرامج المكتوبة بلغات C و C ++ و C # و Java. إنه يعمل على Windows و Linux و macOS.
يقوم برنامج PVS-Studio بتحليل الشفرة الثابتة ويقوم بإنشاء تقرير يساعد المبرمج في العثور على العيوب وحلها. بالنسبة لأولئك المهتمين بالتحديد في كيفية بحث PVS-Studio عن الأخطاء ، أقترح عليك قراءة المقالة "
التقنيات المستخدمة في محلل شفرة PVS-Studio لإيجاد الأخطاء ونقاط الضعف المحتملة ".
ابدأ
يمكن أن أتوصل إلى قصة ذكية ، حيث كنا نفكر لمدة عامين في اللغة التالية التي يجب دعمها في PVS-Studio. حقيقة أن Java هي اختيار معقول استنادًا إلى الشعبية العالية لهذه اللغة وما إلى ذلك.
ومع ذلك ، كما يحدث في الحياة ، فقد تقرر كل شيء ليس عن طريق التحليل المتعمق ، ولكن عن طريق التجربة :). نعم ، كنا نفكر في الاتجاه الذي ينبغي فيه تطوير محلل PVS-Studio. تم اعتبار لغات البرمجة مثل: Java و PHP و Python و JavaScript و IBM RPG. وكنا نميل إلى لغة جافا ، ولكن لم يتم اتخاذ القرار النهائي بعد. أولئك الذين تتعثر أعينهم على RPG IBM غير مألوف ، أود أن أشير إلى هذه
الملاحظة هنا ، والتي سوف يصبح كل شيء واضحًا.
في نهاية عام 2017 ، نظر الزميل إيغور بريديكين إلى المكتبات الجاهزة لتحليل الأكواد البرمجية (بمعنى آخر ، المحللون) المتاحة للاتجاهات الجديدة التي تهمنا. وصادفت عدة مشاريع لتحليل كود جافا. واستنادا إلى
ملعقة ، تمكن بسرعة من صنع محلل أولي مع اثنين من التشخيصات. علاوة على ذلك ، أصبح من الواضح أنه يمكننا استخدام بعض آليات محلل C ++ بمساعدة
SWIG في محلل Java. نظرنا إلى ما حدث وأدركنا أن محللنا التالي سيكون لـ Java.
شكرا لإيجور على تعهده والعمل النشط الذي قام به على محلل جافا. كيف تطورت التطورات التي وصفها في مقال "
تطوير محلل ثابت جديد: PVS-Studio Java ".
المنافسين؟
هناك العديد من برامج تحليل الشفرات الثابتة المجانية والتجارية لجافا في العالم. ليس من المنطقي إدراجها جميعًا في المقالة ، وأترك رابطًا فقط "
قائمة أدوات لتحليل الشفرة الثابتة " (انظر قسم Java و Multi-language).
ومع ذلك ، أعرف أنه أولاً وقبل كل شيء سوف يتم سؤالنا عن IntelliJ IDEA و FindBugs و SonarQube (SonarJava).
IntelliJ IDEAيحتوي IntelliJ IDEA على محلل أكواد ثابت قوي للغاية. علاوة على ذلك ، يتطور المحلل ، ويراقب مؤلفوه عن كثب أنشطتنا. مع IntelliJ IDEA سنكون الأكثر صعوبة. لن نكون قادرين على تجاوز IntelliJ IDEA في القدرات التشخيصية ، على الأقل حتى الآن. لذلك ، سنحاول التركيز على مزايانا الأخرى.
يعد التحليل الثابت في IntelliJ IDEA ، أولاً وقبل كل شيء ، أحد شرائح بيئة التطوير التي تفرض قيودًا معينة عليه. نحن أحرار في ما يمكننا القيام به مع محلل لدينا. على سبيل المثال ، يمكننا تكييف المحلل بسرعة مع الاحتياجات المحددة للعميل. الدعم السريع والعميق هو ميزتنا التنافسية. يتواصل عملاؤنا مباشرةً مع المبرمجين الذين يقومون بتطوير جزء معين من PVS-Studio.
يحتوي برنامج PVS-Studio على العديد من الإمكانيات لدمجه في دورة تطوير المشروعات القديمة الكبيرة. هذا هو
التكامل مع SonarQube . هذا
قمع هائل لرسائل المحلل ، والذي يتيح لك البدء فوراً في استخدام المحلل في مشروع كبير لتتبع الأخطاء فقط في الكود الجديد أو المتغير. تم
دمج PVS-Studio في عملية التكامل المستمر. أعتقد أن هذه الميزات وغيرها ستساعد محللنا في العثور على مكان تحت الشمس في عالم جافا.
Findbugsتم
التخلي عن مشروع FindBugs. ولكن يجب أن نتذكرها لسبب أن هذا ربما يكون أكثر المحلل الثابت المجاني شهرة لرمز Java.
الخلف لـ FindBugs هو مشروع
SpotBugs . ومع ذلك ، فهو أقل شعبية ، وما سيحدث له ليس واضحًا تمامًا بعد.
بشكل عام ، نعتقد أنه على الرغم من أن FindBugs كان ولا يزال يتمتع بشعبية كبيرة ، وأيضًا أنه محلل مجاني ، يجب ألا نفكر فيه. هذا المشروع هو مجرد بهدوء وهدوء شيء من الماضي.
PS بالمناسبة ، الآن يمكن أيضًا
استخدام PVS-Studio
مجانًا عند العمل مع المشاريع المفتوحة.
SonarQube (SonarJava)نعتقد أننا لا نتنافس مع SonarQube ، لكننا نكمله. يتكامل PVS-Studio مع SonarQube ، والذي يسمح للمطورين بالعثور على المزيد من الأخطاء ونقاط الضعف المحتملة في مشاريعهم. كيفية دمج أداة PVS-Studio وغيرها من أجهزة التحليل في SonarQube ، نتحدث بانتظام في فصول الماجستير التي نعقدها في مؤتمرات مختلفة (على
سبيل المثال ).
كيفية بدء PVS-Studio لجافا
لقد أتاحنا للمستخدمين أكثر الطرق شيوعًا لدمج المحلل في نظام التجميع:
- البرنامج المساعد ل Maven.
- البرنامج المساعد ل Gradle.
- البرنامج المساعد ل IntelliJ IDEA
في مرحلة الاختبار ، التقينا بالعديد من المستخدمين الذين لديهم أنظمة تجميع مكتوبة ذاتيًا ، خاصةً في مجال تطوير الأجهزة المحمولة. لقد أحبوا القدرة على تشغيل المحلل مباشرةً ، مع سرد المصادر و classpath.
يمكنك العثور على معلومات مفصلة حول جميع طرق بدء تشغيل المحلل في صفحة الوثائق "
كيفية بدء تشغيل PVS-Studio Java ".
لم نتمكن من تجاهل نظام التحكم في جودة كود
SonarQube ، وهو أمر شائع بين مطوري Java ، لذلك أضفنا دعم لغة Java إلى
المكون الإضافي SonarQube .
خطط أخرى
لدينا العديد من الأفكار التي تحتاج إلى مزيد من الدراسة ، لكن بعض الخطط الخاصة بأي من المحللين لدينا تبدو كما يلي:
- إنشاء تشخيصات جديدة وصقل التشخيصات الحالية ؛
- تطوير تحليل تدفق البيانات.
- تحسين الموثوقية وسهولة الاستخدام.
قد نجد الوقت لتكييف البرنامج المساعد IntelliJ IDEA لـ CLion. مرحبًا C ++ للمطورين الذين يقرأون عن محلل Java :-)
أمثلة على الأخطاء الموجودة في مشاريع مفتوحة المصدر
لن أكون أنا إذا لم أعرض أي أخطاء تم العثور عليها باستخدام المحلل الجديد في المقالة. يمكن أن نأخذ مشروع جافا مفتوح المصدر وكتابة مقال كلاسيكي مع تحليل الأخطاء ، كما
نفعل عادة .
ومع ذلك ، أتوقع فورًا أسئلة حول ما إذا كان بإمكاننا العثور على شيء في مشاريع مثل IntelliJ IDEA و FindBugs وما إلى ذلك. لذلك ، أنا ببساطة ليس لدي مخرج ، وسأبدأ بدقة مع هذه المشاريع. لذلك ، قررت أن أفحص بسرعة وأكتب بعض الأمثلة المثيرة للاهتمام للأخطاء من المشاريع التالية:
- IntelliJ IDEA Community Edition . أعتقد أنه لا توجد حاجة لشرح سبب اختيار هذا المشروع :).
- SpotBugs كما كتبت في وقت سابق ، فإن مشروع FindBugs لا يتطور. لذلك ألق نظرة على مشروع SpotBugs ، والذي يعد خليفة FindBugs. SpotBugs هو محلل جافا كود ثابت الكلاسيكية.
- بعض مشاريع SonarSource ، التي تطور برامج لمراقبة جودة الكود المستمرة. ألقِ نظرة على مشاريع SonarQube و SonarJava .
الكتابة عن الأخطاء في هذه المشاريع هي مهمة صعبة. والحقيقة هي أن هذه المشاريع ذات جودة عالية جدا. في الواقع ، هذا ليس مستغربا. كما تظهر ملاحظاتنا ، يتم دائمًا اختبار رمز التحليلات الثابتة والتحقق منه باستخدام أدوات أخرى.
على الرغم من كل هذا ، يجب أن أبدأ بهذه المشاريع ذاتها. لن يكون لدي فرصة ثانية لكتابة شيء عنهم. أنا متأكد من أنه بعد إصدار PVS-Studio for Java ، سيضع مطورو هذه المشروعات PVS-Studio في الخدمة وسيبدأون في استخدامها لفحصها بانتظام أو على الأقل دوريًا لرمزهم. على سبيل المثال ، أعرف أن Tagir Valeyev (
lany ) ، أحد مطوري JetBrains الذي يعمل في محلل الكود الثابت IntelliJ IDEA ، يلعب بالفعل مع إصدار Beta من PVS-Studio في الوقت الذي أكتب فيه المقالة. لقد كتب لنا بالفعل حوالي 15 رسالة مع تقارير الشوائب والتوصيات. شكرا يا تاجير!
لحسن الحظ ، لست بحاجة إلى العثور على أكبر عدد ممكن من الأخطاء في مشروع واحد معين. مهمتي الآن هي إظهار أن محلل PVS-Studio لجافا لم يظهر هباء وسيتمكن من تجديد سطر الأدوات الأخرى المصممة لتحسين جودة الشفرة. لقد ألقيت نظرة سريعة على تقارير المحلل وكتبت بعض الأخطاء التي بدت مثيرة للاهتمام بالنسبة لي. كلما كان ذلك ممكنا ، حاولت أن أكتب أخطاء من مختلف الأنواع. دعونا نرى ما حدث.
IntelliJ IDEA شعبة عدد صحيح
private static boolean checkSentenceCapitalization(@NotNull String value) { List<String> words = StringUtil.split(value, " "); .... int capitalized = 1; .... return capitalized / words.size() < 0.2;
تحذير PVS-Studio: V6011 [CWE-682] تتم مقارنة الحرف "0.2" من النوع "المزدوج" بقيمة من النوع "int". TitleCapitalizationInspection.java 169
وفقًا لما هو مقصود ، يجب أن تُرجع الدالة صواب إذا بدأ أقل من 20٪ من الكلمات بحرف كبير. في الواقع ، لا يعمل التحقق ، حيث يحدث تقسيم صحيح. نتيجة القسمة ، يمكن الحصول على قيمتين فقط: 0 أو 1.
ستُرجع الدالة قيمة خاطئة فقط إذا كانت كل الكلمات تبدأ بحرف كبير. في جميع الحالات الأخرى ، ستنتج القسمة 0 ، وستُرجع الدالة القيمة الحقيقية.
دورة IntelliJ IDEA المشبوهة
public int findPreviousIndex(int current) { int count = myPainter.getErrorStripeCount(); int foundIndex = -1; int foundLayer = 0; if (0 <= current && current < count) { current--; for (int index = count - 1; index >= 0; index++) {
تحذير PVS-Studio: V6007 [CWE-571] تعبير 'index> = 0' صحيح دائمًا. Updater.java 184
أولاً ، انظر إلى الحالة
(0 <= الحالية && <<العد) . يتم تنفيذها فقط إذا كانت قيمة متغير
العدد أكبر من 0.
انظر الآن إلى الحلقة:
for (int index = count - 1; index >= 0; index++)
تتم تهيئة
مؤشر متغير بواسطة
عدد التعبير
- 1 . نظرًا لأن متغير
العدد أكبر من 0 ، فإن القيمة الأولية لمتغير
الفهرس تكون دائمًا أكبر من أو تساوي 0. اتضح أنه سيتم تنفيذ الحلقة حتى يفيض متغير
المؤشر .
على الأرجح ، هذا مجرد خطأ مطبعي ولا يجب تنفيذ الزيادة ، ولكن التراجع المتغير:
for (int index = count - 1; index >= 0; index--)
IntelliJ IDEA ، نسخ ولصق
@NonNls public static final String BEFORE_STR_OLD = "before:"; @NonNls public static final String AFTER_STR_OLD = "after:"; private static boolean isBeforeOrAfterKeyword(String str, boolean trimKeyword) { return (trimKeyword ? LoadingOrder.BEFORE_STR.trim() : LoadingOrder.BEFORE_STR).equalsIgnoreCase(str) || (trimKeyword ? LoadingOrder.AFTER_STR.trim() : LoadingOrder.AFTER_STR).equalsIgnoreCase(str) || LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase(str) ||
تحذير PVS-Studio: V6001 [CWE-570] هناك تعبيرات فرعية متطابقة 'LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase (str)' إلى اليسار وإلى يمين "||" المشغل. خطوط التحقق: 127 ، 128. ExtensionOrderConverter.java 127
تأثير قديم جيد
من السطر الأخير . سارع المبرمج إلى الأعلى ، وبعد أن تضاعف سطر من التعليمات البرمجية ، نسي إصلاحه. نتيجة لذلك ،
يتم مقارنة ضعف سلسلة
str بـ
BEFORE_STR_OLD . على الأرجح ، يجب أن تكون إحدى المقارنات مع
AFTER_STR_OLD .
خطأ IntelliJ IDEA
public synchronized boolean isIdentifier(@NotNull String name, final Project project) { if (!StringUtil.startsWithChar(name,'\'') && !StringUtil.startsWithChar(name,'\"')) { name = "\"" + name; } if (!StringUtil.endsWithChar(name,'"') && !StringUtil.endsWithChar(name,'\"')) { name += "\""; } .... }
تحذير PVS-Studio: V6001 [CWE-571] هناك تعبيرات فرعية متطابقة '! StringUtil.endsWithChar (الاسم ،' "')' إلى اليسار وإلى يمين المشغل" && ". JsonNamesValidator.java 27
يتحقق جزء التعليمات البرمجية هذا من أن الاسم ضمن علامات اقتباس مفردة أو مزدوجة. إذا لم يكن الأمر كذلك ، تتم إضافة علامات اقتباس مزدوجة تلقائيًا.
بسبب خطأ مطبعي ، يتم التحقق من نهاية الاسم فقط من أجل علامات اقتباس مزدوجة. نتيجة لذلك ، لن تتم معالجة الاسم المأخوذ في علامات اقتباس مفردة بشكل صحيح.
الاسم الأول
'Abcd'
بسبب إضافة علامات الاقتباس المزدوجة الإضافية ، ستتحول إلى:
'Abcd'"
IntelliJ IDEA ، حماية تجاوز سعة صفيف غير صحيحة
static Context parse(....) { .... for (int i = offset; i < endOffset; i++) { char c = text.charAt(i); if (c == '<' && i < endOffset && text.charAt(i + 1) == '/' && startTag != null && CharArrayUtil.regionMatches(text, i + 2, endOffset, startTag)) { endTagStartOffset = i; break; } } .... }
تحذير PVS-Studio: V6007 [CWE-571] التعبير 'i <endOffset' صحيح دائمًا. EnterAfterJavadocTagHandler.java 183
إن subexpression
i <endOffset في حالة
العبارة if غير منطقي. المتغير
i أقل دومًا من
endOffset ، كما يلي من شرط تنفيذ الحلقة.
على الأرجح ، أراد المبرمج حماية نفسه من الخروج عند استدعاء الوظائف:
- text.charAt (i + 1)
- CharArrayUtil.regionMatches (النص ، i + 2 ، endOffset ، startTag)
في هذه الحالة ، يجب أن يكون
التعبير الفرعي للتحقق من الفهرس كما يلي:
i <endOffset - 2 .
IntelliJ IDEA كرر التحقق
public static String generateWarningMessage(....) { .... if (buffer.length() > 0) { if (buffer.length() > 0) { buffer.append(" ").append( IdeBundle.message("prompt.delete.and")).append(" "); } } .... }
تحذير PVS-Studio: V6007 [CWE-571] تعبير 'buffer.length ()> 0' صحيح دائمًا. DeleteUtil.java 62
هذا يمكن أن يكون إما رمز زائدة عن الحاجة ضارة أو خطأ خطير.
إذا ظهر فحص مكرر بالصدفة ، على سبيل المثال ، أثناء إعادة التوطين ، فلا حرج في ذلك. يمكن ببساطة حذف الشيك الثاني.
لكن سيناريو آخر ممكن. يجب أن يكون الاختيار الثاني مختلفًا تمامًا ولا يتصرف الرمز كما هو مقصود. ثم هذا خطأ حقيقي.
ملاحظة بالمناسبة ، هناك الكثير من الشيكات الزائدة عن الحاجة. علاوة على ذلك ، كثيرا ما ينظر إلى أن هذا ليس خطأ. ومع ذلك ، لا يمكن أن يسمى رسائل محلل ايجابيات كاذبة سواء. للتوضيح ، إليك مثال ، مأخوذ أيضًا من IntelliJ IDEA:
private static boolean isMultiline(PsiElement element) { String text = element.getText(); return text.contains("\n") || text.contains("\r") || text.contains("\r\n"); }
يقول المحلل أن النص
text.contains ("\ r \ n") دائمًا ما يكون خطأً. في الواقع ، إذا لم يتم العثور على الرمز "\ n" و "\ r" ، فلا فائدة من البحث عن "\ r \ n". هذا ليس خطأ ، والرمز سيء فقط لأنه يعمل بشكل أبطأ قليلاً ، ويقوم بالبحث بلا معنى عن سلسلة فرعية.
كيفية التعامل مع هذا الرمز ، في كل حالة ، يعود الأمر للمبرمجين لاتخاذ القرار. عند كتابة المقالات ، كقاعدة عامة ، أنا ببساطة لا ألاحظ هذا الرمز.
IntelliJ IDEA ، هناك خطأ ما
public boolean satisfiedBy(@NotNull PsiElement element) { .... @NonNls final String text = expression.getText().replaceAll("_", ""); if (text == null || text.length() < 2) { return false; } if ("0".equals(text) || "0L".equals(text) || "0l".equals(text)) { return false; } return text.charAt(0) == '0'; }
تحذير PVS-Studio: V6007 [CWE-570] تعبير '"0". المساواة (النص)' غير صحيح دائمًا. ConvertIntegerToDecimalPredicate.java 46
هذا الكود يحتوي بالتأكيد على خطأ منطقي. ولكني أجد صعوبة في تحديد ما أراد المبرمج التحقق منه ، وكيفية تصحيح الخلل. لذلك ، هنا سأشير فقط إلى فحص لا معنى له.
في البداية ، يتم التحقق من أن السلسلة يجب أن تحتوي على حرفين على الأقل. إذا لم يكن كذلك ، فستُرجع الدالة
false .
التالي هو
"0". المساواة (النص) تحقق. لا معنى له ، حيث لا يمكن أن تحتوي السلسلة على حرف واحد فقط.
بشكل عام ، هناك خطأ ما هنا ويجب إصلاح الكود.
SpotBugs (الخلف لـ FindBugs) ، خطأ حد التكرار
public static String getXMLType(@WillNotClose InputStream in) throws IOException { .... String s; int count = 0; while (count < 4) { s = r.readLine(); if (s == null) { break; } Matcher m = tag.matcher(s); if (m.find()) { return m.group(1); } } throw new IOException("Didn't find xml tag"); .... }
تحذير PVS-Studio: V6007 [CWE-571] تعبير 'العد <4' صحيح دائمًا. Util.java 394
كما هو مخطط له ، يجب إجراء البحث عن علامة xml فقط في الأسطر الأربعة الأولى من الملف. ولكن نظرًا لحقيقة أنهم نسوا زيادة
عدد المتغيرات ، سيتم قراءة الملف بالكامل.
أولاً ، يمكن أن يكون هذا عملية بطيئة للغاية ، وثانياً ، في مكان ما في منتصف الملف ، يمكن العثور على شيء سيتم تفسيره كعلامة xml ، لكنه لن يكون كذلك.
SpotBugs (الخلف لـ FindBugs) ، الكتابة فوق القيم
private void reportBug() { int priority = LOW_PRIORITY; String pattern = "NS_NON_SHORT_CIRCUIT"; if (sawDangerOld) { if (sawNullTestVeryOld) { priority = HIGH_PRIORITY;
تحذير PVS-Studio: V6021 [CWE-563] تم تعيين القيمة للمتغير "الأولوية" ولكن لا يتم استخدامها. FindNonShortCircuit.java 197
يتم تعيين قيمة متغير
الأولوية اعتمادًا على قيمة المتغير
sawNullTestVeryOld . ومع ذلك ، هذا لا يلعب أي دور. علاوة على ذلك ، سيتم تعيين متغير
الأولوية بقيمة مختلفة في أي حال. خطأ واضح في منطق الوظيفة.
SonarQube ، نسخ لصق
public class RuleDto { .... private final RuleDefinitionDto definition; private final RuleMetadataDto metadata; .... private void setUpdatedAtFromDefinition(@Nullable Long updatedAt) { if (updatedAt != null && updatedAt > definition.getUpdatedAt()) { setUpdatedAt(updatedAt); } } private void setUpdatedAtFromMetadata(@Nullable Long updatedAt) { if (updatedAt != null && updatedAt > definition.getUpdatedAt()) { setUpdatedAt(updatedAt); } } .... }
PVS-Studio: V6032 من الغريب أن يكون نص الأسلوب "setUpdatedAtFromDefinition" مساوياً تمامًا لجسم طريقة أخرى "setUpdatedAtFromMetadata". خطوط التحقق: 396 ، 405. RuleDto.java 396
يستخدم الأسلوب
setUpdatedAtFromMetadata حقل
التعريف . على الأرجح ، يجب استخدام حقل
بيانات التعريف . هذا مشابه جدا لعواقب فشل نسخ اللصق.
SonarJava ، مكررة على خريطة التهيئة
private final Map<JavaPunctuator, Tree.Kind> assignmentOperators = Maps.newEnumMap(JavaPunctuator.class); public KindMaps() { .... assignmentOperators.put(JavaPunctuator.PLUSEQU, Tree.Kind.PLUS_ASSIGNMENT); .... assignmentOperators.put(JavaPunctuator.PLUSEQU, Tree.Kind.PLUS_ASSIGNMENT); .... }
تحذير PVS-Studio: V6033 [CWE-462] تمت إضافة عنصر بنفس المفتاح "JavaPunctuator.PLUSEQU". خطوط التحقق: 104 ، 100. KindMaps.java 104
يتم وضع نفس زوج قيمة المفتاح مرتين في البطاقة. على الأرجح ، تبين أن هذا غافل ، وفي الواقع لا يوجد خطأ حقيقي. ومع ذلك ، في أي حال ، يجب التحقق من هذا الرمز ، لأنك ربما نسيت إضافة زوج آخر.
الخاتمة
ولكن ماذا يمكن أن يكون هناك استنتاج؟! أدعو الجميع ، دون تأخير ، إلى تنزيل PVS-Studio ومحاولة اختبار مشاريعك العاملة في Java!
تحميل PVS-Studio .
شكرا لكم جميعا على اهتمامكم. آمل أن نسعد القراء قريبًا بمجموعة من المقالات المكرسة للتحقق من مختلف مشاريع Java المفتوحة.

إذا كنت ترغب في مشاركة هذا المقال مع جمهور يتحدث الإنجليزية ، فالرجاء استخدام الرابط الخاص بالترجمة: Andrey Karpov.
PVS-Studio لجافا .