Untuk melakukan penelitian tentang pengoperasian program dan OS, ada banyak alat yang berbeda. Mesin virtual, IDE, smart notes, IDA, radare, hex editor, pe editor, dan bahkan lebih dari seratus utilitas Sysinternals - semua ini dilakukan untuk memfasilitasi banyak operasi rutin. Tetapi kadang-kadang suatu saat ketika Anda menyadari bahwa di antara semua keragaman ini Anda kehilangan utilitas kecil yang hanya akan melakukan pekerjaan yang biasa dan sederhana. Anda dapat menulis skrip dengan python atau Powershell di atas lutut, tetapi seringkali Anda tidak dapat melihat kerajinan seperti itu tanpa air mata dan membaginya dengan kolega Anda.
Baru-baru ini, situasi ini datang kepada saya lagi. Dan saya memutuskan sudah waktunya untuk mengambil dan menulis utilitas yang rapi. Saya akan bercerita tentang utilitas di salah satu artikel berikutnya, tetapi saya akan menceritakan tentang salah satu masalah selama pengembangan sekarang.
Kesalahan memanifestasikan dirinya sebagai berikut: jika Anda
memasukkan banyak baris teks ke dalam kontrol TextBox standar, maka panggilan ke fungsi
GetLineText () mulai dari indeks tertentu akan mengembalikan baris yang salah.
Yang salah adalah bahwa meskipun garis akan dari teks yang diinstal, tetapi terletak lebih jauh, sebenarnya
GetLineText () hanya akan melewatkan beberapa baris. Kesalahan muncul dengan sejumlah besar garis. Jadi saya bertemu dengannya - saya mencoba menampilkan 25 megabyte teks dalam TextBox. Bekerja dengan baris terakhir mengungkapkan efek yang tidak terduga.
Google
menyarankan bahwa kesalahan sudah ada sejak 2011 dan Microsoft tidak terburu-buru untuk memperbaiki sesuatu.
Contoh
Tidak ada persyaratan khusus untuk versi .NET. Kami membuat proyek WPF standar dan mengisi file-file seperti ini:
MainWindow.xaml
<Window x:Class="wpf_textbox.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:wpf_textbox" mc:Ignorable="d" Title="WTF, WPF?" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="20"/> <RowDefinition Height="20"/> </Grid.RowDefinitions> <TextBox Grid.Row="0" Margin="5" Name="txt" AcceptsReturn="True" AcceptsTab="True" /> <Button Grid.Row="1" Content="Fire 1!" Click="btn_OnClick" /> <Button Grid.Row="2" Content="Fire 2!" Click="btn2_OnClick" /> </Grid> </Window>
MainWindow.cs (melewatkan penggunaan dan namespace)
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void btn_OnClick(object sender, RoutedEventArgs e) { var sb = new StringBuilder(); for (int i = 0; i < 90009; i++) sb.AppendLine($"{i}"); txt.Text = sb.ToString(); } private void btn2_OnClick(object sender, RoutedEventArgs e) { var sb = new StringBuilder(); for (var i = 1; i < 7; i++) sb.AppendLine("req: " + 150 * i + ", get: " + txt.GetLineText(150 * i).Trim()); for (var i = 1; i < 7; i++) sb.AppendLine("req: " + 15000 * i + ", get: " + txt.GetLineText(15000 * i).Trim()); txt.Text = sb.ToString(); } }
Aplikasi ini terdiri dari TextBox dan dua tombol. Klik pertama "Fire 1!" (Isi TextBox dengan angka), lalu "Fire 2!" (itu akan meminta baris dengan angka dan mencetak).
Hasil yang Diharapkan:
req: 150, dapatkan: 150
req: 300, dapatkan: 300
req: 450, dapatkan: 450
req: 600, dapatkan: 600
req: 750, dapatkan: 750
req: 900, dapatkan: 900
req: 15000, dapatkan: 15000
req: 30000, dapatkan: 30000
req: 45000, dapatkan: 45000
req: 60000, dapatkan: 60000
req: 75000, dapatkan: 75000
req: 90000, dapatkan: 90000
Realitas:

Dapat dilihat bahwa untuk indeks kurang dari 1000 - semuanya baik-baik saja, tetapi untuk 15000 besar - pergeseran telah dimulai. Dan semakin jauh, semakin banyak.
Jelajahi bug
Kami mengungkap bagian dari resolver yang bertanggung jawab untuk melihat kode sumber .NET dan kelas khusus "Ekspander peluang dan pemenang pembatasan berdasarkan Refleksi".
Extender dan Limiter Kemampuan Refleksi public static class ReflectionExtensions { public static T GetFieldValue<T>(this object obj, string name) { var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var field = obj.GetType().GetField(name, bindingFlags); if (field == null) field = obj.GetType().BaseType.GetField(name, bindingFlags); return (T)field?.GetValue(obj); } public static object InvokeMethod(this object obj, string methodName, params object[] methodParams) { var methodParamTypes = methodParams?.Select(p => p.GetType()).ToArray() ?? new Type[] { }; var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; MethodInfo method = null; var type = obj.GetType(); while (method == null && type != null) { method = type.GetMethod(methodName, bindingFlags, Type.DefaultBinder, methodParamTypes, null); var intfs = type.GetInterfaces(); if (method != null) break; foreach (var intf in intfs) { method = intf.GetMethod(methodName, bindingFlags, Type.DefaultBinder, methodParamTypes, null); if (method != null) break; } type = type.BaseType; } return method?.Invoke(obj, methodParams); } }
Secara empiris, kami menetapkan bahwa dalam contoh tertentu, masalah dimulai di wilayah baris 8510. Jika Anda meminta
txt.GetLineText (8510) , maka "8510" kembali. Untuk 8511 - 8511, dan untuk 8512 - tiba-tiba, 8513.
Kami melihat implementasi
GetLineText () pada TextBox:

Kami melewatkan pemeriksaan di baris pertama dan melihat panggilan
GetStartPositionOfLine () . Tampaknya masalahnya ada pada fungsi ini, karena untuk garis yang salah posisi yang salah pada awal garis harus dikembalikan.
Kami memanggil kode kami:
var o00 = txt.InvokeMethod("GetStartPositionOfLine", 8510); var o01 = txt.InvokeMethod("GetStartPositionOfLine", 8511); var o02 = txt.InvokeMethod("GetStartPositionOfLine", 8512);
Dan kebenaran - offset dari objek pertama (awal garis 8510) diindikasikan sebagai 49950 karakter, untuk objek kedua - 49956, dan yang ketiga - 49968. Di antara dua 6 karakter pertama, dan di antara 12. karakter berikutnya - Disorder - ini adalah baris yang hilang.
Masuk ke dalam
GetStartPositionOfLine () :

Sekali lagi kami melewatkan pemeriksaan awal dan melihat tindakan nyata. Pertama, titik dihitung, yang harus menuju ke baris dengan nomor
lineIndex . Ketinggian semua garis diambil dan setengah tinggi garis ditambahkan untuk mencapai pusatnya.
Kami tidak melihat ini.
Pengaturan Vertikal dan ini.
Pengaturan Horizontal - mereka adalah nol.
Kami mempertimbangkan dalam kode kami:
var lineHeight = (double) txt.InvokeMethod("GetLineHeight", null); var y0 = lineHeight * (double)8510 + lineHeight / 2.0 - txt.VerticalOffset; var y1 = lineHeight * (double)8511 + lineHeight / 2.0 - txt.VerticalOffset; var y2 = lineHeight * (double)8512 + lineHeight / 2.0 - txt.VerticalOffset;
Nilai-nilai itu masuk akal, berkorelasi dengan logika, semuanya teratur. Kami melangkah lebih jauh di sepanjang kode
GetStartPositionOfLine () - kami tertarik pada baris yang bermakna berikut (yang pertama di dalam kondisi), yang terlihat seperti buaya dan diakhiri dengan panggilan ke
GetTextPositionFromPoint () .
Kami membuka tantangan dan menariknya melalui refleksi. Harap perhatikan bahwa beberapa antarmuka tidak tersedia bagi kami karena batasan visibilitas, jadi kami harus merujuknya menggunakan Refleksi yang sama.
var renderScope = (txt.GetFieldValue<FrameworkElement>("_renderScope") as IServiceProvider);
Objek yang dihasilkan menunjukkan semua offset yang sama - 49950, 49956, 49568. Kita masuk lebih dalam ke implementasi
GetTextPositionFromPoint () di dalam TextBoxView.

Di,
GetLineIndexFromPoint () terlihat menjanjikan. Panggil kode Anda.
var o20 = textView.InvokeMethod("GetLineIndexFromPoint", new Point(-txt.HorizontalOffset, y0), true); var o21 = textView.InvokeMethod("GetLineIndexFromPoint", new Point(-txt.HorizontalOffset, y1), true); var o22 = textView.InvokeMethod("GetLineIndexFromPoint", new Point(-txt.HorizontalOffset, y2), true);
Kami mendapatkan 8510, 8511 dan 8513 - bingo! Untuk implementasi:

