برنامه نویسی

نحوه یافتن شغل برای رنجرز نجات: تجزیه و تحلیل موتور گودو

Summarize this content to 400 words in Persian Lang
توسعه و انجام بازی‌ها می‌تواند یک فعالیت فوق‌العاده جذاب، اعتیادآور و رضایت‌بخش باشد. اما هیچ چیز مانند یک باگ مخفی، تجربه گیم پلی را خراب نمی کند. بنابراین، امروز ما یک موتور بازی منبع باز – موتور Godot را بررسی خواهیم کرد. بیایید ببینیم چقدر خوب است و آیا آماده است تا احساسات فراموش نشدنی خلق و بازی کردن را به ما بدهد.

گودو

Godot یک موتور بازی دوبعدی و سه بعدی همه کاره است که برای تامین انرژی انواع پروژه ها طراحی شده است. می‌توانید از آن برای ایجاد بازی‌ها یا برنامه‌ها استفاده کنید و سپس آن‌ها را روی دسکتاپ، موبایل یا پلتفرم‌های وب منتشر کنید.

موتور هنوز جوان است، اما در حال حاضر در میان کسانی که از کد منبع باز، نرم افزار رایگان و توسعه پذیری خوب استقبال می کنند، مورد توجه قرار گرفته است. گودو کاملا مبتدی پسند است، بنابراین در بین توسعه دهندگان تازه کار محبوب است.

بازی هایی مانند 1000 Days to Escape، City Game Studio: Your Game Dev Adventure Begins، Precipice و موارد دیگر با استفاده از این موتور توسعه یافته اند.

نسخه Godot Engine که برای تجزیه و تحلیل استفاده کردیم 4.2.2 است.

به هر حال، ما قبلا موتور Godot را در سال 2018 بررسی کرده ایم. می توانید مقاله قبلی را اینجا بخوانید.

نتایج بررسی پروژه با استفاده از PVS-Studio

اولین چیزی که مایلیم بعد از بررسی گزارش با آن شروع کنیم، مسائل کلان پروژه است. پارامترها داخل پرانتز قرار نمی گیرند. بیایید به چند مثال نگاه کنیم که چگونه می تواند به پای شما شلیک کند.

قطعات N1-N2

#define HAS_WARNING(flag) (warning_flags & flag)

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما به این ماکرو نیاز داریم تا بررسی کنیم که آیا پرچم هشدار خاصی تنظیم شده است یا خیر.

این warning_flags متغیر یک بیت ماسک از uint_32t نوع این به این معنی است که مقدار آن 32 بیت است، که در آن هر بیت اگر پرچم تنظیم شده باشد 1 و اگر تنظیم نشده باشد 0 است. ماکرو در عبارات شرطی استفاده می شود، جایی که به طور ضمنی به تبدیل می شود بوول نوع برای اینکه بفهمیم چگونه کار می کند، به جای 32، یک نسخه ساده شده با 8 بیت را انتخاب می کنیم.

فرض کنید یک پرچم X داریم که مربوط به بیت چهارم در ماسک است و در حال حاضر تنظیم شده است. سپس، ارزش warning_flags متغیر در سیستم باینری به شکل زیر است:

00001000

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حالا بیایید بگوییم که ما تصمیم گرفتیم از ماکرو خود استفاده کنیم تا بررسی کنیم که آیا ایکس پرچم تنظیم شده است

عبور می کنیم پرچم متغیر با 00001000 مقدار به ماکرو، و مقدار بیتی AND به یک مقدار غیر صفر تبدیل می شود که به آن تبدیل می شود بوول با ارزش به درست است، واقعی.

حالا فرض کنید می خواهیم آن را بررسی کنیم Y پرچمی که مربوط به بیت سوم با همان مقدار است warning_flags متغیر*. متغیر را با *00000100 پاس می کنیم مقدار به ماکرو، و بیتی AND به یک مقدار صفر تبدیل می شود که به آن تبدیل می شود بوول با ارزش نادرست.

همه چیز عالی به نظر می رسد. چه چیزی ممکن است اشتباه کند؟ اما اگر کسی بخواهد بررسی کند که آیا یکی از آن پرچم ها تنظیم شده است، چه؟ آنها می توانند ماکرو را به صورت زیر فراخوانی کنند:

if (HAS_WARNING(flags::X | flags::Y)) ….

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس، نتیجه عملیات همیشه خواهد بود درست است، واقعی، حتی اگر هیچ یک از پرچم ها تنظیم نشده باشد. چرا این اتفاق می افتد؟ بیایید وانمود کنیم که یک پیش پردازنده هستیم و فقط عبارت ارسال شده به ماکرو را جایگزین کنیم:

if (warning_flags & flags::X | flags::Y) ….

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون به جدول اولویت عملگر نگاه می کنیم:

تقدم
اپراتور
شرح
انجمنی

….
….
….
….

11
الف و ب
به صورت بیتی و
چپ به راست

….
….
….
….

13
\

بیتی OR

اپراتورها از بالا به پایین به ترتیب تقدم نزولی فهرست شده اند. این به این معنی است که عبارت به صورت زیر پردازش می شود:

if (( warning_flags & flags::X ) | flags::Y) ….

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید بگوییم که ایکس و Y پرچم هایی که ما به آنها علاقه مندیم نصب نشده اند warning_flags. سپس، اولین عملیات بیتی AND 0 را برمی گرداند Y پرچم روی آن تنظیم شده است. ما یک چک همیشه واقعی دریافت می کنیم.

بنابراین، تحلیلگر هشدار زیر را برای این ماکرو صادر می کند:

V1003 ماکرو 'HAS_WARNING' یک عبارت خطرناک است. پارامتر “پرچم” باید با پرانتز احاطه شود. shader_language.cpp 40

همانطور که پیام می گوید، برای رفع آن فقط باید پارامتر ماکرو را در پرانتز قرار دهیم:

#define HAS_WARNING(flag) (warning_flags & (flag))

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا مثال دیگری از یک ماکرو خطرناک آورده شده است:

#define IS_SAME_ROW(i, row) (i / current_columns == row)

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V1003 ماکرو 'IS_SAME_ROW' یک عبارت خطرناک است. پارامترهای 'i'، 'row' باید با پرانتز احاطه شوند. item_list.cpp 643

اگر به جای یک متغیر، یک عبارت را به ماکرو ارسال کنیم، به این صورت:

IS_SAME_ROW(current + 1, row)

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس، در نتیجه جایگزینی پیش پردازش، موارد زیر را دریافت می کنیم:

(current + 1 / current_columns == row)

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ترتیب محاسبه در اینجا آن چیزی نیست که شما انتظار دارید.

برای محافظت در برابر چنین شرایطی، به سادگی پارامترهای ماکرو را در پرانتز قرار دهید:

#define IS_SAME_ROW(i, row) ((i) / current_columns == (row))

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قطعه N3

حال بیایید شرایط زیر را بررسی کنیم:

const auto hint_r = ShaderLanguage::ShaderNode::Uniform::HINT_ROUGHNESS_R;
const auto hint_gray = ShaderLanguage::ShaderNode::Uniform::HINT_ROUGHNESS_GRAY;

if (tex->detect_roughness_callback
&& ( p_texture_uniforms[i].hint >= hint_r
|| p_texture_uniforms[i].hint hint_gray))
{
….
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این شرایط همیشه هست درست است، واقعی (به جز زمانی که tex->detect_roughness_callback اشاره گر صفر است).

برای درک اینکه چرا این مورد است، باید نگاه کنیم enum اشاره در لباس فرم ساختار:

struct Uniform
{
….
enum Hint
{
HINT_NONE,
HINT_RANGE,
HINT_SOURCE_COLOR,
HINT_NORMAL,
HINT_ROUGHNESS_NORMAL,
HINT_ROUGHNESS_R,
HINT_ROUGHNESS_G,
HINT_ROUGHNESS_B,
HINT_ROUGHNESS_A,
HINT_ROUGHNESS_GRAY,
HINT_DEFAULT_BLACK,
HINT_DEFAULT_WHITE,
HINT_DEFAULT_TRANSPARENT,
HINT_ANISOTROPY,
HINT_SCREEN_TEXTURE,
HINT_NORMAL_ROUGHNESS_TEXTURE,
HINT_DEPTH_TEXTURE,
HINT_MAX
};
….
};

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

زیر سرپوش چنین enum یک نوع عدد صحیح است، و HINT_ROUGHNESS_R و HINT_ROUGHNESS_GRAY مقادیر مربوط به اعداد 5 و 9 است.

بر اساس موارد فوق، شرط آن را بررسی می کند p_texture_uniforms[i].hint >= 5 یا p_texture_uniforms[i].اشاره این بدان معنی است که هر مقدار از p_texture_uniforms[i].اشاره این چک ها را پاس می کند. این چیزی است که تحلیلگر PVS-Studio در مورد آن هشدار می دهد:

بیان V547 همیشه درست است. material_storage.cpp 929

در واقع، برنامه نویس می خواست بررسی کند که آیا p_texture_uniforms[i].اشاره در محدوده 5 تا 9 بود. برای انجام این کار، آنها باید از “AND” منطقی استفاده می کردند:

if (tex->detect_roughness_callback
&& ( p_texture_uniforms[i].hint >= hint_r
&& p_texture_uniforms[i].hint hint_gray))
{
….
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا یک هشدار مشابه وجود دارد:

بیان V547 همیشه درست است. material_storage.cpp 1003

قطعه N4

سعی کنید خطا را در اینجا پیدا کنید:

Error FontFile::load_bitmap_font(const String &p_path)
{
if (kpk.x >= 0x80 && kpk.x 0xFF)
{
kpk.x = _oem_to_unicode[encoding][kpk.x – 0x80];
} else if (kpk.x > 0xFF){
WARN_PRINT(vformat(“Invalid BMFont OEM character %x
(should be 0x00-0xFF).”, kpk.x));
kpk.x = 0x00;
}

if (kpk.y >= 0x80 && kpk.y 0xFF)
{
kpk.y = _oem_to_unicode[encoding][kpk.y – 0x80];
} else if (kpk.y > 0xFF){
WARN_PRINT(vformat(“Invalid BMFont OEM character %x
(should be 0x00-0xFF).”, kpk.x));
kpk.y = 0x00;
}
….
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V778 دو قطعه کد مشابه پیدا شد. شاید این اشتباه تایپی باشد و باید به جای x از متغیر 'y' استفاده شود. font.cpp 1970

بنابراین، PVS-Studio خطایی را پیدا کرده است که هنگام کپی کردن یک قطعه کد رخ داده است. بیایید نگاهی دقیق تر به بلوک های شرطی بیندازیم. آنها یکسان هستند با این تفاوت که در حالت اول همه عملیات بر روی آنها انجام می شود kpk.x، در حالی که در حالت دوم انجام می شوند kpk.y.

با این حال، شرط دوم به دلیل یک عملیات کپی پیست با خطا مواجه شد. نگاهی به WARN_PRINT تماس بگیرید: اگر kpk.y > 0xFF، kpk.x زمانی که اخطار صادر می شود، کاراکتر نمایش داده می شود، نه kpk.y. جستجوی خطا بر اساس گزارش‌ها دشوارتر است 🙂

PS: کد واقعاً نباید اینطور ضرب می شد. به وضوح می توانید ببینید که دو بلوک کد فقط در قسمت اعمال شده با هم تفاوت دارند. قرار دادن کد در یک تابع و دو بار فراخوانی آن برای فیلدهای مختلف گزینه بهتری خواهد بود:

Error FontFile::load_bitmap_font(const String &p_path)
{
constexpr auto check = [](auto &ch)
{
if (ch >= 0x80 && ch 0xFF)
{
auto res = _oem_to_unicode[encoding][ch – 0x80];
ch = res;
}
else if (ch > 0xFF)
{
WARN_PRINT(vformat(“Invalid BMFont OEM character %x
(should be 0x00-0xFF).”,ch));
ch = 0x00;
}
};

check(kpk.x);
check(kpk.y);
….
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قطعه N5

در اینجا شرایط بیشتری وجود دارد، اما آنها تودرتو هستند:

void GridMapEditor::_mesh_library_palette_input(const RefInputEvent> &p_ie)
{
const RefInputEventMouseButton> mb = p_ie;
// Zoom in/out using Ctrl + mouse wheel
if (mb.is_valid() && mb->is_pressed() && mb->is_command_or_control_pressed())
{
if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP)
{
size_slider->set_value(size_slider->get_value() + 0.2);
}
….
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V571 بررسی مکرر. شرط 'mb->is_pressed()' قبلاً در خط 837 تأیید شده است. grid_map_editor_plugin.cpp 838

این قطعه شامل یک بررسی اضافی در تودرتو است اگر بیانیه. این mb->is_pressed() عبارت قبلاً در بالا بررسی شده است. شاید این یک بررسی دوگانه باشد (در رابط کاربری گرافیکی رایج است)، اما پس از آن توسعه دهندگان باید یک نظر در مورد آن اضافه می کردند. یا شاید چیز دیگری باید بررسی می شد.

در اینجا هشدارهای مشابه وجود دارد:

V571 بررسی مکرر. شرط «!r_state.floor» قبلاً در خط 1711 تأیید شده بود. physics_body_3d.cpp 1713
V571 بررسی مکرر. شرط «!wd_window.is_popup» قبلاً در خط 2012 تأیید شده بود. display_server_x11.cpp 2013
V571 بررسی مکرر. شرط “member.variable->initializer” قبلاً در خط 946 تأیید شده است. gdscript_analyzer.cpp 949

قطعه N6

و این یک مورد کلاسیک عدم ارجاع اشاره گر قبل از بررسی آن است:

void GridMapEditor::_update_cursor_transform()
{
cursor_transform = Transform3D();
cursor_transform.origin = cursor_origin;
cursor_transform.basis = node->get_basis_with_orthogonal_index(cursor_rot);
cursor_transform.basis *= node->get_cell_scale();
cursor_transform = node->get_global_transform() * cursor_transform;

if (selected_palette >= 0)
{
if (node && !node->get_mesh_library().is_null())
{
cursor_transform *= node->get_mesh_library()
->get_item_mesh_transform(selected_palette);
}
}
….
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V595 نشانگر 'node' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 246، 251. grid_map_editor_plugin.cpp 246

نسبتاً عجیب است که یک اشاره گر را تغییر دهید و سپس آن را چند خط پایین بررسی کنید. شاید عدم ارجاع در کد دیرتر از بررسی ظاهر شد و توسعه دهنده متوجه بررسی زیر نشد.

هشدارهای مشابه:

V595 نشانگر 'p_ternary_op->true_expr' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 4518، 4525. gdscript_analyzer.cpp 4518
V595 نشانگر 'p_parent' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 4100، 4104. node_3d_editor_plugin.cpp 4100
V595 نشانگر «اقلام» قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 950، 951. project_export.cpp 950
V595 نشانگر 'title_bar' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 1153، 1163. editor_node.cpp 1153
V595 نشانگر 'render_target' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 2121، 2132. rastizer_canvas_gles3.cpp 2121
V595 نشانگر '_p' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 228، 231. dictionary.cpp 228
V595 نشانگر 'class_doc' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 1215، 1231. extension_api_dump.cpp 1215

قطعه N7

template class T, class U = uint32_t,
bool force_trivial = false, bool tight = false>
class LocalVector
{
….
public:
operator VectorT>() const
{
VectorT> ret;
ret.resize(size());
T *w = ret.ptrw();
memcpy(w, data, sizeof(T) * count);
return ret;
}
….
};

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V780 Instantiation of LocalVector: شیء 'w' از نوع غیر منفعل (غیر PDS) را نمی توان با استفاده از تابع memcpy کپی کرد. local_vector.h 280

این یک قطعه جالب است. این LocalVector قالب کلاس دارای یک عملگر تبدیل به بردار کلاس در این تبدیل، محتویات بردار فعلی باید در بردار جدید کپی شود. برای انجام این کار، توسعه دهندگان از memcpy تابع.

این همه خوب است تا زمانی که تی نوع قالب به طور ساده قابل کپی است. با این حال، تحلیلگر تخصص های مختلفی از آن را شناسایی کرده است LocalVector، که این خاصیت را نقض کرده است. به عنوان مثال، اجازه دهید نگاهی به LocalVector تخصص:

struct AnimationCompressionDataState
{
uint32_t components = 3;
LocalVectoruint8_t> data; // Committed packets.
struct PacketData
{
int32_t data[3] = { 0, 0, 0 };
uint32_t frame = 0;
};

float split_tolerance = 1.5;

LocalVectorPacketData> temp_packets;

// used for rollback if the new frame does not fit
int32_t validated_packet_count = -1;
….
};

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این AnimationCompressionDataState کلاس* حاوی الف LocalVector* که قابل کپی کردن نیست.

برای این مورد، یک یادداشت در وجود دارد memcpy مستندات: “اگر اشیاء به طور بالقوه همپوشانی دارند یا قابل کپی نیستند، رفتار memcpy مشخص نیست و ممکن است تعریف نشده باشد”.

ما به راحتی می توانیم کد را با جایگزین کردن آن برطرف کنیم memcpy تماس با std::uniitialized_copy:

operator VectorT>() const
{
VectorT> ret;
ret.resize(size());
T *w = ret.ptrw();
std::uninitialized_copy(data, data + count, w);
return ret;
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

آنالایزر PVS-Studio 38 تخصص خطرناک تر را شناسایی کرده است، اما البته لیست کامل را ارائه نمی دهم:

V780 Instantiation of LocalVector: شیء 'w' از نوع غیر منفعل (غیر PDS) را نمی توان با استفاده از تابع memcpy کپی کرد. local_vector.h 280
V780 Instantiation of LocalVector: شیء 'w' از نوع غیر منفعل (غیر PDS) را نمی توان با استفاده از تابع memcpy کپی کرد. local_vector.h 280
V780 Instantiation of LocalVector: شیء 'w' از نوع غیر منفعل (غیر PDS) را نمی توان با استفاده از تابع memcpy کپی کرد. local_vector.h 280
V780 Instantiation of LocalVector >: شیء 'w' از نوع غیر منفعل (غیر PDS) را نمی توان با استفاده از تابع memcpy کپی کرد. local_vector.h 280
V780 Instantiation of LocalVector , uint32_t, bool, bool >: شیء 'w' از نوع غیر منفعل (غیر PDS) را نمی توان با استفاده از تابع memcpy کپی کرد. local_vector.h 280

قطعه N8

در اینجا یک نقض احتمالی منطق برنامه وجود دارد:

Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl
(int p_line)
{
const String &str = text_edit->get_line(p_line);
….
if ( is_digit(str[non_op])
|| ( str[non_op] == ‘.’
&& non_op line_length
&& is_digit(str[non_op + 1]) ) )
{
in_number = true;
}
….
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V781 مقدار شاخص 'non_op' پس از استفاده بررسی می شود. شاید اشتباهی در منطق برنامه وجود داشته باشد. gdscript_highlighter.cpp 370

این non_op مقدار ابتدا به عنوان شاخص هنگام دسترسی به کاراکترهای رشته استفاده می شود. فقط پس از آن مقدار بررسی می شود که کمتر از طول باشد.

به دسترسی به رشته بعد از بررسی نگاهی بیندازید. اگر non_op، به این معنی نیست (non_op + 1) . بنابراین، یک شاخص خارج از محدوده ممکن است در داخل اتفاق بیفتد خ[non_op + 1]. به خصوص از آنجایی که هیچ رشته تهی در زیر وجود ندارد رشته کاپوت ماشین.

یک بررسی صحیح باید به شکل زیر باشد:

if ( is_digit(str[non_op])
|| ( str[non_op] == ‘.’
&& non_op + 1 line_length
&& is_digit(str[non_op + 1]) ) )
{
in_number = true;
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قطعه N9

struct Particles
{
….
int amount = 0;
….
};

void ParticlesStorage::_particles_update_instance_buffer(
Particles *particles,
const Vector3 &p_axis,
const Vector3 &p_up_axis)
{
….
uint32_t lifetime_split = ….;
// Offset VBO so you render starting at the newest particle.
if (particles->amount – lifetime_split > 0)
{
….
}
….
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V555 عبارت 'particles->amount – lifetime_split > 0' به صورت 'particles->amount != lifetime_split' کار خواهد کرد. particles_storage.cpp 959

مثال جالب است زیرا با وجود هشدار نه چندان صحیحی که توسط آنالیزور صادر شده است، نشان می دهد که توسعه دهندگان باید به چه نکاتی توجه کنند.

اگر تفاوت بین دو متغیر بدون علامت بزرگتر از صفر باشد، این عبارت از نظر معنایی برابر است با particles->amount != lifetime_split. شرط در نظر گرفته شده است نادرست فقط در صورتی که متغیرها برابر باشند. اگر عملوند سمت چپ کمتر از سمت راست باشد، یک wrap-around رخ می دهد و عبارت حاصل بزرگتر از صفر خواهد بود. اگر عملوند چپ بزرگتر از سمت راست باشد، این تفاوت بزرگتر از صفر خواهد بود.

با این حال، نکته دیگری که در اینجا باید به آن توجه کرد این است که هر دو متغیر دارای رتبه یکسان، اما علامت متفاوت هستند. استاندارد یک کامپایلر را ملزم می کند تا قبل از انجام تفریق، تبدیل های ضمنی را انجام دهد. در این مورد، 32 بیتی بدون علامت بین المللی نوع رایج آن است. اگر عملوند سمت چپ دارای یک عدد منفی باشد، این می تواند چند شگفتی اضافه کند.

صحیح ترین بررسی زمانی که عبارات نوع امضا شده و بدون علامت هم رتبه درگیر هستند به صورت زیر است:

if (particles->amount >= 0 && particles->amount > lifetime_split)

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در واقع، ما دوباره اختراع کرده ایم std::cmp_greaterکه در C++20 معرفی شد. با شروع از این نسخه استاندارد، می توانیم کد مختصر بنویسیم:

if (std::cmp_greater(particles->amount, lifetime_split))

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قطعه N10

void AnimationNodeStateMachineEditor::_delete_tree_draw()
{
TreeItem *item = delete_tree->get_next_selected(nullptr);
while (item)
{
delete_window->get_cancel_button()->set_disabled(false);
return;
}
delete_window->get_cancel_button()->set_disabled(true);
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

شرایط شکست حلقه V1044 به تعداد تکرارها بستگی ندارد. animation_state_machine_editor.cpp 693

این در حالی که حلقه دقیقا یک تکرار طول می کشد. این بسیار شبیه الگویی است که در آن فقط باید اولین عنصر را از ظرف برداریم. ما می توانیم این کار را با استفاده از برای حلقه:

for (auto &&item : items)
{
DoSomething(item);
break;
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بنابراین، لازم نیست بررسی کنیم که آیا ظرف اولین عنصر را دارد یا خیر. IMHO، کد نسبتاً گیج کننده است زیرا انتظار یک ناشناخته را دارید محدود، فانی تعداد تکرار از حلقه ها

با این حال، در قطعه بالا، در حالی که حلقه مطلقاً معنی ندارد. ساده اگر بیانیه کافی بود:

void AnimationNodeStateMachineEditor::_delete_tree_draw()
{
TreeItem *item = delete_tree->get_next_selected(nullptr);
if (item)
{
delete_window->get_cancel_button()->set_disabled(false);
return;
}

delete_window->get_cancel_button()->set_disabled(true);
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قطعه N11

static const char *script_list[][2] = {
….
{ “Myanmar / Burmese”, “Mymr” },
{ “​Nag Mundari”, “Nagm” },
{ “Nandinagari”, “Nand” },
….
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

خواننده ممکن است بپرسد “اینجا چه اشکالی دارد؟” اگر قانون عیب یابی V1076 نبود، خودمان آن را نمی فهمیدیم. جالب است که این اولین هشداری است که ما نوشته ایم. قانون تشخیصی کد برنامه را برای کاراکترهای نامرئی بررسی می کند. چنین کاراکترهایی مانند درهای پشتی هستند که ممکن است برنامه نویس به دلیل تنظیمات نمایش متن در محیط توسعه نبیند، اما کامپایلر آنها را می بیند و به درستی پردازش می کند.

هشدار آنالیزور:

کد V1076 حاوی کاراکترهای نامرئی است که ممکن است منطق آن را تغییر دهد. فعال کردن نمایش کاراکترهای نامرئی در ویرایشگر کد را در نظر بگیرید. locales.h 1114

بیایید نگاهی دقیق تر به خط بعدی بیندازیم:

{ “​Nag Mundari”, “Nagm” },

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این شامل درهای پشتی با یک شخصیت نامرئی است. اگر از ویرایشگر هگز استفاده کنیم، می توانیم موارد زیر را مشاهده کنیم:

3 بایت بین دو علامت کوتیشن و علامت ساندویچ وجود دارد ن شخصیت: E2، 80، و 8B. مطابقت دارند فضای صفر عرض (U+200B) نویسه یونیکد.

رشته ها از script_list آرایه ای که حاوی رشته “آلوده” است به معنای واقعی کلمه وارد می شود TranslationServer::script_map جدول هش کلید جدول هش رشته دوم جفت است و مقدار آن اولین است. بنابراین، رشته درپشتی تحت اللفظی به عنوان مقدار وارد جدول هش می شود و جستجوی هش شکسته نمی شود.

در مرحله بعد، می‌توانیم ببینیم که ارزش جدول هش به طور بالقوه می‌تواند به کجا برسد. من چند جا پیدا کردم:

مقدار می تواند به رشته ای که توسط the برگردانده شده است برود TranslationServer::get_locale_name تابع. با تجزیه و تحلیل توابع تماس گیرنده، می بینیم که این رشته وارد رابط کاربری گرافیکی می شود ([1]، [2]، [3]، [4]) به هر طریقی.
مقدار از TranslationServer::get_script_name تابع. با تجزیه و تحلیل توابع تماس گیرنده، همچنین می توانیم ببینیم که رشته وارد رابط کاربری گرافیکی می شود ([1]، [2]).

به احتمال زیاد، درب پشتی به طور تصادفی با کپی کردن نام از برخی وب سایت ها اضافه شده است. ما به سادگی می توانیم این کاراکتر را از رشته literal حذف کنیم.

قطعه N12

void MeshStorage::update_mesh_instances()
{
….
uint64_t mask = RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_NORMAL
| RS::ARRAY_FORMAT_VERTEX;
….
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V501 عبارت‌های فرعی یکسان «RenderingServer::ARRAY_FORMAT_VERTEX» در سمت چپ و سمت راست «|» وجود دارد. اپراتور. mesh_storage.cpp 1414
V578 یک عملیات بیتی عجیب و غریب شناسایی شد. بررسی آن را در نظر بگیرید. mesh_storage.cpp 1414

این یک بیت ماسک اولیه عجیب و غریب است. RS::ARRAY_FORMAT_VERTEX دو بار در آنجا نوشته شده است، اگرچه توسعه دهندگان ممکن است بخواهند پرچم دیگری بنویسند.

در اینجا یک هشدار مشابه وجود دارد:

V501 عبارت‌های فرعی یکسان «RenderingServer::ARRAY_FORMAT_VERTEX» در سمت چپ و سمت راست «|» وجود دارد. اپراتور. mesh_storage.cpp 1300
V578 یک عملیات بیتی عجیب و غریب شناسایی شد. بررسی آن را در نظر بگیرید. mesh_storage.cpp 1300

قطعه N13

void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps,
Format p_format, const Vectoruint8_t> &p_data)
{
….
ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, “The Image width specified (” +
itos(p_width) +
” pixels) cannot be greater than ” +
itos(MAX_WIDTH) +
” pixels.”);

ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, “The Image height specified (” +
itos(p_height) +
” pixels) cannot be greater than ” +
itos(MAX_HEIGHT) +
” pixels.”);

ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS,
“Too many pixels for image, maximum is ” + itos(MAX_PIXELS));
….
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V1083 سرریز عدد صحیح امضا شده در عبارت حسابی 'p_width * p_height' امکان پذیر است. این منجر به رفتار نامشخص می شود. عملوند چپ در محدوده است[0x1..0x1000000]'، عملوند راست در محدوده است'[0x1..0x1000000]'. image.cpp 2200

بنابراین، ما آن را داریم p_width و p_height متغیرهای بین المللی نوع حداکثر مقدار 4 بایت بین المللی can store 2'147'483'647 است.

ابتدا بررسی می شود که p_width ، جایی که MAX_WIDTH == 16'777'216. سپس بررسی می شود که p_height، کجا MAX_HEIGHT == 16777216. بررسی سوم این است که محصول از p_width * p_height .

بیایید در مورد زمانی که p_width == p_height && p_width == 16'777'216. حاصل ضرب این اعداد 281'474'976'710'656 است. برای نمایش نتیجه، یک عدد 8 بایتی مورد نیاز است، بنابراین یک علامت سرریز وجود دارد. همانطور که می دانیم، این منجر به رفتار نامشخص در C و C ++ می شود.

اگر هیچ توابع کمکی وجود نداشته باشد که سرریز را بررسی کند، ساده ترین راه حل ممکن است به شکل زیر باشد:

ERR_FAIL_COND_MSG((int64_t) p_width * (int64_t) p_height > (int64_t) MAX_PIXELS,
“Too many pixels for image, maximum is ” + itos(MAX_PIXELS));

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قطعه N14

void RemoteDebugger::debug(….)
{
….
mutex.lock();
while (is_peer_connected())
{
mutex.unlock();
….
}

send_message(“debug_exit”, Array());
if (Thread::get_caller_id() == Thread::get_main_id())
{
if (mouse_mode != Input::MOUSE_MODE_VISIBLE)
{
Input::get_singleton()->set_mouse_mode(mouse_mode);
}
}
else
{
MutexLock mutex_lock(mutex);
messages.erase(Thread::get_caller_id());
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

V1020 عملکرد بدون فراخوانی تابع 'mutex.unlock' خارج شد. بررسی خطوط: 556، 438. remote_debugger.cpp 556

این یک قطعه کد بسیار جالب با چند رشته است. تحلیلگر PVS-Studio تشخیص داده است که ممکن است یک mutex در برخی از مسیرهای اجرا باز نشود. بیایید وارد آن شویم.

ما با دیدن نوع mutex شروع می کنیم:

class RemoteDebugger : public EngineDebugger
{
….
private:
// Make handlers and send_message thread safe.
Mutex mutex;
….
};

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما کمی عمیق تر به این موضوع خواهیم پرداخت موتکس است:

template class StdMutexT>
class MutexImpl
{
friend class MutexLockMutexImplStdMutexT>>;
using StdMutexType = StdMutexT;
mutable StdMutexT mutex;
public:
_ALWAYS_INLINE_ void lock() const { mutex.lock(); }

_ALWAYS_INLINE_ void unlock() const { mutex.unlock(); }

_ALWAYS_INLINE_ bool try_lock() const { return mutex.try_lock(); }
};

// Recursive, for general use
using Mutex = MutexImplTHREADING_NAMESPACE::recursive_mutex>;

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بنابراین، ما در واقع یک mutex بازگشتی داریم، نه معمولی. این با یک بسته بندی RAII سفارشی استفاده می شود:

template class MutexT>
class MutexLock
{
friend class ConditionVariable;

std::unique_locktypename MutexT::StdMutexType> lock;

public:
_ALWAYS_INLINE_ explicit MutexLock(const MutexT &p_mutex)
: lock(p_mutex.mutex) {}
};

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توسعه دهندگان استفاده می کنند RemoteDebugger::mutex تقریباً همیشه با لفاف‌های RAII. در اینجا فقط چند قطعه وجود دارد: [1]، [2]، [3]، …..

با این حال، در یک نقطه مشکلی پیش آمد. تحلیلگر به قطعه ای اشاره کرد که در آن mutex به صورت دستی مدیریت می شود. بنابراین، ما چندین گزینه برای اجرای کد داریم:

mutex قفل شده است و حلقه یک بار اجرا نمی شود (N == 0). در نتیجه، جریان کنترلی را ترک می کند RemoteDebugger::debug عملکرد با شمارشگر ضبط 1 افزایش یافته است.
mutex قفل می شود و حلقه اجرا می شود N == 1 بار. در این مورد همه چیز خوب است: تعداد ضبط mutex بازگشتی به همان عدد افزایش و کاهش می یابد.
mutex قفل می شود و حلقه اجرا می شود N > 1 بار. در نتیجه، mutex بازگشتی تعداد ضبط آن کاهش می یابد N – 1 نسبت به زمان قبل از قفل شدن دستی، که می تواند منجر به رفتار نامشخص شود.

اگر ما تماس ها را بررسی کنیم is_peer_connected عملکرد در پایه کد ([1]، [2]، [3]، ….)، سپس می توانیم ببینیم که آنها قفل شده اند RemoteDebugger::mutex در تمام موارد ظاهراً در این مورد، برنامه نویس به قفل نیز نیاز داشت، اما آنها آن را به صورت دستی پیاده سازی کردند.

بر اساس این مفروضات، می توانیم کد را به صورت زیر اصلاح کنیم:

void RemoteDebugger::debug(….)
{
….
const auto is_peer_connected_sync = [this] {
MutexLock _ { mutex };
return is_peer_connected();
};

while (is_peer_connected_sync())
{
….
}
….
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

من نمی توانم تضمین کنم که این تعمیر کاملاً صحیح است، زیرا فقط توسعه دهندگان گودو می دانند که در اینجا چه اتفاقی باید بیفتد. اگرچه، حداقل از شر رفتار بالقوه تعریف نشده مرتبط با باز کردن قفل mutex در هر تکرار حلقه خلاص شدیم.

نتیجه

خطاهای کد از ساده به پیچیده، از قابل مشاهده تا نامرئی متفاوت است. برای اینکه لذت و تجربه محصول از بین نرود، باید دائماً از اشکالات و خطاها پاک شود. تحلیلگرهای استاتیک و دینامیک به خوبی از عهده این کار بر می آیند.

شروع با چنین راه حل هایی ساده تر از آن چیزی است که فکر می کنید. برای مثال، می‌توانید نسخه آزمایشی آنالایزر PVS-Studio را در اینجا دریافت کنید. همچنین تعدادی شرایط و ضوابط برای استفاده رایگان از آن وجود دارد.

متشکرم که این را خواندید و روز خوبی برایتان آرزومندم!

توسعه و انجام بازی‌ها می‌تواند یک فعالیت فوق‌العاده جذاب، اعتیادآور و رضایت‌بخش باشد. اما هیچ چیز مانند یک باگ مخفی، تجربه گیم پلی را خراب نمی کند. بنابراین، امروز ما یک موتور بازی منبع باز – موتور Godot را بررسی خواهیم کرد. بیایید ببینیم چقدر خوب است و آیا آماده است تا احساسات فراموش نشدنی خلق و بازی کردن را به ما بدهد.

image1

گودو

Godot یک موتور بازی دوبعدی و سه بعدی همه کاره است که برای تامین انرژی انواع پروژه ها طراحی شده است. می‌توانید از آن برای ایجاد بازی‌ها یا برنامه‌ها استفاده کنید و سپس آن‌ها را روی دسکتاپ، موبایل یا پلتفرم‌های وب منتشر کنید.

موتور هنوز جوان است، اما در حال حاضر در میان کسانی که از کد منبع باز، نرم افزار رایگان و توسعه پذیری خوب استقبال می کنند، مورد توجه قرار گرفته است. گودو کاملا مبتدی پسند است، بنابراین در بین توسعه دهندگان تازه کار محبوب است.

بازی هایی مانند 1000 Days to Escape، City Game Studio: Your Game Dev Adventure Begins، Precipice و موارد دیگر با استفاده از این موتور توسعه یافته اند.

نسخه Godot Engine که برای تجزیه و تحلیل استفاده کردیم 4.2.2 است.

به هر حال، ما قبلا موتور Godot را در سال 2018 بررسی کرده ایم. می توانید مقاله قبلی را اینجا بخوانید.

نتایج بررسی پروژه با استفاده از PVS-Studio

اولین چیزی که مایلیم بعد از بررسی گزارش با آن شروع کنیم، مسائل کلان پروژه است. پارامترها داخل پرانتز قرار نمی گیرند. بیایید به چند مثال نگاه کنیم که چگونه می تواند به پای شما شلیک کند.

قطعات N1-N2

#define HAS_WARNING(flag) (warning_flags & flag)
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما به این ماکرو نیاز داریم تا بررسی کنیم که آیا پرچم هشدار خاصی تنظیم شده است یا خیر.

این warning_flags متغیر یک بیت ماسک از uint_32t نوع این به این معنی است که مقدار آن 32 بیت است، که در آن هر بیت اگر پرچم تنظیم شده باشد 1 و اگر تنظیم نشده باشد 0 است. ماکرو در عبارات شرطی استفاده می شود، جایی که به طور ضمنی به تبدیل می شود بوول نوع برای اینکه بفهمیم چگونه کار می کند، به جای 32، یک نسخه ساده شده با 8 بیت را انتخاب می کنیم.

فرض کنید یک پرچم X داریم که مربوط به بیت چهارم در ماسک است و در حال حاضر تنظیم شده است. سپس، ارزش warning_flags متغیر در سیستم باینری به شکل زیر است:

00001000
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حالا بیایید بگوییم که ما تصمیم گرفتیم از ماکرو خود استفاده کنیم تا بررسی کنیم که آیا ایکس پرچم تنظیم شده است

عبور می کنیم پرچم متغیر با 00001000 مقدار به ماکرو، و مقدار بیتی AND به یک مقدار غیر صفر تبدیل می شود که به آن تبدیل می شود بوول با ارزش به درست است، واقعی.

حالا فرض کنید می خواهیم آن را بررسی کنیم Y پرچمی که مربوط به بیت سوم با همان مقدار است warning_flags متغیر*. متغیر را با *00000100 پاس می کنیم مقدار به ماکرو، و بیتی AND به یک مقدار صفر تبدیل می شود که به آن تبدیل می شود بوول با ارزش نادرست.

همه چیز عالی به نظر می رسد. چه چیزی ممکن است اشتباه کند؟ اما اگر کسی بخواهد بررسی کند که آیا یکی از آن پرچم ها تنظیم شده است، چه؟ آنها می توانند ماکرو را به صورت زیر فراخوانی کنند:

if (HAS_WARNING(flags::X | flags::Y)) ....
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس، نتیجه عملیات همیشه خواهد بود درست است، واقعی، حتی اگر هیچ یک از پرچم ها تنظیم نشده باشد. چرا این اتفاق می افتد؟ بیایید وانمود کنیم که یک پیش پردازنده هستیم و فقط عبارت ارسال شده به ماکرو را جایگزین کنیم:

if (warning_flags & flags::X | flags::Y) ....
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون به جدول اولویت عملگر نگاه می کنیم:

تقدم اپراتور شرح انجمنی
…. …. …. ….
11 الف و ب به صورت بیتی و چپ به راست
…. …. …. ….
13 \ بیتی OR

اپراتورها از بالا به پایین به ترتیب تقدم نزولی فهرست شده اند. این به این معنی است که عبارت به صورت زیر پردازش می شود:

if (( warning_flags & flags::X ) | flags::Y) ....
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید بگوییم که ایکس و Y پرچم هایی که ما به آنها علاقه مندیم نصب نشده اند warning_flags. سپس، اولین عملیات بیتی AND 0 را برمی گرداند Y پرچم روی آن تنظیم شده است. ما یک چک همیشه واقعی دریافت می کنیم.

بنابراین، تحلیلگر هشدار زیر را برای این ماکرو صادر می کند:

V1003 ماکرو 'HAS_WARNING' یک عبارت خطرناک است. پارامتر “پرچم” باید با پرانتز احاطه شود. shader_language.cpp 40

همانطور که پیام می گوید، برای رفع آن فقط باید پارامتر ماکرو را در پرانتز قرار دهیم:

#define HAS_WARNING(flag) (warning_flags & (flag))
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا مثال دیگری از یک ماکرو خطرناک آورده شده است:

#define IS_SAME_ROW(i, row) (i / current_columns == row)
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V1003 ماکرو 'IS_SAME_ROW' یک عبارت خطرناک است. پارامترهای 'i'، 'row' باید با پرانتز احاطه شوند. item_list.cpp 643

اگر به جای یک متغیر، یک عبارت را به ماکرو ارسال کنیم، به این صورت:

IS_SAME_ROW(current + 1, row)
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس، در نتیجه جایگزینی پیش پردازش، موارد زیر را دریافت می کنیم:

(current + 1 / current_columns == row)
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ترتیب محاسبه در اینجا آن چیزی نیست که شما انتظار دارید.

برای محافظت در برابر چنین شرایطی، به سادگی پارامترهای ماکرو را در پرانتز قرار دهید:

#define IS_SAME_ROW(i, row) ((i) / current_columns == (row))
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قطعه N3

حال بیایید شرایط زیر را بررسی کنیم:

const auto hint_r = ShaderLanguage::ShaderNode::Uniform::HINT_ROUGHNESS_R;
const auto hint_gray = ShaderLanguage::ShaderNode::Uniform::HINT_ROUGHNESS_GRAY;

if (tex->detect_roughness_callback
    && (   p_texture_uniforms[i].hint >= hint_r
        || p_texture_uniforms[i].hint  hint_gray))
{
  ....
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این شرایط همیشه هست درست است، واقعی (به جز زمانی که tex->detect_roughness_callback اشاره گر صفر است).

برای درک اینکه چرا این مورد است، باید نگاه کنیم enum اشاره در لباس فرم ساختار:

struct Uniform
{
  ....
  enum Hint 
  {
    HINT_NONE,
    HINT_RANGE,
    HINT_SOURCE_COLOR,
    HINT_NORMAL,
    HINT_ROUGHNESS_NORMAL,
    HINT_ROUGHNESS_R,
    HINT_ROUGHNESS_G,
    HINT_ROUGHNESS_B,
    HINT_ROUGHNESS_A,
    HINT_ROUGHNESS_GRAY,
    HINT_DEFAULT_BLACK,
    HINT_DEFAULT_WHITE,
    HINT_DEFAULT_TRANSPARENT,
    HINT_ANISOTROPY,
    HINT_SCREEN_TEXTURE,
    HINT_NORMAL_ROUGHNESS_TEXTURE,
    HINT_DEPTH_TEXTURE,
    HINT_MAX
  };
  ....
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

زیر سرپوش چنین enum یک نوع عدد صحیح است، و HINT_ROUGHNESS_R و HINT_ROUGHNESS_GRAY مقادیر مربوط به اعداد 5 و 9 است.

بر اساس موارد فوق، شرط آن را بررسی می کند p_texture_uniforms[i].hint >= 5 یا p_texture_uniforms[i].اشاره این بدان معنی است که هر مقدار از p_texture_uniforms[i].اشاره این چک ها را پاس می کند. این چیزی است که تحلیلگر PVS-Studio در مورد آن هشدار می دهد:

بیان V547 همیشه درست است. material_storage.cpp 929

در واقع، برنامه نویس می خواست بررسی کند که آیا p_texture_uniforms[i].اشاره در محدوده 5 تا 9 بود. برای انجام این کار، آنها باید از “AND” منطقی استفاده می کردند:

if (tex->detect_roughness_callback
    && (   p_texture_uniforms[i].hint >= hint_r
        && p_texture_uniforms[i].hint  hint_gray))
{
  ....
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا یک هشدار مشابه وجود دارد:

  • بیان V547 همیشه درست است. material_storage.cpp 1003

قطعه N4

سعی کنید خطا را در اینجا پیدا کنید:

Error FontFile::load_bitmap_font(const String &p_path)
{
  if (kpk.x >= 0x80 && kpk.x  0xFF)
  {
    kpk.x = _oem_to_unicode[encoding][kpk.x - 0x80];
  } else if (kpk.x > 0xFF){
    WARN_PRINT(vformat("Invalid BMFont OEM character %x
                        (should be 0x00-0xFF).", kpk.x));
    kpk.x = 0x00;
  }

  if (kpk.y >= 0x80 && kpk.y  0xFF) 
  {
    kpk.y = _oem_to_unicode[encoding][kpk.y - 0x80];
  } else if (kpk.y > 0xFF){
    WARN_PRINT(vformat("Invalid BMFont OEM character %x
                        (should be 0x00-0xFF).", kpk.x));
    kpk.y = 0x00;
  }
  ....
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

image2

هشدار آنالیزور:

V778 دو قطعه کد مشابه پیدا شد. شاید این اشتباه تایپی باشد و باید به جای x از متغیر 'y' استفاده شود. font.cpp 1970

بنابراین، PVS-Studio خطایی را پیدا کرده است که هنگام کپی کردن یک قطعه کد رخ داده است. بیایید نگاهی دقیق تر به بلوک های شرطی بیندازیم. آنها یکسان هستند با این تفاوت که در حالت اول همه عملیات بر روی آنها انجام می شود kpk.x، در حالی که در حالت دوم انجام می شوند kpk.y.

با این حال، شرط دوم به دلیل یک عملیات کپی پیست با خطا مواجه شد. نگاهی به WARN_PRINT تماس بگیرید: اگر kpk.y > 0xFF، kpk.x زمانی که اخطار صادر می شود، کاراکتر نمایش داده می شود، نه kpk.y. جستجوی خطا بر اساس گزارش‌ها دشوارتر است 🙂

PS: کد واقعاً نباید اینطور ضرب می شد. به وضوح می توانید ببینید که دو بلوک کد فقط در قسمت اعمال شده با هم تفاوت دارند. قرار دادن کد در یک تابع و دو بار فراخوانی آن برای فیلدهای مختلف گزینه بهتری خواهد بود:

Error FontFile::load_bitmap_font(const String &p_path)
{
  constexpr auto check = [](auto &ch)
  {
    if (ch >= 0x80 && ch  0xFF)
    {
      auto res = _oem_to_unicode[encoding][ch - 0x80];
      ch = res;
    }
    else if (ch > 0xFF)
    {
      WARN_PRINT(vformat("Invalid BMFont OEM character %x
                              (should be 0x00-0xFF).",ch));
      ch = 0x00;
    }
  };

  check(kpk.x);
  check(kpk.y);
  ....
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قطعه N5

در اینجا شرایط بیشتری وجود دارد، اما آنها تودرتو هستند:

void GridMapEditor::_mesh_library_palette_input(const RefInputEvent> &p_ie) 
{
  const RefInputEventMouseButton> mb = p_ie;
  // Zoom in/out using Ctrl + mouse wheel
  if (mb.is_valid() && mb->is_pressed() && mb->is_command_or_control_pressed()) 
  {
    if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) 
    {
      size_slider->set_value(size_slider->get_value() + 0.2);
    }
    ....
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V571 بررسی مکرر. شرط 'mb->is_pressed()' قبلاً در خط 837 تأیید شده است. grid_map_editor_plugin.cpp 838

این قطعه شامل یک بررسی اضافی در تودرتو است اگر بیانیه. این mb->is_pressed() عبارت قبلاً در بالا بررسی شده است. شاید این یک بررسی دوگانه باشد (در رابط کاربری گرافیکی رایج است)، اما پس از آن توسعه دهندگان باید یک نظر در مورد آن اضافه می کردند. یا شاید چیز دیگری باید بررسی می شد.

در اینجا هشدارهای مشابه وجود دارد:

  • V571 بررسی مکرر. شرط «!r_state.floor» قبلاً در خط 1711 تأیید شده بود. physics_body_3d.cpp 1713
  • V571 بررسی مکرر. شرط «!wd_window.is_popup» قبلاً در خط 2012 تأیید شده بود. display_server_x11.cpp 2013
  • V571 بررسی مکرر. شرط “member.variable->initializer” قبلاً در خط 946 تأیید شده است. gdscript_analyzer.cpp 949

قطعه N6

و این یک مورد کلاسیک عدم ارجاع اشاره گر قبل از بررسی آن است:

void GridMapEditor::_update_cursor_transform()
{
  cursor_transform = Transform3D();
  cursor_transform.origin = cursor_origin;
  cursor_transform.basis = node->get_basis_with_orthogonal_index(cursor_rot);
  cursor_transform.basis *= node->get_cell_scale();
  cursor_transform = node->get_global_transform() * cursor_transform;

  if (selected_palette >= 0)
  {
    if (node && !node->get_mesh_library().is_null())
    {
      cursor_transform *= node->get_mesh_library()
                              ->get_item_mesh_transform(selected_palette);
    }
  }
  ....
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V595 نشانگر 'node' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 246، 251. grid_map_editor_plugin.cpp 246

نسبتاً عجیب است که یک اشاره گر را تغییر دهید و سپس آن را چند خط پایین بررسی کنید. شاید عدم ارجاع در کد دیرتر از بررسی ظاهر شد و توسعه دهنده متوجه بررسی زیر نشد.

هشدارهای مشابه:

  • V595 نشانگر 'p_ternary_op->true_expr' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 4518، 4525. gdscript_analyzer.cpp 4518
  • V595 نشانگر 'p_parent' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 4100، 4104. node_3d_editor_plugin.cpp 4100
  • V595 نشانگر «اقلام» قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 950، 951. project_export.cpp 950
  • V595 نشانگر 'title_bar' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 1153، 1163. editor_node.cpp 1153
  • V595 نشانگر 'render_target' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 2121، 2132. rastizer_canvas_gles3.cpp 2121
  • V595 نشانگر '_p' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 228، 231. dictionary.cpp 228
  • V595 نشانگر 'class_doc' قبل از تأیید در برابر nullptr استفاده شد. بررسی خطوط: 1215، 1231. extension_api_dump.cpp 1215

قطعه N7

template class T, class U = uint32_t,
          bool force_trivial = false, bool tight = false>
class LocalVector
{
  ....
public:
  operator VectorT>() const
  {
    VectorT> ret;
    ret.resize(size());
    T *w = ret.ptrw();
    memcpy(w, data, sizeof(T) * count);
    return ret;
  }
  ....
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V780 Instantiation of LocalVector: شیء 'w' از نوع غیر منفعل (غیر PDS) را نمی توان با استفاده از تابع memcpy کپی کرد. local_vector.h 280

این یک قطعه جالب است. این LocalVector قالب کلاس دارای یک عملگر تبدیل به بردار کلاس در این تبدیل، محتویات بردار فعلی باید در بردار جدید کپی شود. برای انجام این کار، توسعه دهندگان از memcpy تابع.

این همه خوب است تا زمانی که تی نوع قالب به طور ساده قابل کپی است. با این حال، تحلیلگر تخصص های مختلفی از آن را شناسایی کرده است LocalVector، که این خاصیت را نقض کرده است. به عنوان مثال، اجازه دهید نگاهی به LocalVector تخصص:

struct AnimationCompressionDataState
{
  uint32_t components = 3;
  LocalVectoruint8_t> data; // Committed packets.
  struct PacketData
  {
    int32_t data[3] = { 0, 0, 0 };
    uint32_t frame = 0;
  };

  float split_tolerance = 1.5;

  LocalVectorPacketData> temp_packets;

  // used for rollback if the new frame does not fit
  int32_t validated_packet_count = -1;
  ....
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این AnimationCompressionDataState کلاس* حاوی الف LocalVector* که قابل کپی کردن نیست.

image3

برای این مورد، یک یادداشت در وجود دارد memcpy مستندات: “اگر اشیاء به طور بالقوه همپوشانی دارند یا قابل کپی نیستند، رفتار memcpy مشخص نیست و ممکن است تعریف نشده باشد”.

ما به راحتی می توانیم کد را با جایگزین کردن آن برطرف کنیم memcpy تماس با std::uniitialized_copy:

operator VectorT>() const
{
  VectorT> ret;
  ret.resize(size());
  T *w = ret.ptrw();
  std::uninitialized_copy(data, data + count, w);
  return ret;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

آنالایزر PVS-Studio 38 تخصص خطرناک تر را شناسایی کرده است، اما البته لیست کامل را ارائه نمی دهم:

  • V780 Instantiation of LocalVector: شیء 'w' از نوع غیر منفعل (غیر PDS) را نمی توان با استفاده از تابع memcpy کپی کرد. local_vector.h 280
  • V780 Instantiation of LocalVector: شیء 'w' از نوع غیر منفعل (غیر PDS) را نمی توان با استفاده از تابع memcpy کپی کرد. local_vector.h 280
  • V780 Instantiation of LocalVector: شیء 'w' از نوع غیر منفعل (غیر PDS) را نمی توان با استفاده از تابع memcpy کپی کرد. local_vector.h 280
  • V780 Instantiation of LocalVector >: شیء 'w' از نوع غیر منفعل (غیر PDS) را نمی توان با استفاده از تابع memcpy کپی کرد. local_vector.h 280
  • V780 Instantiation of LocalVector , uint32_t, bool, bool >: شیء 'w' از نوع غیر منفعل (غیر PDS) را نمی توان با استفاده از تابع memcpy کپی کرد. local_vector.h 280

قطعه N8

در اینجا یک نقض احتمالی منطق برنامه وجود دارد:

Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl
                                                             (int p_line)
{
  const String &str = text_edit->get_line(p_line);
  ....
  if (   is_digit(str[non_op])
      || (   str[non_op] == '.' 
          && non_op  line_length 
          && is_digit(str[non_op + 1]) ) )
  {
    in_number = true;
  }
  ....
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V781 مقدار شاخص 'non_op' پس از استفاده بررسی می شود. شاید اشتباهی در منطق برنامه وجود داشته باشد. gdscript_highlighter.cpp 370

این non_op مقدار ابتدا به عنوان شاخص هنگام دسترسی به کاراکترهای رشته استفاده می شود. فقط پس از آن مقدار بررسی می شود که کمتر از طول باشد.

به دسترسی به رشته بعد از بررسی نگاهی بیندازید. اگر non_op، به این معنی نیست (non_op + 1) . بنابراین، یک شاخص خارج از محدوده ممکن است در داخل اتفاق بیفتد خ[non_op + 1]. به خصوص از آنجایی که هیچ رشته تهی در زیر وجود ندارد رشته کاپوت ماشین.

یک بررسی صحیح باید به شکل زیر باشد:

if (   is_digit(str[non_op])
    || (   str[non_op] == '.' 
        && non_op + 1  line_length 
        && is_digit(str[non_op + 1]) ) )
{
  in_number = true;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قطعه N9

struct Particles
{
  ....
  int amount = 0;
  ....
};

void ParticlesStorage::_particles_update_instance_buffer(
  Particles *particles,
  const Vector3 &p_axis,
  const Vector3 &p_up_axis)
{
  ....
  uint32_t lifetime_split = ....;
  // Offset VBO so you render starting at the newest particle.
  if (particles->amount - lifetime_split > 0)
  {
    ....
  }
  ....
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V555 عبارت 'particles->amount – lifetime_split > 0' به صورت 'particles->amount != lifetime_split' کار خواهد کرد. particles_storage.cpp 959

مثال جالب است زیرا با وجود هشدار نه چندان صحیحی که توسط آنالیزور صادر شده است، نشان می دهد که توسعه دهندگان باید به چه نکاتی توجه کنند.

اگر تفاوت بین دو متغیر بدون علامت بزرگتر از صفر باشد، این عبارت از نظر معنایی برابر است با particles->amount != lifetime_split. شرط در نظر گرفته شده است نادرست فقط در صورتی که متغیرها برابر باشند. اگر عملوند سمت چپ کمتر از سمت راست باشد، یک wrap-around رخ می دهد و عبارت حاصل بزرگتر از صفر خواهد بود. اگر عملوند چپ بزرگتر از سمت راست باشد، این تفاوت بزرگتر از صفر خواهد بود.

با این حال، نکته دیگری که در اینجا باید به آن توجه کرد این است که هر دو متغیر دارای رتبه یکسان، اما علامت متفاوت هستند. استاندارد یک کامپایلر را ملزم می کند تا قبل از انجام تفریق، تبدیل های ضمنی را انجام دهد. در این مورد، 32 بیتی بدون علامت بین المللی نوع رایج آن است. اگر عملوند سمت چپ دارای یک عدد منفی باشد، این می تواند چند شگفتی اضافه کند.

صحیح ترین بررسی زمانی که عبارات نوع امضا شده و بدون علامت هم رتبه درگیر هستند به صورت زیر است:

if (particles->amount >= 0 && particles->amount > lifetime_split)
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در واقع، ما دوباره اختراع کرده ایم std::cmp_greaterکه در C++20 معرفی شد. با شروع از این نسخه استاندارد، می توانیم کد مختصر بنویسیم:

if (std::cmp_greater(particles->amount, lifetime_split))
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قطعه N10

void AnimationNodeStateMachineEditor::_delete_tree_draw()
{
  TreeItem *item = delete_tree->get_next_selected(nullptr);
  while (item) 
  {
    delete_window->get_cancel_button()->set_disabled(false);
    return;
  }
  delete_window->get_cancel_button()->set_disabled(true);
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

شرایط شکست حلقه V1044 به تعداد تکرارها بستگی ندارد. animation_state_machine_editor.cpp 693

این در حالی که حلقه دقیقا یک تکرار طول می کشد. این بسیار شبیه الگویی است که در آن فقط باید اولین عنصر را از ظرف برداریم. ما می توانیم این کار را با استفاده از برای حلقه:

for (auto &&item : items)
{
  DoSomething(item);
  break;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بنابراین، لازم نیست بررسی کنیم که آیا ظرف اولین عنصر را دارد یا خیر. IMHO، کد نسبتاً گیج کننده است زیرا انتظار یک ناشناخته را دارید محدود، فانی تعداد تکرار از حلقه ها

با این حال، در قطعه بالا، در حالی که حلقه مطلقاً معنی ندارد. ساده اگر بیانیه کافی بود:

void AnimationNodeStateMachineEditor::_delete_tree_draw()
{
  TreeItem *item = delete_tree->get_next_selected(nullptr);
  if (item)
  { 
    delete_window->get_cancel_button()->set_disabled(false);
    return;
  }

  delete_window->get_cancel_button()->set_disabled(true);
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قطعه N11

static const char *script_list[][2] = {
  ....
  { "Myanmar / Burmese", "Mymr" },
  { "​Nag Mundari", "Nagm" },
  { "Nandinagari", "Nand" },
  ....
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

خواننده ممکن است بپرسد “اینجا چه اشکالی دارد؟” اگر قانون عیب یابی V1076 نبود، خودمان آن را نمی فهمیدیم. جالب است که این اولین هشداری است که ما نوشته ایم. قانون تشخیصی کد برنامه را برای کاراکترهای نامرئی بررسی می کند. چنین کاراکترهایی مانند درهای پشتی هستند که ممکن است برنامه نویس به دلیل تنظیمات نمایش متن در محیط توسعه نبیند، اما کامپایلر آنها را می بیند و به درستی پردازش می کند.

هشدار آنالیزور:

کد V1076 حاوی کاراکترهای نامرئی است که ممکن است منطق آن را تغییر دهد. فعال کردن نمایش کاراکترهای نامرئی در ویرایشگر کد را در نظر بگیرید. locales.h 1114

بیایید نگاهی دقیق تر به خط بعدی بیندازیم:

{ "​Nag Mundari", "Nagm" },
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این شامل درهای پشتی با یک شخصیت نامرئی است. اگر از ویرایشگر هگز استفاده کنیم، می توانیم موارد زیر را مشاهده کنیم:

image4

3 بایت بین دو علامت کوتیشن و علامت ساندویچ وجود دارد ن شخصیت: E2، 80، و 8B. مطابقت دارند فضای صفر عرض (U+200B) نویسه یونیکد.

رشته ها از script_list آرایه ای که حاوی رشته “آلوده” است به معنای واقعی کلمه وارد می شود TranslationServer::script_map جدول هش کلید جدول هش رشته دوم جفت است و مقدار آن اولین است. بنابراین، رشته درپشتی تحت اللفظی به عنوان مقدار وارد جدول هش می شود و جستجوی هش شکسته نمی شود.

در مرحله بعد، می‌توانیم ببینیم که ارزش جدول هش به طور بالقوه می‌تواند به کجا برسد. من چند جا پیدا کردم:

  1. مقدار می تواند به رشته ای که توسط the برگردانده شده است برود TranslationServer::get_locale_name تابع. با تجزیه و تحلیل توابع تماس گیرنده، می بینیم که این رشته وارد رابط کاربری گرافیکی می شود ([1]، [2]، [3]، [4]) به هر طریقی.
  2. مقدار از TranslationServer::get_script_name تابع. با تجزیه و تحلیل توابع تماس گیرنده، همچنین می توانیم ببینیم که رشته وارد رابط کاربری گرافیکی می شود ([1]، [2]).

به احتمال زیاد، درب پشتی به طور تصادفی با کپی کردن نام از برخی وب سایت ها اضافه شده است. ما به سادگی می توانیم این کاراکتر را از رشته literal حذف کنیم.

قطعه N12

void MeshStorage::update_mesh_instances() 
{
  ....
  uint64_t mask = RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_NORMAL 
                | RS::ARRAY_FORMAT_VERTEX;
  ....
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

  • V501 عبارت‌های فرعی یکسان «RenderingServer::ARRAY_FORMAT_VERTEX» در سمت چپ و سمت راست «|» وجود دارد. اپراتور. mesh_storage.cpp 1414
  • V578 یک عملیات بیتی عجیب و غریب شناسایی شد. بررسی آن را در نظر بگیرید. mesh_storage.cpp 1414

این یک بیت ماسک اولیه عجیب و غریب است. RS::ARRAY_FORMAT_VERTEX دو بار در آنجا نوشته شده است، اگرچه توسعه دهندگان ممکن است بخواهند پرچم دیگری بنویسند.

در اینجا یک هشدار مشابه وجود دارد:

  • V501 عبارت‌های فرعی یکسان «RenderingServer::ARRAY_FORMAT_VERTEX» در سمت چپ و سمت راست «|» وجود دارد. اپراتور. mesh_storage.cpp 1300
  • V578 یک عملیات بیتی عجیب و غریب شناسایی شد. بررسی آن را در نظر بگیرید. mesh_storage.cpp 1300

قطعه N13

void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps,
                            Format p_format, const Vectoruint8_t> &p_data)
{
  ....
  ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "The Image width specified (" + 
                                         itos(p_width) +
                                         " pixels) cannot be greater than " +
                                         itos(MAX_WIDTH) +
                                         " pixels.");

  ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "The Image height specified (" +
                                           itos(p_height) +
                                           " pixels) cannot be greater than " +
                                           itos(MAX_HEIGHT) +
                                           " pixels.");

  ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS,
                   "Too many pixels for image, maximum is " + itos(MAX_PIXELS));
  ....
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هشدار آنالیزور:

V1083 سرریز عدد صحیح امضا شده در عبارت حسابی 'p_width * p_height' امکان پذیر است. این منجر به رفتار نامشخص می شود. عملوند چپ در محدوده است[0x1..0x1000000]'، عملوند راست در محدوده است'[0x1..0x1000000]'. image.cpp 2200

بنابراین، ما آن را داریم p_width و p_height متغیرهای بین المللی نوع حداکثر مقدار 4 بایت بین المللی can store 2'147'483'647 است.

ابتدا بررسی می شود که p_width ، جایی که MAX_WIDTH == 16'777'216. سپس بررسی می شود که p_height، کجا MAX_HEIGHT == 16777216. بررسی سوم این است که محصول از p_width * p_height .

بیایید در مورد زمانی که p_width == p_height && p_width == 16'777'216. حاصل ضرب این اعداد 281'474'976'710'656 است. برای نمایش نتیجه، یک عدد 8 بایتی مورد نیاز است، بنابراین یک علامت سرریز وجود دارد. همانطور که می دانیم، این منجر به رفتار نامشخص در C و C ++ می شود.

اگر هیچ توابع کمکی وجود نداشته باشد که سرریز را بررسی کند، ساده ترین راه حل ممکن است به شکل زیر باشد:

ERR_FAIL_COND_MSG((int64_t) p_width * (int64_t) p_height > (int64_t) MAX_PIXELS,
                  "Too many pixels for image, maximum is " + itos(MAX_PIXELS));
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قطعه N14

void RemoteDebugger::debug(....)
{
  ....
  mutex.lock();
  while (is_peer_connected())
  {
    mutex.unlock();
    ....
  }

  send_message("debug_exit", Array());
  if (Thread::get_caller_id() == Thread::get_main_id())
  {
    if (mouse_mode != Input::MOUSE_MODE_VISIBLE)
    {
      Input::get_singleton()->set_mouse_mode(mouse_mode);
    }
  } 
  else 
  {
    MutexLock mutex_lock(mutex);
    messages.erase(Thread::get_caller_id());
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

V1020 عملکرد بدون فراخوانی تابع 'mutex.unlock' خارج شد. بررسی خطوط: 556، 438. remote_debugger.cpp 556

این یک قطعه کد بسیار جالب با چند رشته است. تحلیلگر PVS-Studio تشخیص داده است که ممکن است یک mutex در برخی از مسیرهای اجرا باز نشود. بیایید وارد آن شویم.

ما با دیدن نوع mutex شروع می کنیم:

class RemoteDebugger : public EngineDebugger
{
  ....
private:
  // Make handlers and send_message thread safe.
  Mutex mutex;
  ....
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما کمی عمیق تر به این موضوع خواهیم پرداخت موتکس است:

template class StdMutexT>
class MutexImpl
{
  friend class MutexLockMutexImplStdMutexT>>;
  using StdMutexType = StdMutexT; 
  mutable StdMutexT mutex;
public:
  _ALWAYS_INLINE_ void lock() const { mutex.lock(); }

  _ALWAYS_INLINE_ void unlock() const { mutex.unlock(); }

  _ALWAYS_INLINE_ bool try_lock() const { return mutex.try_lock(); }
};

// Recursive, for general use
using Mutex = MutexImplTHREADING_NAMESPACE::recursive_mutex>;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بنابراین، ما در واقع یک mutex بازگشتی داریم، نه معمولی. این با یک بسته بندی RAII سفارشی استفاده می شود:

template class MutexT>
class MutexLock
{
  friend class ConditionVariable;

  std::unique_locktypename MutexT::StdMutexType> lock;

public:
  _ALWAYS_INLINE_ explicit MutexLock(const MutexT &p_mutex)
    : lock(p_mutex.mutex) {}
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توسعه دهندگان استفاده می کنند RemoteDebugger::mutex تقریباً همیشه با لفاف‌های RAII. در اینجا فقط چند قطعه وجود دارد: [1]، [2]، [3]، …..

با این حال، در یک نقطه مشکلی پیش آمد. تحلیلگر به قطعه ای اشاره کرد که در آن mutex به صورت دستی مدیریت می شود. بنابراین، ما چندین گزینه برای اجرای کد داریم:

  1. mutex قفل شده است و حلقه یک بار اجرا نمی شود (N == 0). در نتیجه، جریان کنترلی را ترک می کند RemoteDebugger::debug عملکرد با شمارشگر ضبط 1 افزایش یافته است.
  2. mutex قفل می شود و حلقه اجرا می شود N == 1 بار. در این مورد همه چیز خوب است: تعداد ضبط mutex بازگشتی به همان عدد افزایش و کاهش می یابد.
  3. mutex قفل می شود و حلقه اجرا می شود N > 1 بار. در نتیجه، mutex بازگشتی تعداد ضبط آن کاهش می یابد N – 1 نسبت به زمان قبل از قفل شدن دستی، که می تواند منجر به رفتار نامشخص شود.

اگر ما تماس ها را بررسی کنیم is_peer_connected عملکرد در پایه کد ([1]، [2]، [3]، ….)، سپس می توانیم ببینیم که آنها قفل شده اند RemoteDebugger::mutex در تمام موارد ظاهراً در این مورد، برنامه نویس به قفل نیز نیاز داشت، اما آنها آن را به صورت دستی پیاده سازی کردند.

بر اساس این مفروضات، می توانیم کد را به صورت زیر اصلاح کنیم:

void RemoteDebugger::debug(....)
{
  ....
  const auto is_peer_connected_sync = [this]
  {
    MutexLock _ { mutex };
    return is_peer_connected();
  };

  while (is_peer_connected_sync())
  {
    ....
  }
  ....
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

من نمی توانم تضمین کنم که این تعمیر کاملاً صحیح است، زیرا فقط توسعه دهندگان گودو می دانند که در اینجا چه اتفاقی باید بیفتد. اگرچه، حداقل از شر رفتار بالقوه تعریف نشده مرتبط با باز کردن قفل mutex در هر تکرار حلقه خلاص شدیم.

نتیجه

خطاهای کد از ساده به پیچیده، از قابل مشاهده تا نامرئی متفاوت است. برای اینکه لذت و تجربه محصول از بین نرود، باید دائماً از اشکالات و خطاها پاک شود. تحلیلگرهای استاتیک و دینامیک به خوبی از عهده این کار بر می آیند.

شروع با چنین راه حل هایی ساده تر از آن چیزی است که فکر می کنید. برای مثال، می‌توانید نسخه آزمایشی آنالایزر PVS-Studio را در اینجا دریافت کنید. همچنین تعدادی شرایط و ضوابط برای استفاده رایگان از آن وجود دارد.

متشکرم که این را خواندید و روز خوبی برایتان آرزومندم!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا