رمز سطر واحد أو التحقق من Nethermind باستخدام PVS-Studio C # لنظام Linux

الصورة 1

هذه المقالة مخصصة لإطلاق اختبار الإصدار التجريبي من PVS-Studio C # لنظام Linux ، بالإضافة إلى المكون الإضافي لـ Rider. لهذا السبب الرائع ، باستخدام هذه الأدوات ، قمنا بفحص الكود المصدري لمنتج Nethermind وسننظر في هذه المقالة في أخطاء مثيرة للاهتمام وأحيانًا مضحكة.

Nethermind هو عميل سريع لـ .NET Core Ethereum لنظام التشغيل Linux و Windows و MacOs. يمكن استخدامه في المشاريع عند إعداد شبكات خاصة Ethereum أو dApps. Nethermind مفتوح المصدر موجود على جيثب. تأسس المشروع في عام 2017 ويتطور باستمرار.

المقدمة


هل تحب العمل اليدوي؟ على سبيل المثال ، مثل العثور على أخطاء في التعليمات البرمجية للبرنامج. موافق ، من الممل إلى حد ما قراءة وتحليل الموقع الذي كتبته أو المشروع بأكمله بحثًا عن بعض الأخطاء الصعبة. حسنًا ، إذا كان المشروع صغيرًا ، فلنقل 5000 سطر ، ولكن إذا تجاوز حجمه بالفعل مائة ألف أو مليون خط؟ علاوة على ذلك ، تمت كتابته من قبل مبرمجين مختلفين وأحيانًا ليس في شكل سهل الهضم. ماذا تفعل في هذه الحالة؟ هل يجب أن تنام في مكان ما ، وفي مكان ما يقلّل من قيمته و 100٪ من وقتك لتحقيق كل هذه الخطوط التي لا نهاية لها من أجل فهم مكان هذا الخطأ السيئ؟ أشك في أنك ترغب في القيام بذلك. لذا ما العمل ، ربما هناك وسائل حديثة لأتمتة هذا بطريقة أو بأخرى؟

الشكل 3

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

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

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

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

PVS-Studio C # لنظام Linux / macOS


في الوقت الحالي ، ننقل محلل C # الخاص بنا إلى .NET Core ، ونعمل أيضًا بنشاط على تطوير مكون إضافي لـ IDE Rider.

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

هذا ما يبدو عليه رايدر مع ملحق PVS-Studio:

صورة 4

القليل من السخط


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

سأشرح قليلا لفهم النطاق. لدي شاشة UltraWide بطول حوالي 82 سم. إذا قمت بفتح IDE عليه في وضع ملء الشاشة ، فسوف يصلح لحوالي 340 حرفًا. أي أن الخطوط التي أتحدث عنها لا تناسب حتى. إذا كنت تريد أن ترى كيف يبدو ، فقد تركت روابط للملفات على GitHub:

مثال 1

