Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit cc4cc40

Browse files
JonHannaOmarTawfik
authored andcommitted
Fill some test gaps in S.L.Expression's LambdaCompiler (#25909)
* Test invoking delegates held in properties or fields of dynamic objects * Test tail calling method-based && and ||. * Tests for sparse switches. * Tail call switch test. * Test ambiguous jump to labels before jump. * Test ambiguous jump to labels either side of the jump. * Tests for jumps out of try or catch blocks * Test for jumping between switch cases. * Test for compilation of nop blocks. Blocks that only contain empty expressions or other similarly empty blocks should be elided from compilation in some cases. Test this. * Conditional tests with equality with constant nulls. * Test invoked inner lambda writting to property. * Test attempt to tail-call a method with a ref parameter * Test tail-calling from within a block. * Remove accidentally added whitespace
1 parent 2bc4147 commit cc4cc40

File tree

10 files changed

+463
-1
lines changed

10 files changed

+463
-1
lines changed

src/System.Linq.Expressions/tests/BinaryOperators/Logical/BinaryLogicalTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,27 @@ public static void AndAlso_UserDefinedOperator(int leftValue, int rightValue, in
153153
Assert.Equal(calledMethod ? 1 : 0, left.OperatorCallCount);
154154
}
155155

156+
[Theory]
157+
[PerCompilationType(nameof(AndAlso_TestData))]
158+
public static void AndAlso_UserDefinedOperatorTailCall(int leftValue, int rightValue, int expectedValue, bool calledMethod, bool useInterpreter)
159+
{
160+
TrueFalseClass left = new TrueFalseClass(leftValue);
161+
TrueFalseClass right = new TrueFalseClass(rightValue);
162+
163+
BinaryExpression expression = Expression.AndAlso(Expression.Constant(left), Expression.Constant(right));
164+
Func<TrueFalseClass> lambda = Expression.Lambda<Func<TrueFalseClass>>(expression, true).Compile(useInterpreter);
165+
Assert.Equal(expectedValue, lambda().Value);
166+
167+
// AndAlso only evaluates the false operator of left
168+
Assert.Equal(0, left.TrueCallCount);
169+
Assert.Equal(1, left.FalseCallCount);
170+
Assert.Equal(0, right.TrueCallCount);
171+
Assert.Equal(0, right.FalseCallCount);
172+
173+
// AndAlso only evaluates the operator if left is not false
174+
Assert.Equal(calledMethod ? 1 : 0, left.OperatorCallCount);
175+
}
176+
156177
[Theory]
157178
[ClassData(typeof(CompilationTypes))]
158179
public static void AndAlso_UserDefinedOperator_HasMethodNotOperator(bool useInterpreter)
@@ -214,6 +235,27 @@ public static void OrElse_UserDefinedOperator(int leftValue, int rightValue, int
214235
Assert.Equal(calledMethod ? 1 : 0, left.OperatorCallCount);
215236
}
216237

238+
[Theory]
239+
[PerCompilationType(nameof(OrElse_TestData))]
240+
public static void OrElse_UserDefinedOperatorTailCall(int leftValue, int rightValue, int expectedValue, bool calledMethod, bool useInterpreter)
241+
{
242+
TrueFalseClass left = new TrueFalseClass(leftValue);
243+
TrueFalseClass right = new TrueFalseClass(rightValue);
244+
245+
BinaryExpression expression = Expression.OrElse(Expression.Constant(left), Expression.Constant(right));
246+
Func<TrueFalseClass> lambda = Expression.Lambda<Func<TrueFalseClass>>(expression, true).Compile(useInterpreter);
247+
Assert.Equal(expectedValue, lambda().Value);
248+
249+
// OrElse only evaluates the true operator of left
250+
Assert.Equal(1, left.TrueCallCount);
251+
Assert.Equal(0, left.FalseCallCount);
252+
Assert.Equal(0, right.TrueCallCount);
253+
Assert.Equal(0, right.FalseCallCount);
254+
255+
// OrElse only evaluates the operator if left is not true
256+
Assert.Equal(calledMethod ? 1 : 0, left.OperatorCallCount);
257+
}
258+
217259
[Theory]
218260
[ClassData(typeof(CompilationTypes))]
219261
public static void OrElse_UserDefinedOperator_HasMethodNotOperator(bool useInterpreter)

src/System.Linq.Expressions/tests/Block/BlockTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,5 +225,29 @@ public static void ToStringTest()
225225
BlockExpression e3 = Expression.Block(new[] { Expression.Parameter(typeof(int), "x"), Expression.Parameter(typeof(int), "y") }, Expression.Empty());
226226
Assert.Equal("{var x;var y; ... }", e3.ToString());
227227
}
228+
229+
[Fact]
230+
public static void InsignificantBlock()
231+
{
232+
Expression<Action> nop = Expression.Lambda<Action>(
233+
Expression.Block(
234+
Expression.Block(Expression.Empty(), Expression.Default(typeof(void))),
235+
Expression.Block(Expression.Empty(), Expression.Default(typeof(void))),
236+
Expression.Block(Expression.Empty(), Expression.Default(typeof(void))),
237+
Expression.Block(Expression.Empty(), Expression.Default(typeof(void)))));
238+
239+
nop.Verify(
240+
@".method void ::lambda_method(class [System.Linq.Expressions]System.Runtime.CompilerServices.Closure)
241+
{
242+
.maxstack 0
243+
IL_0000: ret
244+
}",
245+
@"object lambda_method(object[])
246+
{
247+
.locals 0
248+
.maxstack 0
249+
.maxcontinuation 0
250+
}");
251+
}
228252
}
229253
}

src/System.Linq.Expressions/tests/Call/CallTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,20 @@ public static void CallByRefMutableStructPropertyWriteBack(bool useInterpreter)
214214
[Theory]
215215
[ClassData(typeof(CompilationTypes))]
216216
public static void CallByRefMutableStructIndexWriteBack(bool useInterpreter)
217+
{
218+
// Should not produce tail-call, but should still succeed
219+
ParameterExpression p = Expression.Parameter(typeof(Mutable));
220+
IndexExpression x = Expression.MakeIndex(p, typeof(Mutable).GetProperty("Item"), new[] { Expression.Constant(0) });
221+
MethodCallExpression call = Expression.Call(typeof(Methods).GetMethod("ByRef"), x);
222+
Action<Mutable> act = Expression.Lambda<Action<Mutable>>(call, true, p).Compile(useInterpreter);
223+
224+
Mutable m = new Mutable { X = 41 };
225+
act(m);
226+
}
227+
228+
[Theory]
229+
[ClassData(typeof(CompilationTypes))]
230+
public static void CallByRefAttemptTailCall(bool useInterpreter)
217231
{
218232
ParameterExpression p = Expression.Parameter(typeof(Mutable));
219233
IndexExpression x = Expression.MakeIndex(p, typeof(Mutable).GetProperty("Item"), new[] { Expression.Constant(0) });

src/System.Linq.Expressions/tests/Conditional/ConditionalTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,50 @@ public static void ToStringTest()
274274
Assert.Equal("IIF(a, b, default(Void))", e2.ToString());
275275
}
276276

