استكشاف جودة التعليمات البرمجية لنظام التشغيل Zephyr

PVS-Studio و Zephyr

قلنا مؤخرًا أن محلل كود PVS-Studio بدأ في الاندماج مع PlatformIO. بطبيعة الحال ، تواصل فريق تطوير PVS-Studio مع فريق PlatformIO واقترحوا ، من أجل الاهتمام ، التحقق من رمز نظام التشغيل Zephyr في الوقت الفعلي. اعتقدنا لماذا لا ، وهنا مقال حول مثل هذه الدراسة.

PlatformIO


قبل البدء في الجزء الرئيسي من المقالة ، أود أن أوصي بمطوري PlatformIO لمطوري الأنظمة المدمجة ، مما يجعل حياتهم أسهل. إنها أداة برمجة عبر منصة متحكم. جوهر PlatformIO هو أداة سطر أوامر ، ولكن يوصى باستخدامه كمكون إضافي لبرنامج Visual Studio Code. يتم دعم عدد كبير من الرقائق واللوحات الأم الحديثة المعتمدة عليها. إنه قادر على تنزيل أنظمة التجميع المناسبة تلقائيًا ، ويحتوي الموقع على مجموعة كبيرة من المكتبات لإدارة المكونات الإلكترونية الإضافية.

PVS-Studio


لا يزال PVS-Studio معروفًا قليلًا في عالم الأنظمة المدمجة ، لذلك في حالة حدوث ذلك فقط ، سأقدم مقدمة للقراء الجدد الذين لم يكونوا على دراية بهذه الأداة بعد. يمكن لقرائنا العاديين التخطي مباشرة إلى القسم التالي.

PVS-Studio هو محلل رمز ثابت يسمح لك بتحديد الأخطاء ونقاط الضعف المحتملة في رمز البرامج المكتوبة بلغة C و C ++ و C # و Java. إذا تحدثنا فقط عن C و C ++ ، فسيتم دعم المترجمات التالية:

  • شبابيك Visual Studio 2010-2019 C و C ++ و C ++ / CLI و C ++ / CX (WinRT)
  • شبابيك منضدة IAR المدمجة ، مترجم C / C ++ لـ ARM C ، C ++
  • شبابيك QNX Momentics ، QCC C ، C ++
  • ويندوز / لينكس Keil µVision، DS-MDK، ARM Compiler 5/6 C، C ++
  • ويندوز / لينكس ستوديو Texas Instruments Code Composer ، أدوات إنشاء رمز ARM C ، C ++
  • Windows/Linux/macOS. GNU Arm Embedded Toolchain, Arm Embedded GCC compiler, C, C++
  • Windows/Linux/macOS. Clang C, C++
  • Linux/macOS. GCC C, C++
  • Windows. MinGW C, C++

يحتوي المحلل على نظام تصنيف التحذير الخاص به ، ولكن إذا لزم الأمر ، يمكنك تمكين عرض التنبيهات وفقًا لمعايير التشفير CWE و SEI CERT و MISRA .

يمكنك البدء بسرعة باستخدام PVS-Studio بانتظام حتى في مشروع قديم كبير. يتم توفير آلية خاصة لقمع التحذيرات الجماعية لهذا الغرض . تعتبر جميع التحذيرات الحالية دينًا فنيًا ويتم إخفاؤها ، مما يسمح لك بالتركيز على التحذيرات التي تنطبق فقط على التعليمات البرمجية الجديدة أو المعدلة. يسمح ذلك للفريق بالبدء فورًا في استخدام المحلل يوميًا في عملهم ، ويمكنك العودة إلى الواجب الفني من وقت لآخر وتحسين الشفرة.

