@@ -254,107 +254,150 @@ private static bool SkipGeneratedBranchForExceptionRethrown(List<Instruction> in
254
254
In case of exception re-thrown inside the catch block,
255
255
the compiler generates a branch to check if the exception reference is null.
256
256
257
- A sample of generated code:
258
-
257
+ // Debug version:
259
258
IL_00b4: isinst [System.Runtime]System.Exception
260
259
IL_00b9: stloc.s 6
261
- // if (ex == null)
262
260
IL_00bb: ldloc.s 6
263
- // (no C# code)
264
261
IL_00bd: brtrue.s IL_00c6
265
262
266
- So we can go back to previous instructions and skip this branch if recognize that type of code block
263
+ // Release version:
264
+ IL_008A: isinst[System.Runtime]System.Exception
265
+ IL_008F: dup
266
+ IL_0090: brtrue.s IL_0099
267
+
268
+ So we can go back to previous instructions and skip this branch if recognize that type of code block.
269
+ There are differences between debug and release configuration so we just look for the highlights.
267
270
*/
271
+
268
272
int branchIndex = instructions . BinarySearch ( instruction , new InstructionByOffsetComparer ( ) ) ;
269
- return branchIndex >= 3 && // avoid out of range exception (need almost 3 instruction before the branch)
270
- instructions [ branchIndex - 3 ] . OpCode == OpCodes . Isinst &&
271
- instructions [ branchIndex - 3 ] . Operand is TypeReference tr && tr . FullName == "System.Exception" &&
272
- instructions [ branchIndex - 2 ] . OpCode == OpCodes . Stloc &&
273
- instructions [ branchIndex - 1 ] . OpCode == OpCodes . Ldloc &&
274
- // check for throw opcode after branch
275
- instructions . Count - branchIndex >= 3 &&
276
- instructions [ branchIndex + 1 ] . OpCode == OpCodes . Ldarg &&
277
- instructions [ branchIndex + 2 ] . OpCode == OpCodes . Ldfld &&
278
- instructions [ branchIndex + 3 ] . OpCode == OpCodes . Throw ;
273
+
274
+ return new [ ] { 2 , 3 } . Any ( x => branchIndex >= x &&
275
+ instructions [ branchIndex - x ] . OpCode == OpCodes . Isinst &&
276
+ instructions [ branchIndex - x ] . Operand is TypeReference tr &&
277
+ tr . FullName == "System.Exception" )
278
+ &&
279
+ // check for throw opcode after branch
280
+ instructions . Count - branchIndex >= 3 &&
281
+ instructions [ branchIndex + 1 ] . OpCode == OpCodes . Ldarg &&
282
+ instructions [ branchIndex + 2 ] . OpCode == OpCodes . Ldfld &&
283
+ instructions [ branchIndex + 3 ] . OpCode == OpCodes . Throw ;
279
284
}
280
285
281
286
private bool SkipGeneratedBranchesForExceptionHandlers ( MethodDefinition methodDefinition , Instruction instruction , List < Instruction > bodyInstructions )
282
287
{
288
+ /*
289
+ This method is used to parse compiler generated code inside async state machine and find branches generated for exception catch blocks.
290
+ There are differences between debug and release configuration. Also variations of the shown examples exist e.g. multiple catch blocks,
291
+ nested catch blocks... Therefore we just look for the highlights.
292
+ Typical generated code for catch block is:
293
+
294
+ // Debug version:
295
+ catch ...
296
+ {
297
+ // (no C# code)
298
+ IL_0028: stloc.2
299
+ // object obj2 = <>s__1 = obj;
300
+ IL_0029: ldarg.0
301
+ // (no C# code)
302
+ IL_002a: ldloc.2
303
+ IL_002b: stfld object ...::'<>s__1'
304
+ // <>s__2 = 1;
305
+ IL_0030: ldarg.0
306
+ IL_0031: ldc.i4.1
307
+ IL_0032: stfld int32 ...::'<>s__2' <- store 1 into <>s__2
308
+ // (no C# code)
309
+ IL_0037: leave.s IL_0039
310
+ } // end handle
311
+
312
+ // int num2 = <>s__2;
313
+ IL_0039: ldarg.0
314
+ IL_003a: ldfld int32 ...::'<>s__2' <- load <>s__2 value and check if 1
315
+ IL_003f: stloc.3
316
+ // if (num2 == 1)
317
+ IL_0040: ldloc.3
318
+ IL_0041: ldc.i4.1
319
+ IL_0042: beq.s IL_0049 <- BRANCH : if <>s__2 value is 1 go to exception handler code
320
+ IL_0044: br IL_00d6
321
+ IL_0049: nop <- start exception handler code
322
+
323
+ // Release version:
324
+ catch ...
325
+ {
326
+ IL_001D: stloc.3
327
+ IL_001E: ldarg.0
328
+ IL_001F: ldloc.3
329
+ IL_0020: stfld object Coverlet.Core.Samples.Tests.CatchBlock / '<ParseAsync>d__0'::'<>7__wrap1'
330
+ IL_0025: ldc.i4.1
331
+ IL_0026: stloc.2
332
+ IL_0027: leave.s IL_0029
333
+ } // end handler
334
+
335
+ IL_0029: ldloc.2
336
+ IL_002A: ldc.i4.1
337
+ IL_002B: bne.un.s IL_00AC
338
+
339
+
340
+ In case of multiple catch blocks as
341
+ try
342
+ {
343
+ }
344
+ catch (ExceptionType1)
345
+ {
346
+ }
347
+ catch (ExceptionType2)
348
+ {
349
+ }
350
+
351
+ generated IL contains multiple branches:
352
+
353
+ // Debug version:
354
+ catch ...(type1)
355
+ {
356
+ ...
357
+ }
358
+ catch ...(type2)
359
+ {
360
+ ...
361
+ }
362
+ // int num2 = <>s__2;
363
+ IL_0039: ldarg.0
364
+ IL_003a: ldfld int32 ...::'<>s__2' <- load <>s__2 value and check if 1
365
+ IL_003f: stloc.3
366
+ // if (num2 == 1)
367
+ IL_0040: ldloc.3
368
+ IL_0041: ldc.i4.1
369
+ IL_0042: beq.s IL_0049 <- BRANCH 1 (type 1)
370
+ IL_0044: br IL_00d6
371
+
372
+ // if (num2 == 2)
373
+ IL_0067: ldloc.s 4
374
+ IL_0069: ldc.i4.2
375
+ IL_006a: beq IL_0104 <- BRANCH 2 (type 2)
376
+
377
+ // (no C# code)
378
+ IL_006f: br IL_0191
379
+
380
+ // Release version:
381
+ catch ...(type1)
382
+ {
383
+ ...
384
+ }
385
+ catch ...(type2)
386
+ {
387
+ ...
388
+ }
389
+ IL_003E: ldloc.2
390
+ IL_003F: ldc.i4.1
391
+ IL_0040: beq.s IL_004E
392
+
393
+ IL_0042: ldloc.2/
394
+ IL_0043: ldc.i4.2
395
+ IL_0044: beq IL_00CD
396
+
397
+ IL_0049: br IL_0147
398
+ */
283
399
if ( ! _compilerGeneratedBranchesToExclude . ContainsKey ( methodDefinition . FullName ) )
284
400
{
285
- /*
286
- This method is used to parse compiler generated code inside async state machine and find branches generated for exception catch blocks
287
- Typical generated code for catch block is:
288
-
289
- catch ...
290
- {
291
- // (no C# code)
292
- IL_0028: stloc.2
293
- // object obj2 = <>s__1 = obj;
294
- IL_0029: ldarg.0
295
- // (no C# code)
296
- IL_002a: ldloc.2
297
- IL_002b: stfld object ...::'<>s__1'
298
- // <>s__2 = 1;
299
- IL_0030: ldarg.0
300
- IL_0031: ldc.i4.1
301
- IL_0032: stfld int32 ...::'<>s__2' <- store 1 into <>s__2
302
- // (no C# code)
303
- IL_0037: leave.s IL_0039
304
- } // end handle
305
-
306
- // int num2 = <>s__2;
307
- IL_0039: ldarg.0
308
- IL_003a: ldfld int32 ...::'<>s__2' <- load <>s__2 value and check if 1
309
- IL_003f: stloc.3
310
- // if (num2 == 1)
311
- IL_0040: ldloc.3
312
- IL_0041: ldc.i4.1
313
- IL_0042: beq.s IL_0049 <- BRANCH : if <>s__2 value is 1 go to exception handler code
314
-
315
- IL_0044: br IL_00d6
316
-
317
- IL_0049: nop <- start exception handler code
318
-
319
- In case of multiple catch blocks as
320
- try
321
- {
322
- }
323
- catch (ExceptionType1)
324
- {
325
- }
326
- catch (ExceptionType2)
327
- {
328
- }
329
-
330
- generated IL contains multiple branches:
331
- catch ...(type1)
332
- {
333
- ...
334
- }
335
- catch ...(type2)
336
- {
337
- ...
338
- }
339
- // int num2 = <>s__2;
340
- IL_0039: ldarg.0
341
- IL_003a: ldfld int32 ...::'<>s__2' <- load <>s__2 value and check if 1
342
- IL_003f: stloc.3
343
- // if (num2 == 1)
344
- IL_0040: ldloc.3
345
- IL_0041: ldc.i4.1
346
- IL_0042: beq.s IL_0049 <- BRANCH 1 (type 1)
347
-
348
- IL_0044: br IL_00d6
349
-
350
- // if (num2 == 2)
351
- IL_0067: ldloc.s 4
352
- IL_0069: ldc.i4.2
353
- IL_006a: beq IL_0104 <- BRANCH 2 (type 2)
354
-
355
- // (no C# code)
356
- IL_006f: br IL_0191
357
- */
358
401
List < int > detectedBranches = new List < int > ( ) ;
359
402
Collection < ExceptionHandler > handlers = methodDefinition . Body . ExceptionHandlers ;
360
403
@@ -370,39 +413,22 @@ private bool SkipGeneratedBranchesForExceptionHandlers(MethodDefinition methodDe
370
413
371
414
int currentIndex = bodyInstructions . BinarySearch ( handler . HandlerEnd , new InstructionByOffsetComparer ( ) ) ;
372
415
373
- /* Detect flag load
374
- // int num2 = <>s__2;
375
- IL_0058: ldarg.0
376
- IL_0059: ldfld int32 ...::'<>s__2'
377
- IL_005e: stloc.s 4
378
- */
379
- if ( bodyInstructions . Count - currentIndex > 3 && // check boundary
380
- bodyInstructions [ currentIndex ] . OpCode == OpCodes . Ldarg &&
381
- bodyInstructions [ currentIndex + 1 ] . OpCode == OpCodes . Ldfld && bodyInstructions [ currentIndex + 1 ] . Operand is FieldReference fr && fr . Name . StartsWith ( "<>s__" ) &&
382
- bodyInstructions [ currentIndex + 2 ] . OpCode == OpCodes . Stloc )
416
+ for ( int i = 0 ; i < numberOfCatchBlocks ; i ++ )
383
417
{
384
- currentIndex += 3 ;
385
- for ( int i = 0 ; i < numberOfCatchBlocks ; i ++ )
418
+ // We expect the first branch after the end of the handler to be no more than five instructions away.
419
+ int firstBranchIndex = currentIndex + Enumerable . Range ( 1 , 5 ) . FirstOrDefault ( x =>
420
+ currentIndex + x < bodyInstructions . Count &&
421
+ bodyInstructions [ currentIndex + x ] . OpCode == OpCodes . Beq ||
422
+ bodyInstructions [ currentIndex + x ] . OpCode == OpCodes . Bne_Un ) ;
423
+
424
+ if ( bodyInstructions . Count - firstBranchIndex > 2 &&
425
+ bodyInstructions [ firstBranchIndex - 1 ] . OpCode == OpCodes . Ldc_I4 &&
426
+ bodyInstructions [ firstBranchIndex - 2 ] . OpCode == OpCodes . Ldloc )
386
427
{
387
- /*
388
- // if (num2 == 1)
389
- IL_0060: ldloc.s 4
390
- IL_0062: ldc.i4.1
391
- IL_0063: beq.s IL_0074
392
-
393
- // (no C# code)
394
- IL_0065: br.s IL_0067
395
- */
396
- if ( bodyInstructions . Count - currentIndex > 4 && // check boundary
397
- bodyInstructions [ currentIndex ] . OpCode == OpCodes . Ldloc &&
398
- bodyInstructions [ currentIndex + 1 ] . OpCode == OpCodes . Ldc_I4 &&
399
- bodyInstructions [ currentIndex + 2 ] . OpCode == OpCodes . Beq &&
400
- bodyInstructions [ currentIndex + 3 ] . OpCode == OpCodes . Br )
401
- {
402
- detectedBranches . Add ( bodyInstructions [ currentIndex + 2 ] . Offset ) ;
403
- }
404
- currentIndex += 4 ;
428
+ detectedBranches . Add ( bodyInstructions [ firstBranchIndex ] . Offset ) ;
405
429
}
430
+
431
+ currentIndex = firstBranchIndex ;
406
432
}
407
433
}
408
434
0 commit comments