Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for VB.NET cached delegate initialization with closures #2844

Conversation

ElektroKill
Copy link
Contributor

Link to issue(s) this covers:
#2199

Problem

The CachedDelegateInitialization transform did not fully support VB cached delegate initialization. The pattern emitted when an anonymous function uses a local from a display class/closure was not supported. This can be seen by decompiling Class5.method_0() from the sample provided in the aforementioned issue report. This can be seen in the code inside the first try region.

Before:

private void method_0(ClassHttpRequest classHttpRequest_0, string string_0)
{
	string text = "";
	if (!bool_1)
	{
		text = "請先進行登入認證~";
		return;
	}
	ClassHttpRequest val = classHttpRequest_0;
	_Closure$__35-0 arg = new _Closure$__35-0(arg);
	arg.$VB$Local_totp_code = "";
	arg.$VB$Local_frm = new Form();
	Form $VB$Local_frm = arg.$VB$Local_frm;
	_Closure$__35-1 arg2 = default(_Closure$__35-1);
	_Closure$__35-1 CS$<>8__locals0 = new _Closure$__35-1(arg2);
	CS$<>8__locals0.$VB$NonLocal_$VB$Closure_2 = arg;
	$VB$Local_frm.ShowIcon = false;
	$VB$Local_frm.StartPosition = FormStartPosition.CenterScreen;
	$VB$Local_frm.Text = "提示";
	$VB$Local_frm.Size = new Size(new Point(100, 120));
	$VB$Local_frm.FormBorderStyle = FormBorderStyle.FixedSingle;
	$VB$Local_frm.MaximizeBox = false;
	$VB$Local_frm.MinimizeBox = false;
	Label label = new Label();
	$VB$Local_frm.Controls.Add(label);
	label.Dock = DockStyle.Top;
	label.BringToFront();
	label.Text = "請打開手機露天代碼產生器應用程式,並輸入驗證碼。";
	label.AutoSize = false;
	label.Height = checked(label.Font.Height * 3);
	CS$<>8__locals0.$VB$Local_TextBox1 = new TextBox();
	$VB$Local_frm.Controls.Add(CS$<>8__locals0.$VB$Local_TextBox1);
	TextBox $VB$Local_TextBox = CS$<>8__locals0.$VB$Local_TextBox1;
	$VB$Local_TextBox.Dock = DockStyle.Top;
	$VB$Local_TextBox.BringToFront();
	Button button = new Button();
	$VB$Local_frm.Controls.Add(button);
	button.Dock = DockStyle.Top;
	button.BringToFront();
	button.Height = 30;
	button.Text = "確認";
	button.Click += [SpecialName] (object a0, EventArgs a1) =>
	{
		CS$<>8__locals0._Lambda$__0();
	};
	$VB$Local_frm.AcceptButton = button;
	IEnumerator enumerator = default(IEnumerator);
	try
	{
		enumerator = Application.OpenForms.GetEnumerator();
		if (enumerator.MoveNext())
		{
			((Form)enumerator.Current).Invoke((CS$<>8__locals0.$VB$NonLocal_$VB$Closure_2.$I1 == null) ? (CS$<>8__locals0.$VB$NonLocal_$VB$Closure_2.$I1 = [SpecialName] () =>
			{
				CS$<>8__locals0.$VB$NonLocal_$VB$Closure_2.$VB$Local_frm.ShowDialog();
			}) : CS$<>8__locals0.$VB$NonLocal_$VB$Closure_2.$I1);
		}
	}
	finally
	{
		if (enumerator is IDisposable)
		{
			(enumerator as IDisposable).Dispose();
		}
	}
	if (arg.$VB$Local_totp_code.Length < 1)
	{
		text += "~請輸入驗證碼!";
	}
	else
	{
		string text2 = "https://member.ruten.com.tw/totp/do_login_validation.php";
		string text3 = "totp_code=" + arg.$VB$Local_totp_code + "&remember_code=true";
		val.Referer = text2;
		string hTML = val.GetHTML((MethodHTTP)1, text2, (object)text3);
		ClassMy.Savefile(string.Format(Application.StartupPath + "\\test\\login\\ruten\\" + string_0 + "\\{0}.htm", "do_login_validation"), hTML);
		string text4;
		if (hTML.Trim().Length > 0 && hTML.Trim().StartsWith("{\"is_success\":true,"))
		{
			text4 = "~認證成功!";
			List<string> list = new List<string>();
			IEnumerator enumerator2 = default(IEnumerator);
			try
			{
				enumerator2 = val.CookieContainerclass.GetCookies(new Uri("https://mybid.ruten.com.tw/upload/step1.htm")).GetEnumerator();
				while (enumerator2.MoveNext())
				{
					Cookie cookie = (Cookie)enumerator2.Current;
					list.Add(cookie.Name + "=" + cookie.Value);
				}
			}
			finally
			{
				if (enumerator2 is IDisposable)
				{
					(enumerator2 as IDisposable).Dispose();
				}
			}
			if (list.Count > 0)
			{
				ClassMy.Savefile(Class4.string_0 + "\\" + string_0 + ".txt", string.Join(";", list.ToArray()));
			}
		}
		else
		{
			text4 = "~認證失敗!";
			text4 = ((!hTML.Contains("VALIDATE_FAILED")) ? (text4 + "\r\n" + Strings.Left(ClassMy.CheckHTML(hTML).ToString().Trim(), 100)) : (text4 + "\r\n可能驗證碼超時,請重試!"));
		}
		text += text4;
	}
	val = null;
}

After:

private void method_0(ClassHttpRequest classHttpRequest_0, string string_0)
{
	string text = "";
	if (!bool_1)
	{
		text = "請先進行登入認證~";
		return;
	}
	ClassHttpRequest val = classHttpRequest_0;
	_Closure$__35-0 arg = new _Closure$__35-0(arg);
	arg.$VB$Local_totp_code = "";
	arg.$VB$Local_frm = new Form();
	Form $VB$Local_frm = arg.$VB$Local_frm;
	_Closure$__35-1 arg2 = default(_Closure$__35-1);
	_Closure$__35-1 CS$<>8__locals0 = new _Closure$__35-1(arg2);
	CS$<>8__locals0.$VB$NonLocal_$VB$Closure_2 = arg;
	$VB$Local_frm.ShowIcon = false;
	$VB$Local_frm.StartPosition = FormStartPosition.CenterScreen;
	$VB$Local_frm.Text = "提示";
	$VB$Local_frm.Size = new Size(new Point(100, 120));
	$VB$Local_frm.FormBorderStyle = FormBorderStyle.FixedSingle;
	$VB$Local_frm.MaximizeBox = false;
	$VB$Local_frm.MinimizeBox = false;
	Label label = new Label();
	$VB$Local_frm.Controls.Add(label);
	label.Dock = DockStyle.Top;
	label.BringToFront();
	label.Text = "請打開手機露天代碼產生器應用程式,並輸入驗證碼。";
	label.AutoSize = false;
	label.Height = checked(label.Font.Height * 3);
	CS$<>8__locals0.$VB$Local_TextBox1 = new TextBox();
	$VB$Local_frm.Controls.Add(CS$<>8__locals0.$VB$Local_TextBox1);
	TextBox $VB$Local_TextBox = CS$<>8__locals0.$VB$Local_TextBox1;
	$VB$Local_TextBox.Dock = DockStyle.Top;
	$VB$Local_TextBox.BringToFront();
	Button button = new Button();
	$VB$Local_frm.Controls.Add(button);
	button.Dock = DockStyle.Top;
	button.BringToFront();
	button.Height = 30;
	button.Text = "確認";
	button.Click += [SpecialName] (object a0, EventArgs a1) =>
	{
		CS$<>8__locals0._Lambda$__0();
	};
	$VB$Local_frm.AcceptButton = button;
	IEnumerator enumerator = default(IEnumerator);
	try
	{
		enumerator = Application.OpenForms.GetEnumerator();
		if (enumerator.MoveNext())
		{
			((Form)enumerator.Current).Invoke((VB$AnonymousDelegate_0)([SpecialName] () =>
			{
				CS$<>8__locals0.$VB$NonLocal_$VB$Closure_2.$VB$Local_frm.ShowDialog();
			}));
		}
	}
	finally
	{
		if (enumerator is IDisposable)
		{
			(enumerator as IDisposable).Dispose();
		}
	}
	if (arg.$VB$Local_totp_code.Length < 1)
	{
		text += "~請輸入驗證碼!";
	}
	else
	{
		string text2 = "https://member.ruten.com.tw/totp/do_login_validation.php";
		string text3 = "totp_code=" + arg.$VB$Local_totp_code + "&remember_code=true";
		val.Referer = text2;
		string hTML = val.GetHTML((MethodHTTP)1, text2, (object)text3);
		ClassMy.Savefile(string.Format(Application.StartupPath + "\\test\\login\\ruten\\" + string_0 + "\\{0}.htm", "do_login_validation"), hTML);
		string text4;
		if (hTML.Trim().Length > 0 && hTML.Trim().StartsWith("{\"is_success\":true,"))
		{
			text4 = "~認證成功!";
			List<string> list = new List<string>();
			IEnumerator enumerator2 = default(IEnumerator);
			try
			{
				enumerator2 = val.CookieContainerclass.GetCookies(new Uri("https://mybid.ruten.com.tw/upload/step1.htm")).GetEnumerator();
				while (enumerator2.MoveNext())
				{
					Cookie cookie = (Cookie)enumerator2.Current;
					list.Add(cookie.Name + "=" + cookie.Value);
				}
			}
			finally
			{
				if (enumerator2 is IDisposable)
				{
					(enumerator2 as IDisposable).Dispose();
				}
			}
			if (list.Count > 0)
			{
				ClassMy.Savefile(Class4.string_0 + "\\" + string_0 + ".txt", string.Join(";", list.ToArray()));
			}
		}
		else
		{
			text4 = "~認證失敗!";
			text4 = ((!hTML.Contains("VALIDATE_FAILED")) ? (text4 + "\r\n" + Strings.Left(ClassMy.CheckHTML(hTML).ToString().Trim(), 100)) : (text4 + "\r\n可能驗證碼超時,請重試!"));
		}
		text += text4;
	}
	val = null;
}

