Kode sampel di bawah ini terjadi secara alami. Tiba-tiba kode saya menghasilkan FatalExecutionEngineError
pengecualian yang sangat buruk . Saya menghabiskan waktu 30 menit untuk mengisolasi dan meminimalkan sampel pelakunya. Kompilasi ini menggunakan Visual Studio 2012 sebagai aplikasi konsol:
class A<T>
{
static A() { }
public A() { string.Format("{0}", string.Empty); }
}
class B
{
static void Main() { new A<object>(); }
}
Seharusnya menghasilkan kesalahan ini pada .NET framework 4 dan 4.5:
Apakah ini bug yang diketahui, apa penyebabnya, dan apa yang bisa saya lakukan untuk menguranginya? Pekerjaan saya saat ini adalah tidak menggunakan string.Empty
, tetapi apakah saya menggonggong pohon yang salah? Mengubah apa pun tentang kode itu membuatnya berfungsi seperti yang Anda harapkan - misalnya menghapus konstruktor statis kosong A
, atau mengubah tipe paramter dari object
menjadi int
.
Saya mencoba kode ini di laptop saya dan tidak mengeluh. Namun, saya memang mencoba aplikasi utama saya dan jatuh di laptop juga. Saya pasti telah menghancurkan sesuatu saat mengurangi masalah, saya akan melihat apakah saya bisa mengetahui apa itu.
Laptop saya macet dengan kode yang sama seperti di atas, dengan framework 4.0, tetapi crash utama bahkan dengan 4,5. Kedua sistem menggunakan VS'12 dengan pembaruan terbaru (Juli?).
Informasi lebih lanjut :
- Kode IL (kompilasi Debug / Setiap CPU / 4.0 / VS2010 (bukankah IDE itu penting?)): Http://codepad.org/boZDd98E
- Tidak terlihat VS 2010 dengan 4.0. Tidak mogok dengan / tanpa optimisasi, CPU target berbeda, debugger terpasang / tidak terpasang, dll. - Tim Medora
- Gangguan pada 2010 jika saya menggunakan AnyCPU, baik-baik saja di x86. Gangguan di Visual Studio 2010 SP1, menggunakan Platform Target = AnyCPU, tetapi baik-baik saja dengan Platform Target = x86. Mesin ini memiliki VS2012RC diinstal juga sehingga 4,5 mungkin melakukan penggantian di tempat. Gunakan AnyCPU dan TargetPlatform = 3.5 maka itu tidak crash sehingga terlihat seperti regresi dalam Framework.- colinsmith
- Tidak dapat mereproduksi pada x86, x64 atau AnyCPU di VS2010 dengan 4.0. - Fuji
- Hanya terjadi untuk x64, (2012rc, Fx4.5) - Henk Holterman
- VS2012 RC pada Win8 RP. Awalnya Tidak melihat MDA ini saat menargetkan .NET 4.5. Saat beralih ke penargetan .NET 4.0, MDA muncul. Kemudian setelah beralih kembali ke .NET 4.5 MDA tetap. - Wayne
Jawaban:
Ini juga bukan jawaban yang lengkap, tetapi saya punya beberapa ide.Saya percaya saya telah menemukan penjelasan yang baik karena kita akan menemukan tanpa seseorang dari tim NET JIT yang menjawab.
MEMPERBARUI
Saya melihat sedikit lebih dalam, dan saya yakin saya telah menemukan sumber masalahnya. Tampaknya disebabkan oleh kombinasi bug dalam logika inisialisasi tipe JIT, dan perubahan dalam kompiler C # yang bergantung pada asumsi bahwa JIT berfungsi sebagaimana dimaksud. Saya pikir bug JIT ada di. NET 4.0, tetapi ditemukan oleh perubahan dalam kompiler untuk .NET 4.5.
Saya kira itu
beforefieldinit
bukan satu-satunya masalah di sini. Saya pikir ini lebih sederhana dari itu.Jenis
System.String
mscorlib.dll dari .NET 4.0 berisi konstruktor statis:Dalam versi .NET 4.5 dari mscorlib.dll,
String.cctor
(konstruktor statis) jelas tidak ada:Dalam kedua versi
String
jenisnya dihiasi denganbeforefieldinit
:Saya mencoba membuat jenis yang akan dikompilasi ke IL sama (sehingga memiliki bidang statis tetapi tidak ada konstruktor statis
.cctor
), tetapi saya tidak bisa melakukannya. Semua tipe ini memiliki.cctor
metode dalam IL:Dugaan saya adalah bahwa dua hal berubah antara .NET 4.0 dan 4.5:
Pertama: EE diubah sehingga secara otomatis akan diinisialisasi
String.Empty
dari kode yang tidak dikelola. Perubahan ini mungkin dibuat untuk .NET 4.0.Kedua: Kompiler berubah sehingga tidak memancarkan konstruktor statis untuk string, mengetahui bahwa
String.Empty
akan ditugaskan dari sisi yang tidak dikelola. Perubahan ini tampaknya telah dibuat untuk .NET 4.5.Tampaknya EE tidak
String.Empty
segera menetapkan beberapa jalur optimasi. Perubahan yang dilakukan ke kompiler (atau apa pun yang diubah untukString.cctor
menghilangkan) mengharapkan EE membuat penugasan ini sebelum kode pengguna dijalankan, tetapi tampaknya EE tidak membuat penugasan ini sebelumString.Empty
digunakan dalam metode tipe referensi kelas generik reified.Terakhir, saya percaya bahwa bug tersebut mengindikasikan masalah yang lebih dalam pada logika inisialisasi tipe JIT. Tampaknya perubahan pada kompiler adalah kasus khusus untuk
System.String
, tetapi saya ragu bahwa JIT telah membuat kasus khusus untuk iniSystem.String
.Asli
Pertama-tama, WOW Orang-orang BCL menjadi sangat kreatif dengan beberapa optimasi kinerja. Banyak dari
String
metode yang sekarang dilakukan dengan menggunakan Thread statis cacheStringBuilder
objek.Saya mengikuti petunjuk itu untuk sementara waktu, tetapi
StringBuilder
tidak digunakan padaTrim
jalur kode, jadi saya memutuskan itu tidak bisa menjadi masalah Thread statis.Saya pikir saya menemukan manifestasi aneh dari bug yang sama.
Kode ini gagal karena pelanggaran akses:
Namun, jika Anda tanda komentar
//new A<int>(out s);
diMain
kemudian kode bekerja dengan baik. Bahkan, jikaA
diverifikasi dengan jenis referensi apa pun, program gagal, tetapi jikaA
diverifikasi dengan jenis nilai apa pun maka kode tersebut tidak gagal. Juga jika Anda berkomentar keluarA
konstruktor statis, kode tidak pernah gagal. Setelah menggali ke dalamTrim
danFormat
, jelas bahwa masalahnya adalah yangLength
sedang digarisbawahi, dan bahwa dalam sampel di atasString
jenis belum diinisialisasi. Secara khusus, di dalam tubuhA
's konstruktor,string.Empty
tidak benar ditetapkan, meskipun di dalam tubuhMain
,string.Empty
yang ditugaskan dengan benar.Sungguh menakjubkan bagi saya bahwa inisialisasi tipe
String
entah bagaimana tergantung pada apakah atau tidakA
diverifikasi dengan tipe nilai. Satu-satunya teori saya adalah bahwa ada beberapa jalur kode JIT yang dioptimalkan untuk inisialisasi tipe generik yang dibagikan di antara semua jenis, dan bahwa jalur tersebut membuat asumsi tentang tipe referensi BCL ("tipe khusus?") Dan statusnya. Pandangan cepat meskipun kelas BCL lainnya denganpublic static
bidang menunjukkan bahwa pada dasarnya semuanya menerapkan konstruktor statis (bahkan yang dengan konstruktor kosong dan tanpa data, sepertiSystem.DBNull
danSystem.Empty
. Tipe nilai BCL denganpublic static
bidang tampaknya tidak menerapkan konstruktor statis (System.IntPtr
, misalnya) Ini sepertinya mengindikasikan bahwa JIT membuat beberapa asumsi tentang inisialisasi tipe referensi BCL.FYI Ini adalah kode JITed untuk dua versi:
A<object>.ctor(out string)
:A<int32>.ctor(out string)
:Sisa kode (
Main
) identik antara dua versi.EDIT
Selain itu, IL dari dua versi identik kecuali untuk panggilan
A.ctor
masukB.Main()
, di mana IL untuk versi pertama berisi:melawan
di yang kedua.
Satu hal yang perlu diperhatikan adalah bahwa kode JITed untuk
A<int>.ctor(out string)
: sama dengan di versi non-generik.sumber
string.Empty
atau""
... :)typeof(string).GetField("Empty").SetValue(null, "Hello world!"); Console.WriteLine(string.Empty);
memberikan hasil yang berbeda pada .NET 4.0 versus .NET 4.5. Apakah perubahan ini terkait dengan perubahan yang dijelaskan di atas? Bagaimana .NET 4.5 secara teknis mengabaikan saya mengubah nilai bidang? Mungkin saya harus mengajukan pertanyaan baru tentang ini?Saya sangat curiga ini disebabkan oleh optimasi ini (terkait dengan
BeforeFieldInit
) di .NET 4.0.Jika saya ingat dengan benar:
Ketika Anda mendeklarasikan konstruktor statis secara eksplisit,
beforefieldinit
dipancarkan, memberi tahu runtime bahwa konstruktor statis harus dijalankan sebelum akses anggota statis .Tebakanku:
Aku menduga bahwa mereka entah bagaimana kacau fakta ini pada JITer x64, sehingga ketika seorang yang berbeda jenis ini anggota statis diakses dari kelas yang sendiri statis konstruktor sudah berjalan, entah bagaimana melompat berjalan (atau mengeksekusi dalam urutan yang salah) yang konstruktor statis - dan karena itu menyebabkan kerusakan. (Anda tidak mendapatkan pengecualian pointer nol, mungkin karena itu bukan inisialisasi nol.)
Saya belum menjalankan kode Anda, jadi bagian ini mungkin salah - tetapi jika saya harus menebak, saya akan mengatakan itu mungkin sesuatu
string.Format
(atauConsole.WriteLine
, yang serupa) perlu diakses secara internal yang menyebabkan kerusakan, seperti mungkin kelas terkait- lokal yang membutuhkan konstruksi statis eksplisit.Sekali lagi, saya belum mengujinya, tapi tebakan terbaik saya pada data.
Jangan ragu untuk menguji hipotesis saya dan biarkan saya tahu bagaimana hasilnya.
sumber
B
tidak memiliki konstruktor statis, dan itu tidak terjadi ketikaA
diverifikasi dengan tipe nilai. Saya pikir ini sedikit lebih rumit.B
memiliki konstruktor statis tidak masalah. KarenaA
memiliki ctor statis, runtime mengacaukan urutan menjalankannya dibandingkan dengan beberapa kelas terkait lokal di beberapa namespace lainnya. Sehingga bidang itu belum diinisialisasi. Namun, jika Anda instantiateA
dengan tipe nilai, maka itu mungkin runtime yang kedua melalui instantiatingA
(CLR mungkin sudah pre-instantiated dengan tipe referensi, sebagai optimisasi) sehingga order berfungsi ketika dijalankan kedua kalinya .beforefieldinit
optimasi yang diberikan adalah akar penyebabnya. Mungkin beberapa penjelasan yang sebenarnya berbeda dari apa yang saya sebutkan, tetapi akar penyebabnya kemungkinan adalah hal yang sama.A<object>.ctor()
.Pengamatan, tetapi DotPeek menunjukkan string yang didekompilasi.
Jika saya menyatakan milik saya
Empty
dengan cara yang sama kecuali tanpa atribut, saya tidak lagi mendapatkan MDA:sumber
""
memecahkannya.