Mengapa nilai enumerasi dari array multi dimensi tidak sama dengan dirinya sendiri?

151

Mempertimbangkan:

using System;

public class Test
{
    enum State : sbyte { OK = 0, BUG = -1 }

    static void Main(string[] args)
    {
        var s = new State[1, 1];
        s[0, 0] = State.BUG;
        State a = s[0, 0];
        Console.WriteLine(a == s[0, 0]); // False
    }
}

Bagaimana ini bisa dijelaskan? Itu terjadi di debug membangun di Visual Studio 2015 ketika berjalan di x86 JIT. Rilis build atau running di x64 JIT mencetak True seperti yang diharapkan.

Untuk mereproduksi dari baris perintah:

csc Test.cs /platform:x86 /debug

( /debug:pdbonly, /debug:portabledan /debug:fulljuga mereproduksi.)

shingo
sumber
2
ideone.com/li3EzY itu benar. tambahkan informasi lebih lanjut tentang versi .net, IDE, compiler
Backs
1
Sama disini. Tetapi setelah mengutak-atik pengaturan proyek saya menemukan bahwa tidak mencentang kotak centang "Lebih suka 32 bit" di tab "Bangun" membuatnya berfungsi sebagaimana dimaksud - kembali benar. Jadi, sepertinya masalah WoW64 bagi saya.
Dmitry Rotay
2
Tampaknya Anda menunjuk bug dalam framework.
Fabien PERRONNET
1
Menariknya, menjalankan kode yang rusak melalui ildasmdan kemudian ilasm"memperbaikinya" ...
Jon Skeet
2
The /debug=IMPLdaun bendera itu rusak; /debug=OPT"memperbaikinya".
Jon Skeet

Jawaban:

163

Anda menemukan bug pembuatan kode di .NET 4 x86 jitter. Ini sangat luar biasa, hanya gagal ketika kode tidak dioptimalkan. Kode mesin terlihat seperti ini:

        State a = s[0, 0];
013F04A9  push        0                            ; index 2 = 0
013F04AB  mov         ecx,dword ptr [ebp-40h]      ; s[] reference
013F04AE  xor         edx,edx                      ; index 1 = 0
013F04B0  call        013F0058                     ; eax = s[0, 0]
013F04B5  mov         dword ptr [ebp-4Ch],eax      ; $temp1 = eax 
013F04B8  movsx       eax,byte ptr [ebp-4Ch]       ; convert sbyte to int
013F04BC  mov         dword ptr [ebp-44h],eax      ; a = s[0, 0]
        Console.WriteLine(a == s[0, 0]); // False
013F04BF  mov         eax,dword ptr [ebp-44h]      ; a
013F04C2  mov         dword ptr [ebp-50h],eax      ; $temp2 = a
013F04C5  push        0                            ; index 2 = 0
013F04C7  mov         ecx,dword ptr [ebp-40h]      ; s[] reference 
013F04CA  xor         edx,edx                      ; index 1 = 0
013F04CC  call        013F0058                     ; eax = s[0, 0]
013F04D1  mov         dword ptr [ebp-54h],eax      ; $temp3 = eax 
                                               ; <=== Bug here!
013F04D4  mov         eax,dword ptr [ebp-50h]      ; a == s[0, 0] 
013F04D7  cmp         eax,dword ptr [ebp-54h]  
013F04DA  sete        cl  
013F04DD  movzx       ecx,cl  
013F04E0  call        731C28F4  

Perselingkuhan dengan banyak temporer dan duplikasi kode, itu normal untuk kode yang tidak dioptimalkan. Instruksi pada 013F04B8 terkenal, di situlah konversi yang diperlukan dari sbyte ke integer 32-bit terjadi. Fungsi pembantu pengambil array mengembalikan 0x0000000FF, sama dengan State.BUG, dan yang perlu dikonversi ke -1 (0xFFFFFFFF) sebelum nilainya dapat dibandingkan. Instruksi MOVSX adalah instruksi Sign eXtension.