private void LogBlockAuthorNicely(Block block, ISyncPeer syncPeer)
{
    string authorString = (block.Author == null ? null : "sealed by " +
(KnownAddresses.GoerliValidators.ContainsKey(block.Author) ?
KnownAddresses.GoerliValidators[block.Author] : block.Author?.ToString())) ??
(block.Beneficiary == null ? string.Empty : "mined by " +
(KnownAddresses.KnownMiners.ContainsKey(block.Beneficiary) ?
KnownAddresses.KnownMiners[block.Beneficiary] : block.Beneficiary?.ToString()));
    if (_logger.IsInfo)
    {
        if (_logger.IsInfo) _logger.Info($"Discovered a new block
{string.Empty.PadLeft(9 - block.Number.ToString().Length, '
')}{block.ToString(Block.Format.HashNumberAndTx)} {authorString}, sent by
{syncPeer:s}");
    }
}

مثال رابط الملف

2

private void BuildTransitions()
{
    ...
    releaseSpec.IsEip1283Enabled = (_chainSpec.Parameters.Eip1283Transition ??
long.MaxValue) <= releaseStartBlock &&
((_chainSpec.Parameters.Eip1283DisableTransition ?? long.MaxValue) 
> releaseStartBlock || (_chainSpec.Parameters.Eip1283ReenableTransition ??
long.MaxValue) <= releaseStartBlock);           
    ...
}

رابط للملف

public void 
Will_not_reject_block_with_bad_total_diff_but_will_reset_diff_to_null()
{
    ...
    _syncServer = new SyncServer(new StateDb(), new StateDb(), localBlockTree,
NullReceiptStorage.Instance, new BlockValidator(Always.Valid, new
HeaderValidator(localBlockTree, Always.Valid, MainnetSpecProvider.Instance,
LimboLogs.Instance), Always.Valid, MainnetSpecProvider.Instance, 
LimboLogs.Instance), Always.Valid, _peerPool, StaticSelector.Full, 
new SyncConfig(), LimboLogs.Instance);
    ...     
}

رابط إلى الملف

الآن تخيل حدوث خطأ في مثل هذا القسم ، هل سيكون من الرائع البحث عنه؟ أنا متأكد من أنها ليست كذلك ، والجميع يفهم أنه من المستحيل الكتابة بهذه الطريقة. وبالمناسبة ، هناك مكان مماثل مع وجود خطأ في هذا المشروع.

نتائج التحقق


الشروط التي لا تحب 0


الشرط 1

public ReceiptsMessage Deserialize(byte[] bytes)
{
    if (bytes.Length == 0 && bytes[0] == Rlp.OfEmptySequence[0])
        return new ReceiptsMessage(null);
    ...
}

تحذير PVS-Studio: مؤشر V3106 ربما يكون خارج النطاق . يشير مؤشر "0" إلى تجاوز حدود "وحدات البايت". Nethermind.Network ReceiptsMessageSerializer.cs 50 لملاحظة

خطأ ، ضع في اعتبارك الحالة عندما يكون عدد العناصر في المصفوفة 0. ثم يكون شرط bytes.Length == 0 صحيحًا وسيتم طرح indexOutOfRangeException من النوع 0 عند الوصول إلى عنصر المصفوفة .

لقد أرادوا الخروج من هذه الطريقة فورًا إذا كان المصفوفة فارغة أو 0 عنصر يساوي قيمة معينة ، ولكن يبدو أنهم اختلطوا عن طريق الخطأ "||" مع "&&". أقترح إصلاح هذه المشكلة على النحو التالي:

public ReceiptsMessage Deserialize(byte[] bytes)
{
    if (bytes.Length == 0 || bytes[0] == Rlp.OfEmptySequence[0])
        return new ReceiptsMessage(null);
    ...
}

الشرط 2

public void DiscoverAll()
{
    ...
    Type? GetStepType(Type[] typesInGroup)
    {
        Type? GetStepTypeRecursive(Type? contextType)
        {
            ...
        }
        ...
        return typesInGroup.Length == 0 ? typesInGroup[0] :
               GetStepTypeRecursive(_context.GetType());
    }
    ...
}

تحذير PVS-Studio: مؤشر V3106 ربما يكون خارج النطاق . يشير مؤشر "0" إلى ما وراء حدود "typesInGroup". Nethermind.Runner EthereumStepsManager.cs 70

يحدث وضع مشابه لما هو موضح أعلاه. إذا كان عدد العناصر في typesInGroup يساوي 0 ، فعند الوصول إلى العنصر 0 ، سيتم طرح استثناء من النوع IndexOutOfRangeException .

فقط في هذه الحالة لا أفهم ما أراد المطور. على الأرجح ، بدلاً من typesInGroup [0] تحتاج إلى كتابة فارغة .

خطأ أم تحسين غير مكتمل؟


private void DeleteBlocks(Keccak deletePointer)
{
   ...
   if (currentLevel.BlockInfos.Length == 1)
   {
      shouldRemoveLevel = true;
   }
   else
   {
      for (int i = 0; i < currentLevel.BlockInfos.Length; i++)
      {
         if (currentLevel.BlockInfos[0].BlockHash == currentHash) // <=
         {
            currentLevel.BlockInfos = currentLevel.BlockInfos
                                      .Where(bi => bi.BlockHash != currentHash)
                                      .ToArray();
            break;
         }
      }
   }
   ...
}

تحذير PVS-Studio: V3102 وصول مشبوه إلى عنصر كائن "currentLevel.BlockInfos" بواسطة فهرس ثابت داخل حلقة. Nethermind.Blockchain BlockTree.cs 895

للوهلة الأولى ، كان الخطأ واضحًا - تهدف الحلقة إلى تكرار عناصر level.BlockInfos الحالية ، ولكن عند الوصول إلى currentLevel.BlockInfos [i] ، كتبوا currentLevel.BlockInfos [0] . نصلح 0 على i وتكتمل المهمة. لكن لا تتسرع ، دعنا نفهم ذلك.

الآن، ونحن طول وصول BlockHash من عنصر فارغة. إذا كان يساوي currentHash ، فإننا نأخذ من currentLevel.BlockInfos جميع العناصر غير المتساويةCurrentHash ، اكتب إليها واخرج من الحلقة. اتضح أن الدورة غير ضرورية.

أعتقد أنه في وقت سابق كان هناك خوارزمية قرروا تغييرها / تحسينها باستخدام linq ، ولكن حدث خطأ ما. الآن ، في الحالة التي تكون فيها الحالة خاطئة ، نحصل على تكرارات لا معنى لها.

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

private void DeleteBlocks(Keccak deletePointer)
{
    ...
    if (currentLevel.BlockInfos.Length == 1)
    {
        shouldRemoveLevel = true;
    }
    else
    {
        currentLevel.BlockInfos = currentLevel.BlockInfos
                                  .Where(bi => bi.BlockHash != currentHash)
                                  .ToArray();
    }
    ...
}

الرجوع إلى مرجع فارغ


إلغاء الإشارة 1

public void Sign(Transaction tx, int chainId)
{
    if (_logger.IsDebug)
        _logger?.Debug($"Signing transaction: {tx.Value} to {tx.To}");
    IBasicWallet.Sign(this, tx, chainId);
}

تحذير PVS-Studio: V3095 تم استخدام كائن "_logger" قبل التحقق من صلاحيته. تحقق من الأسطر: 118 ، 118. Nethermind.Wallet DevKeyStoreWallet.cs 118

خطأ في التسلسل الخاطئ. أولاً هناك طعن في _logger.IsDebug وفقط بعد ذلك يتم التحقق من _logger على أنه فارغ . وفقا لذلك، في حالة عندما _logger هو باطل ، وحصلنا على استثناء من نوع NullReferenceException .

إلغاء الإشارة 2

private void BuildNodeInfo()
{
    _nodeInfo = new NodeInfo();
    _nodeInfo.Name = ClientVersion.Description;
    _nodeInfo.Enode = _enode.Info;                           // <=
    byte[] publicKeyBytes = _enode?.PublicKey?.Bytes;        // <=
    _nodeInfo.Id = (publicKeyBytes == null ? Keccak.Zero :
                   Keccak.Compute(publicKeyBytes)).ToString(false);
    _nodeInfo.Ip = _enode?.HostIp?.ToString();
    _nodeInfo.ListenAddress = $"{_enode.HostIp}:{_enode.Port}";
    _nodeInfo.Ports.Discovery = _networkConfig.DiscoveryPort;
    _nodeInfo.Ports.Listener = _networkConfig.P2PPort;
    UpdateEthProtocolInfo();
}

تحذير PVS-Studio: V3095 تم استخدام كائن "_enode" قبل التحقق من صلاحيته. تحقق من الأسطر: 55 ، 56. Nethermind.JsonRpc AdminModule.cs 55

الخطأ تمامًا كما هو موضح أعلاه ، فقط هذه المرة الجاني هو _enode .

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

المفضلة لدينا نسخ ولصق


حالة 1

public static bool Equals(ref UInt256 a, ref UInt256 b)
{
    return a.s0 == b.s0 && a.s1 == b.s1 && a.s2 == b.s2 && a.s2 == b.s2;
}

تحذير PVS-Studio: V3001 هناك تعبيرات فرعية متطابقة "a.s2 == b.s2" إلى اليسار وإلى يمين عامل التشغيل "&&". Nethermind.Dirichlet.Numerics UInt256.cs 1154

هنا يتم التحقق من نفس الحالة مرتين:

a.s2 == b.s2

نظرًا لأن المعلمات a و b لها حقل s3 ، افترض أنه عند النسخ نسينا ببساطة تغيير s2 إلى s3 .

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

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

الحالة 2

public async Task<ApiResponse> 
PublishBlockAsync(SignedBeaconBlock signedBlock,
                  CancellationToken cancellationToken)
{
    bool acceptedLocally = false;
    ...
    if (acceptedLocally)
    {
        return new ApiResponse(StatusCode.Success);
    }
    else
    {
        return new ApiResponse(StatusCode.Success);
    }
    ...
}

تحذير PVS-Studio: V3004 عبارة "ثم" تعادل عبارة "آخر". Nethermind.BeaconNode BeaconNodeFacade.cs 177

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

الحالة 3

public void TearDown()
{
    ...
    foreach (var testResult in _results)
    {
         string message = $"{testResult.Order}. {testResult.Name} has " 
               + $"{(testResult.Passed ? "passed [+]" : "failed [-]")}";
         if (testResult.Passed)
         {
               TestContext.WriteLine(message);
         }
         else
         {
               TestContext.WriteLine(message);
         }
     }
}

تحذير PVS-Studio: V3004 عبارة "ثم" تعادل عبارة "آخر". Nethermind.Overseer.Test TestBuilder.cs 46

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

الحالة 4

public void Setup()
{
    if (_decoderBuffer.ReadableBytes > 0)
    {
        throw new Exception("decoder buffer");
    }

    if (_decoderBuffer.ReadableBytes > 0)
    {
        throw new Exception("decoder buffer");
    }
    ...
}

تحذير PVS-Studio: V3021 هناك عبارتان 'if' مع تعبيرات شرطية متطابقة. تحتوي العبارة 'if' الأولى على طريقة الإرجاع. هذا يعني أن العبارة الثانية "if" هي عبارة بلا معنى Nethermind.Network.Benchmark InFlowBenchmarks.cs 55

فقط اضغط على Ctrl + V مرة أخرى . نزيل الشيك الزائد وكل شيء على ما يرام. أنا متأكد من أنه إذا كانت هناك حالة أخرى مهمة هنا ، فسيكتب الجميع في حالة واحدة من خلال العامل المنطقي I.

الحالة 5

private void LogBlockAuthorNicely(Block block, ISyncPeer syncPeer)
{
    if (_logger.IsInfo)
    {
        if (_logger.IsInfo)
        {
            ...
        }
    }
}

تحذير PVS-Studio: V3030 الاختيار المتكرر. تم التحقق من حالة "_logger.IsInfo" بالفعل في السطر 242. Nethermind.Synchronization SyncServer.cs 244

بنفس الطريقة كما في الحالة الرابعة ، يتم إجراء فحص إضافي. ومع ذلك ، فإن الفرق هو أن _logger ليس له خاصية واحدة فقط ، ولكن أيضًا ، على سبيل المثال ، ' bool IsError {get؛ } ". لذلك ، من المحتمل أن يبدو الرمز على النحو التالي:

private void LogBlockAuthorNicely(Block block, ISyncPeer syncPeer)
{
    if (_logger.IsInfo)
    {
        if (!_logger.IsError) // <=
        {
            ...
        }
    }
}

حسنًا ، أو لم يعد هناك خطأ في إعادة هيكلة الرمز وفحص واحد فقط.

الحالة 6

if (missingParamsCount != 0)
{
    bool incorrectParametersCount = missingParamsCount != 0; // <=
    if (missingParamsCount > 0)
    {
        ...
    }
    ...
}

تحذير PVS-Studio: تعبير V3022 'مفقودParamsCount! = 0' صحيح دائمًا. Nethermind.JsonRpc JsonRpcService.cs 127

تحقق من الحالة (مفقودة ParamsCount! = 0) وإذا كان صحيحًا ، فإننا نحسبها مرة أخرى ونعين النتيجة لمتغير. توافق على أن هذه طريقة أصلية إلى حد ما لكتابة الحقيقة.

شيك مربك


public async Task<long> 
DownloadHeaders(PeerInfo bestPeer, 
                BlocksRequest blocksRequest, 
                CancellationToken cancellation)
{
  ...
  for (int i = 1; i < headers.Length; i++)
  {
    ...
    BlockHeader currentHeader = headers[i];
    ...
    bool isValid = i > 1 ? 
        _blockValidator.ValidateHeader(currentHeader, headers[i - 1], false):
        _blockValidator.ValidateHeader(currentHeader, false);
    ...
    if (HandleAddResult(bestPeer, 
                        currentHeader, 
                        i == 0,                              // <=
                        _blockTree.Insert(currentHeader))) 
    {
       headersSynced++;
    }

    ...
  }
  ...
}

تحذير PVS-Studio: تعبير V3022 'i == 0' خطأ دائمًا. Nethermind.Synchronization BlockDownloader.cs 192

لنبدأ بالترتيب. عند التهيئة ، يتم تعيين القيمة 1. للمتغير i. بعد ذلك ، يتم زيادة المتغير فقط ؛ وبالتالي ، سيتم تمرير القيمة دائمًا إلى الدالة false .

الآن دعونا نلقي نظرة على HandleAddResult :

private bool HandleAddResult(PeerInfo peerInfo, 
                             BlockHeader block,
                             bool isFirstInBatch, 
                             AddBlockResult addResult)
{
    ...
    if (isFirstInBatch)
    {
        ...
    }
    else
    {
        ...
    }
    ...
}

نحن مهتمون في isFirstInBatch . إذا حكمنا باسم هذه المعلمة ، فهي مسؤولة عما إذا كان شيء ما هو الأول في الحزب. حسنًا ، أولاً. دعونا ننظر مرة أخرى أعلاه ونرى أن هناك مكالمتين باستخدام i :

BlockHeader currentHeader = headers[i];
_blockValidator.ValidateHeader(currentHeader, headers[i - 1], false)

لا ننسى أنه في هذه الحالة العد هو في 1. وتبين أن لدينا خيارين: إما وسيلة "الأولى" عنصر في الفهرس 1، أو تحت الرمز 0. وعلى أية حال، في الوقت نفسه أنا سوف يكون على قدم المساواة إلى 1.

وتبين أن يجب أن يبدو استدعاء دالة مثل هذا:

HandleAddResult(bestPeer, currentHeader, 
                i == 1, _blockTree.Insert(currentHeader))

او مثل هذا:

HandleAddResult(bestPeer, currentHeader, 
                i - 1 == 0, _blockTree.Insert(currentHeader))

ومرة أخرى ، إذا استخدم المطور باستمرار محللًا ثابتًا ، فسيكتب هذا الرمز ويرى تحذيرًا ، وسيصلحه بسرعة ويستمتع بالحياة.

أولوية ؟؟


الحالة 1

public int MemorySize
{
  get
  {
    int unaligned = (Keccak == null ? MemorySizes.RefSize : 
        MemorySizes.RefSize + Keccak.MemorySize) 
        + (MemorySizes.RefSize + FullRlp?.Length 
                                 ?? MemorySizes.ArrayOverhead)   // <=
        + (MemorySizes.RefSize + _rlpStream?.MemorySize 
                                 ?? MemorySizes.RefSize)         // <=
        + MemorySizes.RefSize + (MemorySizes.ArrayOverhead + _data?.Length 
        * MemorySizes.RefSize ?? MemorySizes.ArrayOverhead) 
        + MemorySizes.SmallObjectOverhead + (Key?.MemorySize ?? 0);
    return MemorySizes.Align(unaligned);
  }
}

تحذيرات PVS-Studio:

  • V3123 لعله ؟؟ عامل التشغيل بطريقة مختلفة عما كان متوقعا. أولويتها أقل من أولوية المشغلين الآخرين في الجزء الأيسر. Nethermind.Trie TrieNode.cs 43
  • V3123 لعله ؟؟ عامل التشغيل بطريقة مختلفة عما كان متوقعا. أولويتها أقل من أولوية المشغلين الآخرين في الجزء الأيسر. Nethermind.Trie TrieNode.cs 44

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

(MemorySizes.RefSize + FullRlp?.Length ?? MemorySizes.ArrayOverhead)

MemorySizes.RefSize و MemorySizes.ArrayOverhead هي ثوابت:

public static class MemorySizes
{
    ...
    public const int RefSize = 8;
    public const int ArrayOverhead = 20;
    ...
}

لذلك ، من أجل الوضوح ، أقترح إعادة كتابة السلسلة ، واستبدال قيمها:

(8 + FullRlp?.Length ?? 20)

لنفترض الآن FullRlp هو باطل. ثم (8 + null) ستكون فارغة . بعد ذلك ، نحصل على التعبير ( null ؟؟ 20 ) ، والذي سيُرجع 20.

وتبين أنه ، شريطة أن يكون FullRlp فارغًا ، سيتم إرجاع القيمة من MemorySizes.ArrayOverhead دائمًا ، بغض النظر عن ما يتم تخزينه في MemorySizes.RefSize. الجزء الموجود على الخط أدناه مشابه.

لكن السؤال هو ، هل أراد المطور هذا السلوك؟ دعونا نلقي نظرة على السطر التالي:

MemorySizes.RefSize + (MemorySizes.ArrayOverhead 
    + _data?.Length * MemorySizes.RefSize ?? MemorySizes.ArrayOverhead) 

هنا ، كما هو الحال في الأقسام المذكورة أعلاه ، تمت إضافة MemorySizes.RefSize إلى التعبير ، ولكن

لاحظ أنه بعد عامل التشغيل "+" الأول يوجد قوس. اتضح أننا يجب أن نضيف بعض التعبيرات إلى MemorySizes.RefSize ، وإذا كانت فارغة ، فسنضيف أخرى. لذلك يجب أن يبدو الرمز كما يلي:

public int MemorySize
{
  get
  {
    int unaligned = (Keccak == null ? MemorySizes.RefSize : 
       MemorySizes.RefSize + Keccak.MemorySize) 
       + (MemorySizes.RefSize + (FullRlp?.Length 
                                 ?? MemorySizes.ArrayOverhead))    // <=
       + (MemorySizes.RefSize + (_rlpStream?.MemorySize 
                                 ?? MemorySizes.RefSize))          // <=
       + MemorySizes.RefSize + (MemorySizes.ArrayOverhead + _data?.Length 
       * MemorySizes.RefSize ?? MemorySizes.ArrayOverhead) 
       + MemorySizes.SmallObjectOverhead + (Key?.MemorySize ?? 0);
    return MemorySizes.Align(unaligned);
  }
}

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

((MemorySizes.RefSize + FullRlp?.Length) ?? MemorySizes.ArrayOverhead)

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

الحالة 2

private async Task<JsonRpcResponse> 
ExecuteAsync(JsonRpcRequest request, 
             string methodName,
             (MethodInfo Info, bool ReadOnly) method)
{
    var expectedParameters = method.Info.GetParameters();
    var providedParameters = request.Params;
    ...
    int missingParamsCount = expectedParameters.Length 
            - (providedParameters?.Length ?? 0) 
            + providedParameters?.Count(string.IsNullOrWhiteSpace) ?? 0; // <=
    if (missingParamsCount != 0)
    {
        ...
    }
    ...
}

تحذير PVS-Studio: V3123 ربما "؟؟" عامل التشغيل بطريقة مختلفة عما كان متوقعا. أولويتها أقل من أولوية المشغلين الآخرين في الجزء الأيسر. Nethermind.JsonRpc JsonRpcService.cs 123

ومرة أخرى ، فإن أولوية العملية هي "؟؟" ، لذلك ، كما حدث في المرة الأخيرة ، نعتبر الوضع. نحن ننظر إلى هذا الخط هنا:

expectedParameters.Length 
            - (providedParameters?.Length ?? 0) 
            + providedParameters?.Count(string.IsNullOrWhiteSpace) ?? 0;

لنفترض أن providedParameters هو باطل ، ثم من أجل الوضوح، استبدل على الفور كل ما يتعلق providedParameters مع لاغية، و استبدال قيمة عشوائية بدلا من expectedParameters.Length :

100 - (null ?? 0) + null ?? 0;

الآن من الملاحظ على الفور أن هناك نوعان من الفحوصات المماثلة ، فقط في حالة واحدة توجد أقواس ، وفي الحالة الأخرى هناك. لنقم بتشغيل هذا المثال. أولاً نحصل على أن ( null ؟؟ 0 ) سيُرجع 0 ، ثم اطرح 0 من 100 واحصل على 100:

100 + null ?? 0;

الآن ، بدلاً من تنفيذ " null ؟؟ 0 " أولاً والحصول على ( 100 + 0 ) ، أصبحنا مختلفين تمامًا.

أولاً سيتم تنفيذ ( 100 + null ) ونحصل على null . ثم يتحقق ( null ؟؟ 0 ) ، مما يؤدي إلى حقيقة أن قيمة المتغير المفقود ParamCount ستكون 0.

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

ومرة أخرى ، نحن نضيع الوقت ، على الرغم من أننا ربما لم نفعل ذلك ؛ استخدم وضع التحليل المتزايد عند كتابة التعليمات البرمجية .

استنتاج


في الختام ، آمل أن أكون قد تمكنت من إخبارك بأن المحلل الثابت هو صديقك ، وليس مشرفًا شريرًا ينتظر منك فقط لارتكاب خطأ.

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

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



إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فيرجى استخدام رابط الترجمة: Nikolay Mironov. رمز سطر واحد أو التحقق من Nethermind باستخدام PVS-Studio C # لنظام Linux .

All Articles