Bahkan dengan mata telanjang, jelas bahwa ini adalah pencarian biner.
_lineMetrics - daftar karakteristik string (mulai, panjang, lebar perbatasan). Saya menggosok pena dengan gembira - Saya pikir karena sering terjadi, mereka lupa menempel
+1 di suatu tempat atau meletakkan
> bukannya
> = . Salin fungsi ke kode dan lakukan debug. Karena sifat tertutup dari tipe
_lineMetrics ,
kami menariknya melalui refleksi,
_lineTinggi yang kami dapatkan sebelumnya. Total:
var lm = textView.GetFieldValue<object>("_lineMetrics"); var c = (int)lm.InvokeMethod("get_Count"); var lineMetrics = new List<Tuple<int,int,int,double>>(); for (var i = 0; i < c; i++) { var arr_o = lm.InvokeMethod("get_Item", i); var contLength = arr_o.GetFieldValue<int>("_contentLength"); var length = arr_o.GetFieldValue<int>("_length"); var offset = arr_o.GetFieldValue<int>("_offset"); var width = arr_o.GetFieldValue<double>("_width"); lineMetrics.Add(new Tuple<int, int, int, double>(contLength, length, offset, width)); } var o30 = GetLineIndexFromPoint(lineMetrics, lineHeight, new Point(-txt.HorizontalOffset, y0), true); var o31 = GetLineIndexFromPoint(lineMetrics, lineHeight, new Point(-txt.HorizontalOffset, y1), true); var o32 = GetLineIndexFromPoint(lineMetrics, lineHeight, new Point(-txt.HorizontalOffset, y2), true); private int GetLineIndexFromPoint(List<Tuple<int, int, int, double>> lm, double _lineHeight, Point point, bool snapToText) { if (point.Y < 0.0) return !snapToText ? -1 : 0; if (point.Y >= _lineHeight * (double)lm.Count) { if (!snapToText) return -1; return lm.Count - 1; } int index = -1; int num1 = 0; int num2 = lm.Count; while (num1 < num2) { index = num1 + (num2 - num1) / 2; var lineMetric = lm[index]; double num3 = _lineHeight * (double)index; if (point.Y < num3) num2 = index; else if (point.Y >= num3 + _lineHeight) { num1 = index + 1; } else { if (!snapToText && (point.X < 0.0 || point.X >= lineMetric.Item4)) { index = -1; break; } break; } } if (num1 >= num2) return -1; return index; }
Kami tidak bisa melakukan debugging. o30, o31 dan o32 masing-masing adalah 8510, 8511 dan 8512. Seperti seharusnya! Tapi O20, O21 dan O22 tidak setuju dengan mereka. Bagaimana bisa begitu? Kami hampir tidak mengubah kode. Hampir? Dan inilah wawasannya.
var lh = textView.GetFieldValue<double>("_lineHeight");

Itulah alasannya - perbedaannya adalah
0,0009375 . Selain itu, jika kami memperkirakan akumulasi kesalahan - kami mengalikannya dengan 8511, kami mendapatkan 7.9790625. Ini hanya sekitar setengah dari garis Tinggi, dan oleh karena itu, ketika menghitung koordinat, titik tersebut terbang di luar garis yang diinginkan dan jatuh pada baris berikutnya. Variabel yang sama (dalam arti) dihitung dengan dua cara yang berbeda dan, tiba-tiba, tidak cocok.
Tentang ini saya memutuskan untuk berhenti. Mungkin untuk benar-benar memahami mengapa ketinggian kolom ternyata berbeda, tetapi saya tidak melihat banyak gunanya. Sangat diragukan bahwa Microsoft akan memperbaikinya, jadi kami melihat tongkat penyangga untuk mengatasinya. Reflection-crutch - atur
_lineHeigh yang benar
di salah satu atau di tempat lain. Kedengarannya bodoh, mungkin lambat dan kemungkinan besar tidak bisa diandalkan. Atau Anda dapat mempertahankan rangkaian garis Anda sendiri, sejajar dengan TextBox, dan mengambil garis darinya, karena mendapatkan nomor baris pada posisi kursor berfungsi dengan benar.
Kesimpulan
Dari programmer pemula Anda sering dapat mendengar sesuatu tentang bug di kompiler atau komponen standar. Pada kenyataannya, mereka tidak begitu umum, tetapi masih tidak ada yang aman dari mereka. Jangan takut untuk melihat ke dalam alat yang Anda butuhkan - itu menarik dan menarik.
Tulis kode yang bagus!
Artikel blog lainnya
â
Pembelajaran Mesin dengan Keamanan MenyerangTidak ada mobil yang bisa menggantikan saya. Muhaha ha. Saya harap begitu.
â
Di mana memasukkan tanda kutip di IPv6Orang-orang tahu di mana dan apa yang harus didorong untuk membuatnya menjadi baik. Setelah kata-kata seperti itu, mereka akan menggantikan saya dengan robot, pasti. Menikah! UHF!