هناك العديد من السيناريوهات الأخرى لاستخدام PVS-Studio. على سبيل المثال ، يمكنك استخدامه كمكوِّن إضافي لـ SonarQube. يمكن التكامل مع أنظمة مثل Travis CI و CircleCI و GitLab CI / CD وما إلى ذلك. وصف أكثر تفصيلاً لـ PVS-Studio يتجاوز نطاق هذه المقالة. لذلك ، أقترح أن تتعرف على المقالة ، التي تحتوي على العديد من الروابط المفيدة ، والتي تجيب على العديد من الأسئلة: " أسباب إدخال محلل الشفرة الثابتة PVS-Studio في عملية التطوير ."

زفير


من خلال العمل على دمج PVS-Studio في PlatformIO ، تحدثت فرقنا وطلب منهم التحقق من مشروع من العالم المضمن ، وهو Zephyr. لقد أحببنا الفكرة ، والتي كانت سبب كتابة هذا المقال.

Zephyr هو نظام تشغيل خفيف الوزن في الوقت الفعلي مصمم للعمل على الأجهزة ذات الموارد المحدودة لمختلف التصميمات. يتم توزيع الكود تحت رخصة أباتشي 2.0 مفتوحة المصدر. يعمل على الأنظمة الأساسية التالية: ARM (Cortex-M0 ، Cortex-M3 ، Cortex-M4 ، Cortex-M23 ، Cortex-M33 ، Cortex-R4 ، Cortex-R5 ، Cortex-A53) ، x86 ، x86-64 ، ARC ، RISC- V ، Nios II ، Xtensa.

بعض الملامح:

  • مساحة عنوان موحدة. ينشئ رمز التطبيق المحدد مع النواة المخصصة صورة متجانسة يتم تنفيذها على الجهاز.
  • . , .
  • . .
  • . . .
  • : , , , , .

من اللحظات المثيرة للاهتمام بالنسبة لنا ، تشارك Synopsys في تطوير نظام التشغيل . في عام 2014 ، استحوذت سينوبسيس على Coverity ، التي أنتجت محلل الشفرة الثابتة الذي يحمل نفس الاسم.

من الطبيعي أن يستخدم مطور Zephyr منذ البداية محلل Coverity . المحلل هو رائد في السوق وهذا لا يمكن إلا أن يؤثر على جودة رمز نظام التشغيل للأفضل.

جودة كود زفير


في رأيي ، رمز نظام التشغيل Zephyr هو الجودة. إليك ما يعطيني سببًا للتفكير بذلك:

  • PVS-Studio 122 High 367 Medium. , , 560 C/C++ . . 7810 C/C++ 10075 . , . , , .
  • . «» , .
  • أعطت أداة SourceMonitor المساعدة ، بعد تحليل شفرة المصدر ، إحصائيات تفيد بأن 48 ٪ من الشفرة هي تعليقات. هذا كثير ويشير في تجربتي إلى وجود اهتمام كبير بجودة الشفرة وإمكانية فهمها للمطورين الآخرين.
  • عند تطوير مشروع ، يتم استخدام محلل كود ثابت للغلاف. على الأرجح ، بسبب هذه الحقيقة ، لم يتمكن محلل PVS-Studio ، على الرغم من أنه وجد أخطاء في المشروع ، من إظهار نفسه بشكل واضح ، كما يحدث أحيانًا عند تحليل مشاريع أخرى.

بناءً على ذلك ، أعتقد أن مؤلفي المشروع يهتمون بجودة وموثوقية الشفرة. دعونا الآن نلقي نظرة على بعض التحذيرات الصادرة عن محلل PVS-Studio (الإصدار 7.06).

تحذيرات شبه كاذبة


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

مثال على التفعيل "شبه الخاطئ" N1

static struct char_framebuffer char_fb;

int cfb_framebuffer_invert(struct device *dev)
{
  struct char_framebuffer *fb = &char_fb;

  if (!fb || !fb->buf) {
    return -1;
  }

  fb->inverted = !fb->inverted;

  return 0;
}

تحذير PVS-Studio: V560 دائمًا ما يكون جزء من التعبير الشرطي خطأ :! Fb . cfb.c 188