Smaller code to reproduce the issue:

Dim t As String = "hello"
Dim collection As List(Of String) = New List(Of String)
For Each element In collection.Where(Function(x) x = t)
	Console.WriteLine(element)
Next

Solution

Implement support for the pattern seen in this sample.
A unit test was not added as ILSpy does not currently support VB.NET closures/display classes as they differ in structure from the C# ones. This lack of support makes it impossible to create pretty C# decompilation while testing this pattern.

@icsharpcode icsharpcode deleted a comment from loveslikey Nov 26, 2022
@ElektroKill
Copy link
Contributor Author

In the latest commit, I fixed support for cached delegate initialization detection using the pattern from CachedDelegateInitializationVB . It turns out that the stack slot which is part of the pattern should not be restricted to only one load.

Test code:

Public Shared Function UseDelegate() As Func(Of Integer)
	Dim del As Func(Of Integer) = Function() 2
	del(2)
	Return del
End Function

ILAst before CachedDelegateInitialization transform:

if (logic.not(comp.o(ldsfld $I1-0 != ldnull))) Block IL_0000 {
	stloc S_0(stsfld $I1-0(newobj Func..ctor(ldsfld $I, ldftn _Lambda$__1-0)))
} else Block IL_0007 {
	stloc S_0(ldsfld $I1-0)
}
callvirt Invoke(ldloc S_0)
leave IL_0005 (ldloc S_0)

S_0 has more than one usage!

@ElektroKill
Copy link
Contributor Author

Applied the fix mentioned in the comment above to the newly added CachedDelegateInitializationVBWithClosure pattern too.

@ElektroKill
Copy link
Contributor Author

ElektroKill commented Nov 27, 2022

The latest commit adds support for a rare case of cached delegate initialization generated by the VB compiler when an anonymous method that does not utilize captured variables and is directly returned.

VB code:

Public Shared Function ReturnDelegate() As Func(Of Integer)
	Return Function() 2
End Function

IL:

IL_0000: ldsfld class [mscorlib]System.Func`1<int32> DelegateConstruction/_Closure$____0::'$I0-0'
IL_0005: brfalse.s IL_000d

IL_0007: ldsfld class [mscorlib]System.Func`1<int32> DelegateConstruction/_Closure$____0::'$I0-0'
IL_000c: ret

IL_000d: ldsfld class DelegateConstruction/_Closure$____0 DelegateConstruction/_Closure$____0::$I
IL_0012: ldftn instance int32 DelegateConstruction/_Closure$____0::'_Lambda$__0-0'()
IL_0018: newobj instance void class [mscorlib]System.Func`1<int32>::.ctor(object, native int)
IL_001d: dup
IL_001e: stsfld class [mscorlib]System.Func`1<int32> DelegateConstruction/_Closure$____0::'$I0-0'
IL_0023: ret

ILAst (before cached delegate initialization transformation):

if (comp.o(ldsfld $I0-0 != ldnull)) leave IL_0005 (ldsfld $I0-0)
leave IL_0005 (stsfld $I0-0(newobj Func..ctor(ldsfld $I, ldftn _Lambda$__0-0)))

@siegfriedpammer
Copy link
Member

Thank you very much for your contribution!

@siegfriedpammer siegfriedpammer merged commit 3082f0d into icsharpcode:master Nov 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants