@@ -249,14 +249,24 @@ public unsafe void Process()
249249 var reportTypeGenericInstance = new GenericInstanceType ( coverageReporterTypeReference ) ;
250250 reportTypeGenericInstance . GenericArguments . Add ( moduleCoverageMetadataImplTypeDef ) ;
251251
252- var reportGetCountersMethod = new MethodReference ( "GetFileCounter" , new PointerType ( module . TypeSystem . Void ) , reportTypeGenericInstance )
252+ var coverageProbeTypeDefinition = datadogTracerAssembly . MainModule . GetType ( typeof ( CoverageProbe ) . FullName ) ;
253+ var coverageProbeTypeReference = module . ImportReference ( coverageProbeTypeDefinition ) ;
254+ var reportAcquireCountersMethod = new MethodReference ( "AcquireFileCounter" , coverageProbeTypeReference , reportTypeGenericInstance )
253255 {
254256 HasThis = false ,
255257 Parameters =
256258 {
257259 new ParameterDefinition ( module . TypeSystem . Int32 ) { Name = "fileIndex" }
258260 }
259261 } ;
262+ var probePointerGetter = new MethodReference ( "get_Pointer" , new PointerType ( module . TypeSystem . Void ) , coverageProbeTypeReference )
263+ {
264+ HasThis = true
265+ } ;
266+ var probeDisposeMethod = new MethodReference ( nameof ( IDisposable . Dispose ) , module . TypeSystem . Void , coverageProbeTypeReference )
267+ {
268+ HasThis = true
269+ } ;
260270
261271 // GenericInstanceMethod? arrayEmptyOfIntMethodReference = null;
262272 for ( var typeIndex = 0 ; typeIndex < moduleTypes . Count ; typeIndex ++ )
@@ -432,6 +442,15 @@ public unsafe void Process()
432442
433443 var methodBody = moduleTypeMethod . Body ;
434444 var instructions = methodBody . Instructions ;
445+ if ( instructions . Any ( static instruction => instruction . OpCode == OpCodes . Tail ) )
446+ {
447+ // The invocation probe is released from an outer fault handler and a shared return
448+ // epilogue. Wrapping a tail transfer in that protected region would invalidate its
449+ // stack-constant semantics, so leave these uncommon methods untouched.
450+ _logger . Debug ( $ "\t \t [NO] { moduleTypeMethod . FullName } , contains a tail call.") ;
451+ continue ;
452+ }
453+
435454 var instructionsOriginalLength = instructions . Count ;
436455 if ( instructions . Capacity < instructionsOriginalLength * 2 )
437456 {
@@ -455,22 +474,23 @@ public unsafe void Process()
455474 }
456475 }
457476
458- VariableDefinition ? countersVariable = null ;
459- if ( instructionsWithValidSequencePoints . Count > 1 || instructions [ 0 ] != instructionsWithValidSequencePoints [ 0 ] . Instruction )
477+ // The probe local owns the native buffer for the complete invocation. The generated
478+ // outer fault handler and shared return epilogue release it on every exit path.
479+ var probeVariable = new VariableDefinition ( coverageProbeTypeReference ) ;
480+ VariableDefinition countersVariable ;
481+ if ( _coverageMode == CoverageMode . LineExecution )
460482 {
461- // Step 3 - Modify local var to add the Coverage counters instance.
462- if ( _coverageMode == CoverageMode . LineExecution )
463- {
464- countersVariable = new VariableDefinition ( new PointerType ( module . TypeSystem . Byte ) ) ;
465- }
466- else
467- {
468- countersVariable = new VariableDefinition ( new PointerType ( module . TypeSystem . Int32 ) ) ;
469- }
470-
471- methodBody . Variables . Add ( countersVariable ) ;
483+ countersVariable = new VariableDefinition ( new PointerType ( module . TypeSystem . Byte ) ) ;
484+ }
485+ else
486+ {
487+ countersVariable = new VariableDefinition ( new PointerType ( module . TypeSystem . Int32 ) ) ;
472488 }
473489
490+ methodBody . Variables . Add ( probeVariable ) ;
491+ methodBody . Variables . Add ( countersVariable ) ;
492+ methodBody . InitLocals = true ;
493+
474494 // Step 4 - Insert the counter retriever
475495 FileMetadata fileMetadata ;
476496 if ( ! fileDictionaryIndex . TryGetValue ( filePath , out fileMetadata ) )
@@ -479,12 +499,13 @@ public unsafe void Process()
479499 fileDictionaryIndex [ filePath ] = fileMetadata ;
480500 }
481501
502+ var probeProtectedStart = Instruction . Create ( OpCodes . Ldloca , probeVariable ) ;
482503 instructions . Insert ( 0 , Instruction . Create ( OpCodes . Ldc_I4 , fileMetadata . Index ) ) ;
483- instructions . Insert ( 1 , Instruction . Create ( OpCodes . Call , reportGetCountersMethod ) ) ;
484- if ( countersVariable is not null )
485- {
486- instructions . Insert ( 2 , Instruction . Create ( OpCodes . Stloc , countersVariable ) ) ;
487- }
504+ instructions . Insert ( 1 , Instruction . Create ( OpCodes . Call , reportAcquireCountersMethod ) ) ;
505+ instructions . Insert ( 2 , Instruction . Create ( OpCodes . Stloc , probeVariable ) ) ;
506+ instructions . Insert ( 3 , probeProtectedStart ) ;
507+ instructions . Insert ( 4 , Instruction . Create ( OpCodes . Call , probePointerGetter ) ) ;
508+ instructions . Insert ( 5 , Instruction . Create ( OpCodes . Stloc , countersVariable ) ) ;
488509
489510 // Step 5 - Insert line reporter
490511 for ( var i = 0 ; i < instructionsWithValidSequencePoints . Count ; i ++ )
@@ -635,6 +656,7 @@ public unsafe void Process()
635656 instructions . Insert ( ++ optIdx , currentInstructionClone ) ;
636657 }
637658
659+ AddProbeCleanup ( methodBody , probeVariable , probeProtectedStart , probeDisposeMethod ) ;
638660 isDirty = true ;
639661 }
640662 }
@@ -955,6 +977,77 @@ private static void WriteInt32LittleEndian(byte[] buffer, int offset, int value)
955977 buffer [ offset + 3 ] = ( byte ) ( value >> 24 ) ;
956978 }
957979
980+ private static void AddProbeCleanup (
981+ Mono . Cecil . Cil . MethodBody methodBody ,
982+ VariableDefinition probeVariable ,
983+ Instruction protectedStart ,
984+ MethodReference probeDisposeMethod )
985+ {
986+ var instructions = methodBody . Instructions ;
987+ var returnInstructions = instructions . Where ( static instruction => instruction . OpCode == OpCodes . Ret ) . ToArray ( ) ;
988+ VariableDefinition ? returnVariable = null ;
989+ if ( returnInstructions . Length > 0 && methodBody . Method . ReturnType . MetadataType != MetadataType . Void )
990+ {
991+ returnVariable = new VariableDefinition ( methodBody . Method . ReturnType ) ;
992+ methodBody . Variables . Add ( returnVariable ) ;
993+ }
994+
995+ var faultStart = Instruction . Create ( OpCodes . Ldloca , probeVariable ) ;
996+ var faultDispose = Instruction . Create ( OpCodes . Call , probeDisposeMethod ) ;
997+ var faultEnd = Instruction . Create ( OpCodes . Endfinally ) ;
998+ var epilogueStart = returnInstructions . Length == 0 ? null : Instruction . Create ( OpCodes . Ldloca , probeVariable ) ;
999+
1000+ foreach ( var exceptionHandler in methodBody . ExceptionHandlers )
1001+ {
1002+ // Cecil represents "until the end of the method" with null. The generated outer
1003+ // fault handler extends the body, so existing regions must keep their original end.
1004+ exceptionHandler . TryEnd ??= faultStart ;
1005+ exceptionHandler . HandlerEnd ??= faultStart ;
1006+ }
1007+
1008+ if ( epilogueStart is not null )
1009+ {
1010+ foreach ( var returnInstruction in returnInstructions )
1011+ {
1012+ if ( returnVariable is not null )
1013+ {
1014+ returnInstruction . OpCode = OpCodes . Stloc ;
1015+ returnInstruction . Operand = returnVariable ;
1016+ instructions . Insert ( instructions . IndexOf ( returnInstruction ) + 1 , Instruction . Create ( OpCodes . Leave , epilogueStart ) ) ;
1017+ }
1018+ else
1019+ {
1020+ returnInstruction . OpCode = OpCodes . Leave ;
1021+ returnInstruction . Operand = epilogueStart ;
1022+ }
1023+ }
1024+ }
1025+
1026+ instructions . Add ( faultStart ) ;
1027+ instructions . Add ( faultDispose ) ;
1028+ instructions . Add ( faultEnd ) ;
1029+ if ( epilogueStart is not null )
1030+ {
1031+ instructions . Add ( epilogueStart ) ;
1032+ instructions . Add ( Instruction . Create ( OpCodes . Call , probeDisposeMethod ) ) ;
1033+ if ( returnVariable is not null )
1034+ {
1035+ instructions . Add ( Instruction . Create ( OpCodes . Ldloc , returnVariable ) ) ;
1036+ }
1037+
1038+ instructions . Add ( Instruction . Create ( OpCodes . Ret ) ) ;
1039+ }
1040+
1041+ methodBody . ExceptionHandlers . Add (
1042+ new ExceptionHandler ( ExceptionHandlerType . Fault )
1043+ {
1044+ TryStart = protectedStart ,
1045+ TryEnd = faultStart ,
1046+ HandlerStart = faultStart ,
1047+ HandlerEnd = epilogueStart ,
1048+ } ) ;
1049+ }
1050+
9581051 private static void RemoveShortOpCodes ( Instruction instruction )
9591052 {
9601053 if ( instruction . OpCode == OpCodes . Br_S ) { instruction . OpCode = OpCodes . Br ; }
0 commit comments