Hal yang sama terjadi lagi di 013F04CC, tetapi kali ini tidak ada instruksi MOVSX untuk melakukan konversi yang sama. Di situlah chip jatuh, instruksi CMP membandingkan 0xFFFFFFFF dengan 0x000000FF dan itu salah. Jadi ini adalah kesalahan kelalaian, pembuat kode gagal memancarkan MOVSX lagi untuk melakukan konversi yang sama ke konversi int.

Apa yang sangat tidak biasa tentang bug ini adalah bahwa ini berfungsi dengan benar ketika Anda mengaktifkan optimizer, sekarang tahu untuk menggunakan MOVSX dalam kedua kasus.

Kemungkinan alasan bahwa bug ini tidak terdeteksi begitu lama adalah penggunaan sbyte sebagai tipe dasar dari enum. Cukup langka untuk dilakukan. Menggunakan array multi dimensi juga penting, kombinasi ini fatal.

Kalau tidak, bug yang cukup kritis akan saya katakan. Seberapa luas itu mungkin sulit ditebak, saya hanya memiliki jitter 4.6.1 x86 untuk diuji. Jitter x64 dan 3.5 x86 menghasilkan kode yang sangat berbeda dan menghindari bug ini. Solusi sementara untuk melanjutkan adalah menghapus sbyte sebagai tipe enum base dan membiarkannya menjadi default, int , jadi tidak perlu ekstensi tanda.

Anda dapat mengajukan bug di connect.microsoft.com, menautkan ke Q + A ini harus cukup untuk memberi tahu mereka segala yang perlu mereka ketahui. Beritahu saya jika Anda tidak ingin meluangkan waktu dan saya akan membereskannya.

Hans Passant
sumber
33
Bagus, data solid dengan penyebab pasti dari masalah aneh ini, selalu menyenangkan untuk dibaca, +1.
Lasse V. Karlsen
11
Silakan kirim tautan ke artikel connect.microsoft.com sehingga kami dapat memilihnya.
Hans Passant
Saya berasumsi menggunakan bytebukannya sbyteharus baik-baik saja dan mungkin lebih baik jika kode asli digunakan dengan mengatakan ORM di mana Anda tidak ingin bendera Anda di database mengambil ruang ekstra.
Voo
6
Saya akan memposting bug di dotnet / coreclr daripada terhubung, Anda akan langsung ke JIT devs.
Lucas Trzesniewski
8
Saya seorang dev di tim JIT di Microsoft. Saya telah mereproduksi bug dan telah membuka masalah untuknya secara internal (pengiriman x86 JIT belum dibuka di github). Dalam hal waktu kapan ini akan diperbaiki, saya mengantisipasi bahwa kami akan memiliki perbaikan ini termasuk dalam rilis utama berikutnya dari alat. Jika bug ini memiliki dampak bisnis, dan Anda memerlukan perbaikan lebih awal, harap ajukan masalah koneksi (connect.microsoft.com) sehingga kami dapat melihat dampaknya dan alternatif apa yang harus kami perbaiki untuk Anda dengan lebih cepat.
Russell C. Hadley
8

Mari kita pertimbangkan deklarasi OP:

enum State : sbyte { OK = 0, BUG = -1 }

Karena bug hanya terjadi ketika BUGnegatif (dari -128 ke -1) dan State adalah enum dari byte yang ditandatangani, saya mulai menganggap bahwa ada masalah pemeran di suatu tempat.

Jika Anda menjalankan ini:

Console.WriteLine((sbyte)s[0, 0]);
Console.WriteLine((sbyte)State.BUG);
Console.WriteLine(s[0, 0]);
unchecked
{
    Console.WriteLine((byte) State.BUG);
}

itu akan menampilkan:

255

-1

BUG

255

Untuk alasan yang saya abaikan (seperti yang sekarang) s[0, 0] dilemparkan ke byte sebelum evaluasi dan itulah mengapa klaim itu a == s[0,0]salah.

Thomas Ayoub
sumber