عند أخذ عنوان متغير ثابت ، يتم دائمًا الحصول على مؤشر غير صفري. لذلك ، يكون المؤشر fb دائمًا غير صفري ولا يكون التحقق منه منطقيًا.

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

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

مثال على تشغيل "شبه مقطوع" لـ N2

int hex2char(u8_t x, char *c)
{
  if (x <= 9) {
    *c = x + '0';
  } else if (x >= 10 && x <= 15) {
    *c = x - 10 + 'a';
  } else {
    return -EINVAL;
  }
  return 0;
}

تحذير PVS-Studio: V560 دائمًا ما يكون جزء من التعبير الشرطي صحيحًا: x> = 10. hex.c 31

يكون المحلل صحيحًا مرة أخرى رسميًا في تأكيد أن هذا الجزء من الحالة صحيح دائمًا. إذا كان المتغير x ليس أقل من / يساوي 9 ، فعندئذٍ يتبين أنه دائمًا أكبر من / يساوي 10. ويمكن تبسيط الكود:

} else if (x <= 15) {

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

الآن لنلقي نظرة على مثال أكثر تعقيدًا لـ N3.

أولاً ، دعنا نرى كيف يمكن تنفيذ ماكرو CHECKIF .

#if defined(CONFIG_ASSERT_ON_ERRORS)
#define CHECKIF(expr) \
  __ASSERT_NO_MSG(!(expr));   \
  if (0)
#elif defined(CONFIG_NO_RUNTIME_CHECKS)
#define CHECKIF(...) \
  if (0)
#else
#define CHECKIF(expr) \
  if (expr)
#endif

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

#define CHECKIF(expr) \
  if (expr)

الآن دعونا نرى ما يؤدي هذا.

int k_queue_append_list(struct k_queue *queue, void *head, void *tail)
{
  CHECKIF(head == NULL || tail == NULL) {
    return -EINVAL;
  }

  k_spinlock_key_t key = k_spin_lock(&queue->lock);
  struct k_thread *thread = NULL;
  if (head != NULL) {
    thread = z_unpend_first_thread(&queue->wait_q);
  }
  ....
}

تحذير PVS-Studio: V547 [CWE-571] التعبير "رأس! = NULL" صحيح دائمًا. queue.c 244

يرى المحلل أن التحقق (head! = NULL) يعطي دائمًا صواب. وهو كذلك بالفعل. إذا كان مؤشر الرأس فارغًا ، فستتوقف الوظيفة عن العمل بسبب فحص في بداية الوظيفة:

CHECKIF(head == NULL || tail == NULL) {
  return -EINVAL;
}

تذكر أنه هنا يتم توسيع الماكرو على النحو التالي:

if (head == NULL || tail == NULL) {
  return -EINVAL;
}

لذا ، فإن محلل PVS-Studio هو من وجهة نظره ويصدر تحذيرًا صحيحًا. ومع ذلك ، لا يمكن حذف هذا الاختيار. هي مطلوبة. في سيناريو آخر ، سيتم فتح الماكرو على النحو التالي:

if (0) {
  return -EINVAL;
}

ثم هناك حاجة إلى إعادة التحقق من المؤشر. بالطبع ، لن يقوم المحلل بإنشاء تحذير في هذا الإصدار من تجميع التعليمات البرمجية. ومع ذلك ، فإنه يعطي تحذيرًا لإصدار التصحيح من الترجمة.

آمل أن يكون من الواضح الآن للقراء من أين تأتي التحذيرات "شبه الكاذبة". ومع ذلك ، فلا حرج عليهم. يوفر محلل PVS-Studio آليات مختلفة لقمع التنبيهات الكاذبة ، والتي يمكن العثور عليها في الوثائق.

تحذيرات الحالة


ولكن هل وجدت شيئًا مثيرًا للاهتمام على كل حال؟ لقد كان ناجحًا ، والآن سنلقي نظرة على أخطاء مختلفة. في الوقت نفسه ، أود أن أشير على الفور إلى نقطتين:

  1. . : , , , Coverity. , PVS-Studio - , .
  2. . , «» . , . GitHub, PVS-Studio.

N1,

static void gen_prov_ack(struct prov_rx *rx, struct net_buf_simple *buf)
{
  ....
  if (link.tx.cb && link.tx.cb) {
    link.tx.cb(0, link.tx.cb_data);
  }
  ....
}

تحذير PVS-Studio: V501 [CWE-571] هناك تعبيرات فرعية متطابقة إلى اليسار وإلى يمين عامل التشغيل "&&": link.tx.cb && link.tx.cb pb_adv.c 377

تم التحقق من أحدهما مرتين نفس المتغير link.tx.cb . يبدو أن هذا خطأ مطبعي ، ويجب أن يكون المتغير الثاني الذي سيتم التحقق منه link.tx.cb_data .

الجزء N2 ، تجاوز سعة المخزن المؤقت

النظر في الوظيفة net_hostname_get ، التي سيتم استخدامها لاحقًا.

#if defined(CONFIG_NET_HOSTNAME_ENABLE)
const char *net_hostname_get(void);
#else
static inline const char *net_hostname_get(void)
{
  return "zephyr";
}
#endif

في حالتي ، عند المعالجة المسبقة ، تم تحديد الخيار المتعلق بفرع #else . أي ، في الملف المعالج ، يتم تنفيذ الوظيفة على النحو التالي:
static inline const char *net_hostname_get(void)
{
  return "zephyr";
}

تقوم الدالة بإرجاع مؤشر إلى صفيف من 7 بايت (نأخذ في الاعتبار محطة الصفر في نهاية السطر).

ضع في اعتبارك الآن الرمز الذي يؤدي إلى الخروج من حدود المصفوفة.

static int do_net_init(void)
{
  ....
  (void)memcpy(hostname, net_hostname_get(), MAX_HOSTNAME_LEN);
  ....
}

تحذير PVS-Studio: V512 [CWE-119] سيؤدي استدعاء وظيفة "memcpy" إلى أن يصبح المخزن المؤقت "net_hostname_get ()" خارج النطاق. log_backend_net.c 114

بعد المعالجة المسبقة ، يتم توسيع MAX_HOSTNAME_LEN على النحو التالي:

(void)memcpy(hostname, net_hostname_get(),
    sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx"));

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

الجزء N3 ، تجاوز سعة المخزن المؤقت المحتمل

int do_write_op_json(struct lwm2m_message *msg)
{
  u8_t value[TOKEN_BUF_LEN];
  u8_t base_name[MAX_RESOURCE_LEN];
  u8_t full_name[MAX_RESOURCE_LEN];
  ....
  /* combine base_name + name */
  snprintf(full_name, TOKEN_BUF_LEN, "%s%s", base_name, value);
  ....
}

تحذير PVS-Studio: V512 [CWE-119] سيؤدي استدعاء وظيفة "snprintf" إلى تجاوز سعة المخزن المؤقت "full_name". lwm2m_rw_json.c 826

إذا استبدلنا قيم الماكرو ، فستبدو صورة ما يحدث كما يلي:

u8_t value[64];
u8_t base_name[20];
u8_t full_name[20];
....
snprintf(full_name, 64, "%s%s", base_name, value);

يتم تخصيص 20 بايت فقط تحت المخزن المؤقت full_name الذي يتم فيه تشكيل السلسلة. في هذه الحالة ، يتم تخزين الأجزاء التي تتكون منها السلسلة في مخازن عازلة بحجم 20 و 64 بايت. علاوة على ذلك ، من الواضح أن الثابت 64 ، الذي تم تمريره إلى وظيفة snprintf والمصممة لمنع الصفيف من السفر إلى الخارج ، كبير جدًا!

لا يؤدي هذا الرمز بالضرورة إلى تجاوز سعة المخزن المؤقت. ربما محظوظ دائمًا ، والخطوط الفرعية دائمًا ما تكون صغيرة جدًا. ومع ذلك ، بشكل عام ، هذا الرمز غير محمي من تجاوز بأي شكل من الأشكال ويحتوي على عيب الأمان الكلاسيكي CWE-119 .

الجزء N4 ، التعبير صحيح دائمًا

static int keys_set(const char *name, size_t len_rd, settings_read_cb read_cb,
                    void *cb_arg)
{
  ....
  size_t len;
  ....
  len = read_cb(cb_arg, val, sizeof(val));
  if (len < 0) {
    BT_ERR("Failed to read value (err %zu)", len);
    return -EINVAL;
  }
  ....
}

تحذير PVS-Studio: V547 [CWE-570] التعبير 'len <0' خطأ دائمًا. قيمة النوع غير الموقعة هي أبداً <0. keys.c 312

المتغير len له نوع غير موقّع ، وبالتالي ، لا يمكن أن يكون أقل من 0. بناءً على ذلك ، لا تتم معالجة حالة الخطأ بأي شكل من الأشكال. في أماكن أخرى ، يتم استخدام النوع int أو ssize_t لتخزين نتيجة دالة read_cb . مثال:


static inline int mesh_x_set(....)
{
 ssize_t len;
 len = read_cb(cb_arg, out, read_len);
 if (len < 0) {
 ....
}

ملحوظة. يبدو أن كل شيء سيئ مع وظيفة read_cb . والحقيقة أنه تم إعلانها على هذا النحو:

static u8_t read_cb(const struct bt_gatt_attr *attr, void *user_data)

نوع u8_t هو الحرف غير الموقعة.

تقوم الدالة دائمًا بإرجاع أرقام موجبة فقط من نوع char غير الموقعة . إذا وضعت هذه القيمة في متغير موقّع من النوع int أو ssize_t ، فسيكون القيمة موجبة دائمًا. لذلك ، في أماكن أخرى ، لا يعمل التحقق من حالة الخطأ أيضًا. لكنني لم أخوض في دراسة هذه القضية.

جزء N5 ، شيء غريب جدا

static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    ((u8_t *)mntpt)[strlen(mntpt)] = '\0';
    memcpy(cpy_mntpt, mntpt, strlen(mntpt));
  }
  return cpy_mntpt;
}

تحذير PVS-Studio: V575 [CWE-628] لا تقوم وظيفة "memcpy" بنسخ السلسلة بأكملها. استخدم الدالة 'strcpy / strcpy_s' للحفاظ على القيمة الطرفية فارغة. غلاف ج 427

كود غريب

حاول شخص ما عمل تناظرية لوظيفة strdup ، لكنه لم ينجح.

لنبدأ بتحذير المحلل. تشير إلى أن وظيفة memcpy تنسخ السطر ، لكنها لا تنسخ الطرفية صفر ، وهذا أمر مريب للغاية.

يبدو أنه تم نسخ هذا الطرف 0 هنا:

((u8_t *)mntpt)[strlen(mntpt)] = '\0';

لكن لا! إليك خطأ مطبعي ، والذي يتم من خلاله نسخ المحطة الصفرية لنفسها! لاحظ أن الكتابة إلى مصفوفة mntpt ، وليس cpy_mntpt . نتيجة لذلك ، ترجع الدالة mntpt_prepare سلسلة غير كاملة مع صفر طرفية.

في الحقيقة ، أراد المبرمج أن يكتب هكذا:

((u8_t *)cpy_mntpt)[strlen(mntpt)] = '\0';

ومع ذلك ، لا يزال من غير الواضح لماذا كانت معقدة للغاية! يمكن تبسيط هذا الرمز إلى الخيار التالي:

static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    strcpy(cpy_mntpt, mntpt);
  }
  return cpy_mntpt;
}

الجزء N6 ، إلغاء الإشارة إلى المؤشر قبل التحقق من الصحة

int bt_mesh_model_publish(struct bt_mesh_model *model)
{
  ....
  struct bt_mesh_model_pub *pub = model->pub;
  ....
  struct bt_mesh_msg_ctx ctx = {
    .send_rel = pub->send_rel,
  };
  ....
  if (!pub) {
    return -ENOTSUP;
  }
  ....
}

تحذير PVS-Studio: V595 [CWE-476] تم استخدام مؤشر "pub" قبل التحقق من صلاحيته باستخدام nullptr. التحقق من الخطوط: 708 ، 719. access.c 708 نمط خطأ شائع

جدًا . أولاً ، يتم إلغاء الإشارة إلى المؤشر لتهيئة عضو في الهيكل:

.send_rel = pub->send_rel,

وبعد ذلك فقط يتبع التحقق من أن هذا المؤشر يمكن أن يكون فارغًا.

الجزء N7-N9 ، الإشارة إلى المؤشر قبل التحقق من الصحة

int net_tcp_accept(struct net_context *context, net_tcp_accept_cb_t cb,
                   void *user_data)
{
  ....
  struct tcp *conn = context->tcp;
  ....
  conn->accept_cb = cb;

  if (!conn || conn->state != TCP_LISTEN) {
    return -EINVAL;
  }
  ....
}

تحذير PVS-Studio: V595 [CWE-476] تم استخدام مؤشر "conn" قبل التحقق من صحته باستخدام nullptr. تحقق من الخطوط: 1071 ، 1073. tcp2.c 1071

وهو نفس الحال في الحالة السابقة. التفسير غير مطلوب هنا.

يمكن رؤية خطأين آخرين هنا:

  • V595 [CWE-476] تم استخدام مؤشر "السياق-> tcp" قبل التحقق منه مقابل nullptr. خطوط التحقق: 1512 ، 1518. tcp.c 1512
  • V595 [CWE-476] تم استخدام مؤشر "fsm" قبل التحقق من صلاحيته باستخدام nullptr. تحقق من الخطوط: 365 ، 382. fsm.c 365

جزء N10 ، فحص خاطئ

static int x509_get_subject_alt_name( unsigned char **p,
                                      const unsigned char *end,
                                      mbedtls_x509_sequence *subject_alt_name)
{
  ....
    while( *p < end )
    {
        if( ( end - *p ) < 1 )
            return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
                    MBEDTLS_ERR_ASN1_OUT_OF_DATA );
    ....
  }
  ....
}

تحذير PVS-Studio: V547 [CWE-570] التعبير '(النهاية - * ص) <1' ​​خطأ دائمًا. x509_crt.c 635

ألق نظرة عن كثب على الشروط:

  • * ص <النهاية
  • (النهاية - * ص) <1

يتعارضون مع بعضهم البعض.

إذا كانت (* p <end) ، فستعطي (end - * p) دائمًا قيمة 1 أو أكثر. بشكل عام ، هناك شيء خاطئ هنا ، لكني لا أعرف كيفية تهجئته بشكل صحيح.

الجزء N11 ، رمز لا يمكن الوصول إليه

uint32_t lv_disp_get_inactive_time(const lv_disp_t * disp)
{
    if(!disp) disp = lv_disp_get_default();
    if(!disp) {
        LV_LOG_WARN("lv_disp_get_inactive_time: no display registered");
        return 0;
    }

    if(disp) return lv_tick_elaps(disp->last_activity_time);

    lv_disp_t * d;
    uint32_t t = UINT32_MAX;
    d          = lv_disp_get_next(NULL);
    while(d) {
        t = LV_MATH_MIN(t, lv_tick_elaps(d->last_activity_time));
        d = lv_disp_get_next(d);
    }

    return t;
}

تحذير PVS-Studio: V547 [CWE-571] التعبير "disp" صحيح دائمًا. lv_disp.c 148

تنتهي الوظيفة إذا كان disp مؤشر فارغ. ثم ، على العكس ، يتم التحقق من أن مؤشر disp ليس فارغًا (وهذا هو الحال دائمًا) ، وتنتهي الوظيفة عملها مرة أخرى.

