-
-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathNonCapturingLambdaProcessorTests.cs
175 lines (146 loc) · 8.23 KB
/
NonCapturingLambdaProcessorTests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
using System;
using System.Collections;
using System.Linq;
using Cecilifier.Core.AST;
using Cecilifier.Core.Misc;
using Cecilifier.Core.Variables;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NUnit.Framework;
namespace Cecilifier.Core.Tests.Tests.Unit
{
[TestFixture]
public class NonCapturingLambdaProcessorTests
{
[TestCase("class Foo { System.Func<string, int> Bar() => s => s.Length; }", TestName = "Simple Lambda Expression")]
[TestCase("class Foo { System.Func<string, int> Bar() => (s) => s.Length; }", TestName = "Parenthesized Lambda Expression")]
[TestCase("class Foo { System.Func<int> Bar() => () => 42; }", TestName = "No Parameters")]
public void BasicTests(string source)
{
var context = RunProcessorOn(source);
Assert.That(context.Output, Contains.Substring("/Synthetic method for lambda expression"));
}
[TestCase("class Foo { void Bar(int fromParentMethod) { System.Func<int, int> fi = i => i + fromParentMethod ; } }", "//Anonymous method / lambda that captures context are not supported. Node 'i => i + fromParentMethod' captures fromParentMethod", TestName = "Capture Parameter")]
[TestCase("class Foo { int field; System.Func<int, int> Bar() => i => i + field; }", "//Anonymous method / lambda that captures context are not supported. Node 'i => i + field' captures field", TestName = "Capture Field")]
[TestCase("class Foo { void Bar() { int local = 10; System.Func<int, int> fi = i => i + local ; } }", "//Anonymous method / lambda that captures context are not supported. Node 'i => i + local' captures local", TestName = "Capture Local")]
public void Captured_Variables_AreDetected(string source, string expected)
{
var context = RunProcessorOn(source);
Assert.That(context.Output, Contains.Substring(expected));
Assert.That(context.Output, Does.Not.Contains("/Synthetic method for lambda expression"));
}
[Test]
public void FalsePositives_Captured_Variables_AreNotReported()
{
var context = RunProcessorOn("class Foo { void Bar() { System.Func<int, int> fi = i => { int local = 10; return i + local ; }; } }");
Assert.That(context.Output, Does.Not.Contains("//Lambdas that captures context are not supported"));
Assert.That(context.Output, Contains.Substring("/Synthetic method for lambda expression"));
}
[TestCase("class Foo { delegate int D(int i); void Bar() { D fi = i => i + 1; } }", TestName = "Non Generic Delegate")]
[TestCase("class Foo { delegate T D<T>(T i); void Bar() { D<int> fi = i => i + 1; } }", TestName = "Generic Delegate")]
public void Conversion_FromDelegates_OtherThanFuncAndAction_AreReported(string source)
{
var context = RunProcessorOn(source);
Assert.That(context.Output, Does.Not.Contains("//Synthetic method for lambda expression: i => i + 1"));
Assert.That(context.Output, Contains.Substring("//Lambda to delegates conversion is only supported for Func<> and Action<>"));
}
[TestCase("using System; class Foo { void M(Func<int, int> a) { M(x => x + 1); } }", TestName = "Expression")]
[TestCase("using System; class Foo { void M(Func<int, int> a) { M(x => { return x + 1; } ); } }", TestName = "Statement")]
public void LambdaBodyIsProcessed(string source)
{
var context = RunProcessorOn(source);
Assert.That(
context.Output,
Does.Match(@"(il_lambda_.+\.Emit\(OpCodes\.)Ldarg_0\);\s+" +
@"\1Ldc_I4, 1\);\s+" +
@"\1Add\);\s+" +
@"\1Ret\);"));
}
[TestCaseSource(nameof(Issue_176_TestScenarios))]
public void Issue_176(string lambdaDeclaration, string expectedSnippet)
{
var context = RunProcessorOn($@"using System; class Foo {{ void Bar() {{ {lambdaDeclaration} }} }}");
Assert.That(context.Output, Contains.Substring(expectedSnippet));
}
static IEnumerable Issue_176_TestScenarios()
{
return new[]
{
new TestCaseData(
@"Action<int> a = i => { int l = 42; };",
@"var l_l_3 = new VariableDefinition(assembly.MainModule.TypeSystem.Int32);
m_lambda_0_55_0.Body.Variables.Add(l_l_3);
il_lambda_0_55_2.Emit(OpCodes.Ldc_I4, 42);
il_lambda_0_55_2.Emit(OpCodes.Stloc, l_l_3);
il_lambda_0_55_2.Emit(OpCodes.Ret);").SetName("Lambda Without Explicit Return"),
new TestCaseData(
@"Func<int, int> f = i => { int l = i; return l; };",
@"//int l = i;
var l_l_3 = new VariableDefinition(assembly.MainModule.TypeSystem.Int32);
m_lambda_0_58_0.Body.Variables.Add(l_l_3);
il_lambda_0_58_2.Emit(OpCodes.Ldarg_0);
il_lambda_0_58_2.Emit(OpCodes.Stloc, l_l_3);
//return l;
il_lambda_0_58_2.Emit(OpCodes.Ldloc, l_l_3);
il_lambda_0_58_2.Emit(OpCodes.Ret);").SetName("Local Variable Initialization"),
new TestCaseData(
@"Func<int, int> f = i => { int l; l = i; return l; };",
@"//int l;
var l_l_3 = new VariableDefinition(assembly.MainModule.TypeSystem.Int32);
m_lambda_0_58_0.Body.Variables.Add(l_l_3);
//l = i;
il_lambda_0_58_2.Emit(OpCodes.Ldarg_0);
il_lambda_0_58_2.Emit(OpCodes.Stloc, l_l_3);
//return l;
il_lambda_0_58_2.Emit(OpCodes.Ldloc, l_l_3);
il_lambda_0_58_2.Emit(OpCodes.Ret);").SetName("Local Variable Assignment"),
new TestCaseData(
@"Func<int, int> f = i => { int l = i + 1; return l; };",
@"//int l = i + 1;
var l_l_3 = new VariableDefinition(assembly.MainModule.TypeSystem.Int32);
m_lambda_0_58_0.Body.Variables.Add(l_l_3);
il_lambda_0_58_2.Emit(OpCodes.Ldarg_0);
il_lambda_0_58_2.Emit(OpCodes.Ldc_I4, 1);
il_lambda_0_58_2.Emit(OpCodes.Add);
il_lambda_0_58_2.Emit(OpCodes.Stloc, l_l_3);
//return l;
il_lambda_0_58_2.Emit(OpCodes.Ldloc, l_l_3);
il_lambda_0_58_2.Emit(OpCodes.Ret);").SetName("Local Variable Initialization With Expression"),
new TestCaseData(
@"Func<int, int> f = i => { int l = i; l = l + 1; return l; };",
@"//int l = i;
var l_l_3 = new VariableDefinition(assembly.MainModule.TypeSystem.Int32);
m_lambda_0_58_0.Body.Variables.Add(l_l_3);
il_lambda_0_58_2.Emit(OpCodes.Ldarg_0);
il_lambda_0_58_2.Emit(OpCodes.Stloc, l_l_3);
//l = l + 1;
il_lambda_0_58_2.Emit(OpCodes.Ldloc, l_l_3);
il_lambda_0_58_2.Emit(OpCodes.Ldc_I4, 1);
il_lambda_0_58_2.Emit(OpCodes.Add);
il_lambda_0_58_2.Emit(OpCodes.Stloc, l_l_3);
//return l;
il_lambda_0_58_2.Emit(OpCodes.Ldloc, l_l_3);
il_lambda_0_58_2.Emit(OpCodes.Ret);").SetName("Local Variable In Expression"),
};
}
private static CecilifierContext RunProcessorOn(string source)
{
var syntaxTree = CSharpSyntaxTree.ParseText(source);
var comp = CSharpCompilation.Create(null, new[] { syntaxTree }, new[] { MetadataReference.CreateFromFile(typeof(Func<>).Assembly.Location) }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
var diagnostics = comp.GetDiagnostics();
var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error);
if (errors.Any())
throw new Exception(errors.Aggregate("", (acc, curr) => acc + curr.GetMessage() + Environment.NewLine));
var context = new CecilifierContext(comp.GetSemanticModel(syntaxTree), new CecilifierOptions(), -1);
DefaultParameterExtractorVisitor.Initialize(context);
UsageVisitor.ResetInstance();
context.DefinitionVariables.RegisterNonMethod("Foo", "field", VariableMemberKind.Field, "fieldVar"); // Required for Field tests
NonCapturingLambdaProcessor.InjectSyntheticMethodsForNonCapturingLambdas(
context,
syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().SingleOrDefault(),
"");
return context;
}
}
}