Kode baris tunggal atau validasi Nethermind menggunakan PVS-Studio C # untuk Linux

Gambar 1

Artikel ini didedikasikan untuk peluncuran uji beta PVS-Studio C # untuk Linux, serta plug-in untuk Rider. Untuk alasan yang luar biasa, menggunakan alat ini, kami memeriksa kode sumber produk Nethermind dan dalam artikel ini kita akan melihat kesalahan yang menarik dan terkadang lucu.

Nethermind adalah klien cepat untuk .NET Core Ethereum untuk Linux, Windows, MacOs. Itu dapat digunakan dalam proyek ketika mengatur jaringan pribadi Ethereum atau dApps. Nethermind adalah open source yang terletak di GitHub. Proyek ini didirikan pada 2017 dan terus berkembang.

pengantar


Apakah Anda suka kerja manual? Misalnya, seperti menemukan kesalahan dalam kode program. Setuju, agak membosankan untuk membaca dan menganalisis situs yang Anda tulis atau seluruh proyek untuk mencari bug yang rumit. Nah, jika proyeknya kecil, katakanlah 5.000 baris, tetapi jika ukurannya sudah melebihi seratus ribu atau sejuta baris? Selain itu, ditulis oleh programmer yang berbeda dan kadang-kadang tidak dalam bentuk yang sangat mudah dicerna. Apa yang harus dilakukan dalam kasus ini? Apakah Anda benar-benar harus tidur di suatu tempat, di suatu tempat meremehkan dan 100% waktu Anda untuk menyadari semua kalimat tanpa akhir ini untuk memahami di mana kesalahan jahat ini? Saya ragu Anda ingin melakukan ini. Jadi apa yang harus dilakukan, mungkin ada cara modern untuk mengotomatisasi ini?

Gambar 3

Dan kemudian alat seperti penganalisa kode statis mulai bekerja. Alat analisis statis adalah alat untuk mendeteksi cacat pada kode sumber program. Keuntungan alat ini dibandingkan verifikasi manual adalah sebagai berikut:

  • hampir tidak menghabiskan waktu Anda mencari lokasi kesalahan (setidaknya itu pasti lebih cepat daripada mencari copy-paste yang gagal dengan mata Anda);
  • tidak lelah, tidak seperti orang yang setelah beberapa waktu mencari perlu istirahat;
  • mengetahui banyak pola kesalahan yang bahkan mungkin tidak disadari oleh seseorang;
  • menggunakan teknologi seperti: analisis aliran data, eksekusi simbolis, pencocokan pola, dan lainnya;
  • memungkinkan Anda untuk secara teratur dan kapan saja menganalisis;
  • dll.

Tentu saja, menggunakan penganalisa kode statis tidak menggantikan atau membatalkan ulasan kode. Tetapi tinjauan kode menjadi lebih produktif dan bermanfaat. Anda bisa fokus menemukan kesalahan tingkat tinggi, mentransfer pengetahuan, daripada melelahkan membaca kode untuk mencari kesalahan ketik.

Jika Anda tertarik untuk membaca lebih banyak tentang hal itu, maka saya menyarankan artikel berikut , serta artikel tentang teknologi yang digunakan dalam PVS-Studio.

PVS-Studio C # untuk Linux / macOS


Saat ini, kami memindahkan penganalisis C # kami ke .NET Core, dan kami juga aktif mengembangkan plug-in untuk IDE Rider.

Jika Anda tertarik, Anda dapat mendaftar untuk tes beta dengan mengisi formulir di halaman ini . Instruksi pemasangan akan dikirimkan ke email Anda (jangan khawatir, itu sangat sederhana), serta lisensi untuk menggunakan penganalisa.

Seperti inilah tampilan Rider dengan plugin PVS-Studio:

Gambar 4

Sedikit marah


Saya ingin mengatakan bahwa di beberapa tempat sangat sulit untuk membaca kode sumber Nethermind, karena di dalamnya panjang 300-500 karakter cukup normal. Ya, itu semua kode tanpa memformat dalam 1 baris. Dan garis-garis ini, misalnya, mengandung beberapa operator ternary, dan operator logis, dan tidak ada apa pun di sana. Secara umum, kenikmatan seperti dari musim terakhir dari permainan takhta.

Saya akan menjelaskan sedikit untuk memahami ruang lingkup. Saya memiliki monitor UltraWide dengan panjang sekitar 82 sentimeter. Jika Anda membuka IDE di dalamnya dalam layar penuh, maka itu akan muat sekitar 340 karakter. Artinya, kalimat yang saya bicarakan tidak cocok. Jika Anda ingin melihat tampilannya, saya meninggalkan tautan ke file di GitHub:

Contoh 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}");
    }
}

Contoh Tautan File

2

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

Tautan ke file

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);
    ...     
}

Tautan ke file

Sekarang bayangkan ada kesalahan pada bagian seperti itu, apakah akan menyenangkan untuk mencarinya? Saya yakin tidak, dan semua orang mengerti bahwa tidak mungkin menulis seperti itu. Dan omong-omong, ada tempat serupa dengan kesalahan dalam proyek ini.

Hasil Validasi


Kondisi yang tidak disukai 0


Kondisi 1

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

PVS-Studio Warning: V3106 Kemungkinan indeks di luar batas. Indeks '0' menunjuk di luar batas 'byte'. Nethermind.Network ReceiptsMessageSerializer.cs 50

Untuk melihat kesalahan, pertimbangkan kasus ketika jumlah elemen dalam array adalah 0. Kemudian kondisi byte. Panjang == 0 kondisi akan benar dan indeksOutOfRangeException dari tipe 0 akan dilemparkan ketika mengakses elemen array .

Mereka ingin segera keluar dari metode ini jika array kosong atau elemen 0 sama dengan nilai tertentu, tetapi tampaknya mereka secara tidak sengaja mencampurkan "||" dengan "&&". Saya mengusulkan untuk memperbaiki masalah ini sebagai berikut:

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

Kondisi 2

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

PVS-Studio Warning: V3106 Kemungkinan indeks di luar batas. Indeks '0' menunjuk di luar batas 'typesInGroup'. Nethermind.Runner EthereumStepsManager.cs 70

Situasi yang mirip dengan yang dijelaskan di atas terjadi. Jika jumlah elemen dalam typesInGroup sama dengan 0, maka saat mengakses elemen 0, pengecualian tipe IndexOutOfRangeException akan dilempar .

Hanya dalam hal ini saya tidak mengerti apa yang diinginkan pengembang. Kemungkinan besar, alih-alih typesInGroup [0] Anda harus menulis nol .

Kesalahan atau optimasi yang belum selesai?


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;
         }
      }
   }
   ...
}

Peringatan PVS-Studio: V3102 Akses mencurigakan ke elemen objek 'currentLevel.BlockInfos' dengan indeks konstan di dalam satu loop. Nethermind.Blockchain BlockTree.cs 895

Pada pandangan pertama, kesalahannya jelas - loop ini bertujuan untuk mengulangi elemen currentLevel.BlockInfos , tetapi ketika mengakses currentLevel.BlockInfos [i], mereka menulis currentLevel.BlockInfos [0] . Kami memperbaiki 0 pada i dan misi selesai. Tapi jangan buru-buru, mari kita cari tahu.

Sekarang, kita Panjang mengakses BlockHash dari elemen nol. Jika sama dengan currentHash , maka kita ambil dari currentLevel.BlockInfos semua elemen yang tidak samacurrentHash , tulis untuk itu dan keluar dari loop. Ternyata siklus itu berlebihan.

Saya pikir sebelumnya ada algoritma yang mereka putuskan untuk ubah / optimalkan menggunakan LINQ , tetapi ada yang tidak beres. Sekarang, jika kondisinya salah, kita mendapatkan iterasi yang tidak berarti.

Ngomong-ngomong, jika pengembang yang menulis ini akan menggunakan mode analisis tambahan , maka ia segera menyadari bahwa ada sesuatu yang salah dan akan memperbaiki semuanya dengan cepat. Saat ini, saya akan menulis ulang kode seperti ini:

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

Referensi nol referensi negatif


Dereferencing 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);
}

Peringatan PVS-Studio: V3095 Objek '_logger' digunakan sebelum diverifikasi dengan null. Periksa baris: 118, 118. Nethermind.Wallet DevKeyStoreWallet.cs 118

Kesalahan dalam urutan yang salah. Pertama ada banding ke _logger.IsDebug dan hanya setelah itu _logger diperiksa untuk null . Karenanya, dalam kasus ketika _logger adalah nol , kami mendapatkan pengecualian dari tipe NullReferenceException .

Dereferencing 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();
}

Peringatan PVS-Studio: V3095 Objek '_enode' digunakan sebelum diverifikasi dengan null. Periksa baris: 55, 56. Nethermind.JsonRpc AdminModule.cs 55

Kesalahannya benar-benar mirip dengan yang dijelaskan di atas, hanya kali ini pelakunya adalah _enode .

Saya ingin menambahkan bahwa jika Anda lupa untuk memeriksa nol, maka ingat ini hanya ketika program Anda mogok. Penganalisa akan mengingatkan Anda tentang ini dan semuanya akan baik-baik saja.

Salin-Tempel favorit kami


Kasus 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;
}

Peringatan PVS-Studio: V3001 Ada sub-ekspresi identik 'a.s2 == b.s2' di sebelah kiri dan di sebelah kanan operator '&&'. Nethermind.Dirichlet.Numerics UInt256.cs 1154

Di sini kondisi yang sama diperiksa 2 kali:

a.s2 == b.s2

Karena parameter a dan b memiliki bidang s3 , saya berasumsi bahwa ketika menyalin kita hanya lupa mengubah s2 ke s3 .

Ternyata parameter akan sama lebih sering dari yang diharapkan oleh pembuat kode. Pada saat yang sama, beberapa pengembang berpikir bahwa mereka tidak melakukan ini dan mulai mencari kesalahan di tempat yang sama sekali berbeda, menghabiskan banyak energi dan saraf.

Omong-omong, kesalahan dalam fungsi perbandingan umumnya klasik. Tampaknya, programmer, mengingat fungsi-fungsi seperti itu sederhana, berhubungan dengan tulisan mereka dengan sangat santai dan tanpa perhatian. Bukti . Mengetahui hal ini, sekarang berhati-hatilah :)!

Kasus 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 Warning: V3004 Pernyataan 'then' setara dengan pernyataan 'else'. Nethermind.BeaconNode BeaconNodeFacade.cs 177

Untuk nilai variabel apa pun yang diterima secara lokal, metode ini mengembalikan yang sama. Sulit untuk mengatakan apakah itu kesalahan atau tidak. Misalkan seorang programmer menyalin sebuah baris dan lupa untuk mengubah StatusCode. Berhasil dengan yang lain, maka ini adalah kesalahan nyata. Selain itu, StatusCode memiliki InternalError dan InvalidRequest . Tetapi refactoring kode mungkin disalahkan dan kami tidak peduli tentang diterima secara lokal, dalam hal ini, kondisi menjadi tempat yang membuat Anda duduk dan berpikir apakah itu kesalahan atau tidak. Jadi bagaimanapun, kasus ini sangat tidak menyenangkan.

Kasus 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 Warning: V3004 Pernyataan 'then' setara dengan pernyataan 'else'. Nethermind.Overseer.Test TestBuilder.cs 46

Dan lagi, kami tidak peduli untuk verifikasi, karena pada akhirnya kami mendapatkan hal yang sama. Dan lagi-lagi kita duduk dan menderita, berpikir, tetapi apa yang ingin ditulis pengembang di sini. Buang-buang waktu yang bisa dihindari dengan menggunakan analisis statis dan segera memperbaiki kode ambigu tersebut.

Kasus 4

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

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

Peringatan PVS-Studio: V3021 Ada dua pernyataan 'jika' dengan ekspresi kondisional yang identik. Pernyataan 'jika' pertama berisi pengembalian metode. Ini berarti bahwa pernyataan 'jika' yang kedua tidak masuk akal Nethermind.Network.Benchmark InFlowBenchmarks.cs 55 Tekan

saja Ctrl + V lagi . Kami menghapus kelebihan pemeriksaan dan semuanya baik-baik saja. Saya yakin bahwa jika beberapa kondisi lain penting di sini, maka semua orang akan menulis dalam satu jika melalui operator logis I.

Kasus 5

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

Peringatan PVS-Studio: V3030 Pemeriksaan berulang. Kondisi '_logger.IsInfo' sudah diverifikasi di baris 242. Nethermind.Synchronization SyncServer.cs 244

Dengan cara yang sama seperti dalam kasus keempat, pemeriksaan tambahan dilakukan. Namun, perbedaannya adalah bahwa _logger tidak hanya memiliki satu properti, tetapi juga, misalnya, ' bool IsError {get; } '. Karenanya, kodenya mungkin terlihat seperti ini:

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

Nah, atau semua kesalahan kode refactoring dan hanya satu pemeriksaan tidak lagi diperlukan.

Kasus 6

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

Peringatan PVS-Studio: Ekspresi V3022 'missingParamsCount! = 0' selalu benar. Nethermind.JsonRpc JsonRpcService.cs 127

Periksa kondisi (missingParamsCount! = 0) dan jika itu benar, maka kami menghitung lagi dan menetapkan hasilnya ke variabel. Setuju bahwa ini adalah cara yang cukup orisinal untuk menulis yang benar.

Cek membingungkan


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++;
    }

    ...
  }
  ...
}

Peringatan PVS-Studio: Ekspresi V3022 'i == 0' selalu salah. Nethermind.Synchronization BlockDownloader.cs 192

Mari kita mulai berurutan. Saat menginisialisasi, variabel i diberi nilai 1. Selanjutnya, variabel hanya bertambah, oleh karena itu, nilai akan selalu diteruskan ke fungsi false .

Sekarang mari kita lihat HandleAddResult :

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

Di sini kita tertarik pada isFirstInBatch . Menilai dengan nama parameter ini, itu bertanggung jawab untuk apakah ada sesuatu yang pertama di pesta. Hm, pertama. Mari kita lihat lagi di atas dan melihat bahwa ada 2 panggilan menggunakan i :

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

Jangan lupa bahwa dalam hal ini hitungannya adalah pada 1. Ternyata kita memiliki dua opsi: "pertama" berarti elemen pada indeks 1, atau di bawah simbol 0. Dalam kasus apa pun, pada saat yang sama saya akan sama dengan 1.

Ternyata pemanggilan fungsi akan terlihat seperti ini:

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

Atau seperti ini:

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

Dan lagi, jika pengembang terus menggunakan analisa statis, maka dia akan menulis kode ini dan melihat peringatan, akan segera memperbaikinya dan menikmati hidup.

Sebuah prioritas ??


Situasi 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);
  }
}

Peringatan PVS-Studio:

  • V3123 Mungkin '??' Operator bekerja dengan cara yang berbeda dari yang diharapkan. Prioritasnya lebih rendah daripada prioritas operator lain di bagian kirinya. Nethermind.Trie TrieNode.cs 43
  • V3123 Mungkin '??' Operator bekerja dengan cara yang berbeda dari yang diharapkan. Prioritasnya lebih rendah daripada prioritas operator lain di bagian kirinya. Nethermind.Trie TrieNode.cs 44

Penganalisa menyarankan Anda untuk memeriksa bagaimana kami menggunakan operator "??", dan untuk memahami apa masalahnya, saya mengusulkan untuk mempertimbangkan situasi berikut. Kami melihat garis ini di sini:

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

MemorySizes.RefSize dan MemorySizes.ArrayOverhead adalah konstanta:

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

Oleh karena itu, untuk kejelasan, saya mengusulkan untuk menulis ulang string, mengganti nilainya

(8 + FullRlp?.Length ?? 20)

Sekarang anggap FullRlp adalah nol. Maka (8 + nol) akan menjadi nol . Selanjutnya, kita mendapatkan ekspresi ( null ?? 20 ), yang akan mengembalikan 20.

Ternyata, asalkan FullRlp adalah null , nilai dari MemorySizes.ArrayOverhead akan selalu dikembalikan, terlepas dari apa yang disimpan di MemorySizes.RefSize. Fragmen pada baris di bawah ini mirip.

Tetapi pertanyaannya adalah, apakah pengembang menginginkan perilaku ini? Mari kita lihat baris berikut:

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

Di sini, seperti pada bagian yang dipertimbangkan di atas, MemorySizes.RefSize ditambahkan ke ekspresi, tetapi

perhatikan bahwa setelah operator "+" pertama ada braket. Ternyata kita harus menambahkan beberapa ekspresi ke MemorySizes.RefSize , dan jika itu nol , maka kita akan menambahkan yang lain. Jadi kodenya akan terlihat seperti ini:

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);
  }
}

Sekali lagi, ini hanya asumsi, namun, jika pengembang menginginkan perilaku yang berbeda, maka Anda harus secara eksplisit menunjukkan ini:

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

Dan kemudian, orang yang membaca kode ini tidak perlu mempelajari untuk waktu yang lama apa yang terjadi di sini dan apa yang diinginkan oleh programmer.

Situasi 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)
    {
        ...
    }
    ...
}

Peringatan PVS-Studio: V3123 Mungkin '??' Operator bekerja dengan cara yang berbeda dari yang diharapkan. Prioritasnya lebih rendah daripada prioritas operator lain di bagian kirinya. Nethermind.JsonRpc JsonRpcService.cs 123

Dan lagi, prioritas operasi adalah "??", oleh karena itu, seperti yang terakhir kali, kami mempertimbangkan situasinya. Kami melihat garis ini di sini:

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

Misalkan disediakanParameters adalah nol , maka untuk kejelasan, segera ganti segala sesuatu yang terkait denganParameter yang disediakan dengan nol, dan gantilah nilai acak sebagai gantiParameter yang diharapkan. Panjang :

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

Sekarang langsung terlihat bahwa ada dua pemeriksaan serupa, hanya dalam satu kasus ada tanda kurung, dan yang lain ada. Mari kita jalankan contoh ini. Pertama kita mendapatkan bahwa ( nol ?? 0 ) akan mengembalikan 0, kemudian kurangi 0 dari 100 dan dapatkan 100:

100 + null ?? 0;

Sekarang, alih-alih mengeksekusi " null ?? 0 " dan akhirnya mendapatkan ( 100 + 0 ), kami menjadi sangat berbeda.

Pertama akan dieksekusi ( 100 + null ) dan kami mendapatkan null . Kemudian memeriksa ( nol ?? 0 ), yang mengarah pada fakta bahwa nilai variabel missingParamsCount akan menjadi 0.

Karena kondisi selanjutnya adalah missingParamsCount sama dengan nol, kita dapat mengasumsikan bahwa pengembang mencari perilaku ini. Dan saya akan mengatakan, mengapa tidak menaruh tanda kurung dan mengekspresikan pikiran saya secara eksplisit? Secara umum, ada kemungkinan bahwa pemeriksaan ini disebabkan oleh kesalahpahaman mengapa 0 kadang-kadang dikembalikan, dan ini tidak lebih dari penopang.

Dan lagi, kita membuang-buang waktu, meskipun kita mungkin tidak melakukan ini, gunakan mode analisis tambahan saat menulis kode .

Kesimpulan


Sebagai kesimpulan, saya berharap bahwa saya dapat menyampaikan kepada Anda bahwa penganalisa statis adalah teman Anda, dan bukan pengawas kejahatan yang hanya menunggu Anda melakukan kesalahan.

Perlu juga dicatat bahwa dengan menggunakan alat analisis sekali atau jarang menggunakannya, Anda tentu saja akan menemukan kesalahan dan beberapa dari mereka bahkan akan cepat diperbaiki, tetapi ada juga yang perlu Anda hancurkan. Oleh karena itu, Anda perlu menggunakan penganalisa statis terus-menerus. Kemudian Anda akan menemukan lebih banyak kesalahan dan memperbaikinya pada saat kode program ditulis dan Anda mengerti persis apa yang Anda coba lakukan.

Kebenaran sederhana adalah bahwa semua orang membuat kesalahan dan itu normal. Kita semua belajar dari kesalahan, tetapi hanya dari mereka yang mampu memperhatikan dan memahami. Karenanya, gunakan alat modern untuk mencari kesalahan ini, misalnya - PVS-Studio. Terimakasih atas perhatiannya.



Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Nikolay Mironov. Kode baris tunggal atau periksa Nethermind menggunakan PVS-Studio C # untuk Linux .

All Articles