ونتيجة لذلك ، لن يتحكم جزء من التعليمات البرمجية في إحدى الوظائف مطلقًا.

جزء N12 ، قيمة إرجاع غريبة

static size_t put_end_tlv(struct lwm2m_output_context *out, u16_t mark_pos,
        u8_t *writer_flags, u8_t writer_flag,
        int tlv_type, int tlv_id)
{
  struct tlv_out_formatter_data *fd;
  struct oma_tlv tlv;
  u32_t len = 0U;

  fd = engine_get_out_user_data(out);
  if (!fd) {
    return 0;
  }

  *writer_flags &= ~writer_flag;

  len = out->out_cpkt->offset - mark_pos;

  /* use stored location */
  fd->mark_pos = mark_pos;

  /* set instance length */
  tlv_setup(&tlv, tlv_type, tlv_id, len);
  len = oma_tlv_put(&tlv, out, NULL, true) - tlv.length;
  return 0;
}

تحذير PVS-Studio: V1001 يتم تعيين متغير "len" ولكن لا يتم استخدامه في نهاية الوظيفة. lwm2m_rw_oma_tlv.c 338

تحتوي الدالة على جهازي إرجاع يعود كلاهما 0. ومن الغريب أن الدالة ترجع دائمًا 0. ومن الغريب أيضًا أن متغير len لم يعد يُستخدم بعد التعيين. لدي شك كبير في أنه يجب كتابتها على هذا النحو:

  len = oma_tlv_put(&tlv, out, NULL, true) - tlv.length;
  return len;
}

جزء N13-N16 ، خطأ في المزامنة

static int nvs_startup(struct nvs_fs *fs)
{
  ....
  k_mutex_lock(&fs->nvs_lock, K_FOREVER);
  ....
  if (fs->ate_wra == fs->data_wra && last_ate.len) {
    return -ESPIPE;
  }
  ....
end:
  k_mutex_unlock(&fs->nvs_lock);
  return rc;
}

تحذير PVS-Studio: V1020 تم إنهاء الوظيفة بدون استدعاء الوظيفة "k_mutex_unlock". التحقق من الخطوط: 620 ، 549. nvs.c 620

هناك حالة عندما تنهي الدالة عملها دون فتح كائن المزامنة. كما أفهمها ، سيكون من الصحيح أن أكتب مثل هذا:

static int nvs_startup(struct nvs_fs *fs)
{
  ....
  k_mutex_lock(&fs->nvs_lock, K_FOREVER);
  ....
  if (fs->ate_wra == fs->data_wra && last_ate.len) {
    rc = -ESPIPE;
    goto end;
  }
  ....
end:
  k_mutex_unlock(&fs->nvs_lock);
  return rc;
}

ثلاثة أخطاء أخرى:

  • V1020 تم إنهاء الوظيفة بدون استدعاء الوظيفة "k_mutex_unlock". التحقق من الخطوط: 574 ، 549. nvs.c 574
  • V1020 تم إنهاء الوظيفة بدون استدعاء الوظيفة "k_mutex_unlock". تحقق من الخطوط: 908 ، 890. net_context.c 908
  • V1020 تم إنهاء الوظيفة بدون استدعاء الوظيفة "k_mutex_unlock". خطوط التحقق: 1194 ، 1189. قشرة ج 1194

استنتاج


اتمنى ان تكون قد استمتعت به. قم بزيارة مدونتنا لتقرأ عن فحوصات المشاريع الأخرى والمنشورات الأخرى المثيرة للاهتمام.

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

أقترح أيضًا عدم التأجيل ومحاولة التحقق من مشاريعك باستخدام محلل PVS-Studio. انظر المقال: كيف ترى بسرعة التحذيرات المثيرة للاهتمام التي تم إنشاؤها بواسطة محلل PVS-Studio لرمز C و C ++؟



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

All Articles