277+
[Theory, ClassData(typeof(CompilationTypes))]
278+
public void TurnOnNullableComparedWithConstantNull(bool useInterpreter)
279+
{
280+
Func<int> func = Expression.Lambda<Func<int>>(
281+
Expression.Condition(
282+
Expression.Equal(Expression.Constant(2, typeof(int?)), Expression.Default(typeof(int?))),
283+
Expression.Constant(1), Expression.Constant(2)))
284+
.Compile(useInterpreter);
285+
Assert.Equal(2, func());
286+
}
287+
288+
[Theory, ClassData(typeof(CompilationTypes))]
289+
public void TurnOnReferenceComparedWithConstantNull(bool useInterpreter)
290+
{
291+
Func<int> func = Expression.Lambda<Func<int>>(
292+
Expression.Condition(
293+
Expression.Equal(Expression.Constant(new object()), Expression.Default(typeof(object))),
294+
Expression.Constant(1), Expression.Constant(2)))
295+
.Compile(useInterpreter);
296+
Assert.Equal(2, func());
297+
}
298+
299+
[Theory, ClassData(typeof(CompilationTypes))]
300+
public void TurnOnConstantNullComparedWithNullable(bool useInterpreter)
301+
{
302+
Func<int> func = Expression.Lambda<Func<int>>(
303+
Expression.Condition(
304+
Expression.Equal(Expression.Default(typeof(int?)), Expression.Constant(2, typeof(int?))),
305+
Expression.Constant(1), Expression.Constant(2)))
306+
.Compile(useInterpreter);
307+
Assert.Equal(2, func());
308+
}
309+
310+
[Theory, ClassData(typeof(CompilationTypes))]
311+
public void TurnOnConstantNullComparedWithReference(bool useInterpreter)
312+
{
313+
Func<int> func = Expression.Lambda<Func<int>>(
314+
Expression.Condition(
315+
Expression.Equal(Expression.Default(typeof(object)), Expression.Constant(new object())),
316+
Expression.Constant(1), Expression.Constant(2)))
317+
.Compile(useInterpreter);
318+
Assert.Equal(2, func());
319+
}
320+
277321
private static IEnumerable<object[]> ConditionalValues()
278322
{
279323
yield return new object[] { true, "yes", "no", "yes" };

src/System.Linq.Expressions/tests/Dynamic/InvokeMemberBindingTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,5 +387,40 @@ public void OperationOnTwoObjectsDifferentTypesOfSameName(object x, object y)
387387
}
388388

389389
#endif
390+
391+
public class FuncWrapper<TResult>
392+
{
393+
public delegate void OutAction(out TResult arg);
394+
private Func<TResult> _delegate;
395+
396+
public Func<TResult> Delegate
397+
{
398+
get => _delegate;
399+
set
400+
{
401+
_delegate = value;
402+
OutDelegate = value == null ? default(OutAction) : (out TResult arg) =>
403+
{
404+
arg = value();
405+
};
406+
}
407+
}
408+
409+
public OutAction OutDelegate;
410+
}
411+
412+
[Fact]
413+
public void InvokeFuncMember()
414+
{
415+
dynamic d = new FuncWrapper<int>
416+
{
417+
Delegate = () => 2
418+
};
419+
int result = d.Delegate();
420+
Assert.Equal(2, result);
421+
result = 0;
422+
d.OutDelegate(out result);
423+
Assert.Equal(2, result);
424+
}
390425
}
391426
}

src/System.Linq.Expressions/tests/ExceptionHandling/ExceptionHandlingExpressions.cs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,97 @@ public void JumpIntoTry(bool useInterpreter)
12001200
Assert.Throws<InvalidOperationException>(() => tryExp.Compile(useInterpreter));
12011201
}
12021202

1203+
[Theory, ClassData(typeof(CompilationTypes))]
1204+
public void JumpOutOfTry(bool useInterpreter)
1205+
{
1206+
LabelTarget target = Expression.Label();
1207+
Expression<Action> tryExp = Expression.Lambda<Action>(
1208+
Expression.Block(
1209+
Expression.TryFinally(
1210+
Expression.Block(
1211+
Expression.Goto(target),
1212+
Expression.Throw(Expression.Constant(new TestException()))),
1213+
Expression.Empty()),
1214+
Expression.Label(target)));
1215+
Action act = tryExp.Compile(useInterpreter);
1216+
act();
1217+
}
1218+
1219+
[Theory, ClassData(typeof(CompilationTypes))]
1220+
public void JumpOutOfTryToPreviousLabel(bool useInterpreter)
1221+
{
1222+
LabelTarget skipStart = Expression.Label();
1223+
LabelTarget skipToEnd = Expression.Label(typeof(int));
1224+
LabelTarget backToStart = Expression.Label();
1225+
Expression<Func<int>> tryExp = Expression.Lambda<Func<int>>(
1226+
Expression.Block(
1227+
Expression.Goto(skipStart), Expression.Label(backToStart),
1228+
Expression.Return(skipToEnd, Expression.Constant(1)), Expression.Label(skipStart),
1229+
Expression.TryCatch(
1230+
Expression.Goto(backToStart), Expression.Catch(typeof(Exception), Expression.Empty())),
1231+
Expression.Return(skipToEnd, Expression.Constant(2)),
1232+
Expression.Label(skipToEnd, Expression.Constant(0))));
1233+
Func<int> func = tryExp.Compile(useInterpreter);
1234+
Assert.Equal(1, func());
1235+
}
1236+
1237+
[Theory, ClassData(typeof(CompilationTypes))]
1238+
public void JumpOutOfTryToPreviousLabelInOtherBlock(bool useInterpreter)
1239+
{
1240+
LabelTarget skipStart = Expression.Label();
1241+
LabelTarget skipToEnd = Expression.Label(typeof(int));
1242+
LabelTarget backToStart = Expression.Label();
1243+
Expression<Func<int>> tryExp = Expression.Lambda<Func<int>>(
1244+
Expression.Block(
1245+
Expression.Goto(skipStart),
1246+
Expression.Block(
1247+
Expression.Label(backToStart), Expression.Return(skipToEnd, Expression.Constant(1))),
1248+
Expression.Block(
1249+
Expression.Label(skipStart),
1250+
Expression.TryCatch(
1251+
Expression.Goto(backToStart), Expression.Catch(typeof(Exception), Expression.Empty())),
1252+
Expression.Return(skipToEnd, Expression.Constant(2))),
1253+
Expression.Label(skipToEnd, Expression.Constant(0))));
1254+
Func<int> func = tryExp.Compile(useInterpreter);
1255+
Assert.Equal(1, func());
1256+
}
1257+
1258+
[Theory, ClassData(typeof(CompilationTypes))]
1259+
public void JumpOutOfCatch(bool useIntepreter)
1260+
{
1261+
LabelTarget target = Expression.Label(typeof(int));
1262+
Expression<Func<int>> tryExp = Expression.Lambda<Func<int>>(
1263+
Expression.Block(
1264+
Expression.TryCatch(
1265+
Expression.Throw(Expression.Constant(new Exception())),
1266+
Expression.Catch(
1267+
typeof(Exception),
1268+
Expression.Block(
1269+
Expression.Goto(target, Expression.Constant(1)),
1270+
Expression.Throw(Expression.Constant(new Exception()))))),
1271+
Expression.Return(target, Expression.Constant(2)),
1272+
Expression.Label(target, Expression.Constant(0))));
1273+
Assert.Equal(1, tryExp.Compile(useIntepreter)());
1274+
}
1275+
1276+
[Theory, ClassData(typeof(CompilationTypes))]
1277+
public void JumpOutOfCatchToPreviousLabel(bool useIntepreter)
1278+
{
1279+
LabelTarget skipStart = Expression.Label();
1280+
LabelTarget skipToEnd = Expression.Label(typeof(int));
1281+
LabelTarget backToStart = Expression.Label();
1282+
Expression<Func<int>> tryExp = Expression.Lambda<Func<int>>(
1283+
Expression.Block(
1284+
Expression.Goto(skipStart), Expression.Label(backToStart),
1285+
Expression.Return(skipToEnd, Expression.Constant(1)), Expression.Label(skipStart),
1286+
Expression.TryCatch(
1287+
Expression.Throw(Expression.Constant(new Exception())),
1288+
Expression.Catch(typeof(Exception), Expression.Goto(backToStart))),
1289+
Expression.Return(skipToEnd, Expression.Constant(2)),
1290+
Expression.Label(skipToEnd, Expression.Constant(0))));
1291+
Assert.Equal(1, tryExp.Compile(useIntepreter)());
1292+
}
1293+
12031294
[Fact]
12041295
public void NonAssignableTryAndCatchTypes()
12051296
{

src/System.Linq.Expressions/tests/Goto/Goto.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,5 +288,27 @@ public void JumpInWithValue(bool useInterpreter)
288288
);
289289
Assert.Throws<InvalidOperationException>(() => exp.Compile(useInterpreter));
290290
}
291+
292+
[Theory, ClassData(typeof(CompilationTypes))]
293+
public void AmbiguousJumpBack(bool useInterpreter)
294+
{
295+
LabelTarget label = Expression.Label(typeof(void));
296+
BlockExpression block = Expression.Block(
297+
Expression.Block(Expression.Label(label)), Expression.Block(Expression.Label(label)),
298+
Expression.Block(Expression.Block(Expression.Goto(label))));
299+
Expression<Action> exp = Expression.Lambda<Action>(block);
300+
Assert.Throws<InvalidOperationException>(() => exp.Compile(useInterpreter));
301+
}
302+
303+
[Theory, ClassData(typeof(CompilationTypes))]
304+
public void AmbiguousJumpSplit(bool useInterpreter)
305+
{
306+
LabelTarget label = Expression.Label(typeof(void));
307+
BlockExpression block = Expression.Block(
308+
Expression.Block(Expression.Label(label)), Expression.Block(Expression.Block(Expression.Goto(label))),
309+
Expression.Block(Expression.Label(label)));
310+
Expression<Action> exp = Expression.Lambda<Action>(block);
311+
Assert.Throws<InvalidOperationException>(() => exp.Compile(useInterpreter));
312+
}
291313
}
292314
}

src/System.Linq.Expressions/tests/Goto/Return.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,5 +288,51 @@ public void JumpInWithValue(bool useInterpreter)
288288
);
289289
Assert.Throws<InvalidOperationException>(() => exp.Compile(useInterpreter));
290290
}
291+
292+
public static void DoNothing()
293+
{
294+
}
295+
296+
[Theory, ClassData(typeof(CompilationTypes))]
297+
public void TailCallThenReturn(bool useInterpreter)
298+
{
299+
LabelTarget target = Expression.Label();
300+
Expression<Action> lambda = Expression.Lambda<Action>(
301+
Expression.Block(
302+
Expression.Call(GetType().GetMethod(nameof(DoNothing))),
303+
Expression.Return(target),
304+
Expression.Throw(Expression.Constant(new Exception())),
305+
Expression.Label(target)),
306+
true);
307+
Action act = lambda.Compile(useInterpreter);
308+
act();
309+
lambda.Verify(@"
310+
.method void ::lambda_method(class [System.Linq.Expressions]System.Runtime.CompilerServices.Closure)
311+
{
312+
.maxstack 2
313+
314+
IL_0000: tail.
315+
IL_0002: call void class [System.Linq.Expressions.Tests]System.Linq.Expressions.Tests.Return::DoNothing()
316+
IL_0007: ret
317+
IL_0008: ldarg.0
318+
IL_0009: ldfld class [System.Linq.Expressions]System.Runtime.CompilerServices.Closure::Constants
319+
IL_000e: ldc.i4.0
320+
IL_000f: ldelem.ref
321+
IL_0010: castclass class [System.Private.CoreLib]System.Exception
322+
IL_0015: throw
323+
IL_0016: ret
324+
}", @"
325+
object lambda_method(object[])
326+
{
327+
.locals 0
328+
.maxstack 1
329+
.maxcontinuation 0
330+
331+
IP_0000: Call(Void DoNothing())
332+
IP_0001: Goto[0] -> 4
333+
IP_0002: LoadCached(0: System.Exception: Exception of type 'System.Exception' was thrown.)
334+
IP_0003: Throw()
335+
}");
336+
}
291337
}
292338
}

0 commit comments

Comments
 (0)