Skip to content

Commit db5dbc5

Browse files
committed
Remove the need to register events when instrumenting System.Private.CoreLib
1 parent 92b26a4 commit db5dbc5

File tree

2 files changed

+49
-8
lines changed

2 files changed

+49
-8
lines changed

src/coverlet.core/Instrumentation/Instrumenter.cs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ internal class Instrumenter
2929
private FieldDefinition _customTrackerHitsFilePath;
3030
private ILProcessor _customTrackerClassConstructorIl;
3131
private TypeDefinition _customTrackerTypeDef;
32+
private MethodReference _customTrackerRegisterUnloadEventsMethod;
3233
private MethodReference _customTrackerRecordHitMethod;
3334

3435
public Instrumenter(string module, string identifier, string[] excludeFilters, string[] includeFilters, string[] excludedFiles, string[] excludedAttributes)
@@ -95,13 +96,50 @@ private void InstrumentModule()
9596
}
9697

9798
// Fixup the custom tracker class constructor, according to all instrumented types
99+
if (_customTrackerRegisterUnloadEventsMethod == null)
100+
{
101+
_customTrackerRegisterUnloadEventsMethod = new MethodReference(
102+
nameof(ModuleTrackerTemplate.RegisterUnloadEvents), module.TypeSystem.Void, _customTrackerTypeDef);
103+
}
104+
98105
Instruction lastInstr = _customTrackerClassConstructorIl.Body.Instructions.Last();
106+
107+
var isCoreLibrary = module.Equals(module.TypeSystem.Void.Module);
108+
if (!isCoreLibrary)
109+
{
110+
// For "normal" cases, where the instrumented assembly is not the core library, we add a call to
111+
// RegisterUnloadEvents to the static constructor of the generated custom tracker. Due to static
112+
// initialization constraints, the core library is handled separately below.
113+
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Call, _customTrackerRegisterUnloadEventsMethod));
114+
}
115+
99116
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4, _result.HitCandidates.Count));
100117
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Newarr, module.TypeSystem.Int32));
101118
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArray));
102119
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath));
103120
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsFilePath));
104121

122+
if (isCoreLibrary)
123+
{
124+
// Handle the core library by instrumenting System.AppContext.OnProcessExit to directly call
125+
// the UnloadModule method of the custom tracker type. This avoids loops between the static
126+
// initialization of the custom tracker and the static initialization of the hosting AppDomain
127+
// (which for the core library case will be instrumented code).
128+
var eventArgsType = new TypeReference(nameof(System), nameof(EventArgs), module, module.TypeSystem.CoreLibrary).Resolve();
129+
var customTrackerUnloadModule = new MethodReference(nameof(ModuleTrackerTemplate.UnloadModule), module.TypeSystem.Void, _customTrackerTypeDef);
130+
customTrackerUnloadModule.Parameters.Add(new ParameterDefinition(module.TypeSystem.Object));
131+
customTrackerUnloadModule.Parameters.Add(new ParameterDefinition(eventArgsType));
132+
133+
var appContextType = new TypeReference(nameof(System), nameof(AppContext), module, module.TypeSystem.CoreLibrary).Resolve();
134+
var onProcessExitMethod = new MethodReference("OnProcessExit", module.TypeSystem.Void, appContextType).Resolve();
135+
var onProcessExitIl = onProcessExitMethod.Body.GetILProcessor();
136+
137+
lastInstr = onProcessExitIl.Body.Instructions.Last();
138+
onProcessExitIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldnull));
139+
onProcessExitIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldnull));
140+
onProcessExitIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Call, customTrackerUnloadModule));
141+
}
142+
105143
module.Write(stream);
106144
}
107145
}
@@ -135,12 +173,9 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module)
135173
{
136174
MethodDefinition methodOnCustomType = new MethodDefinition(methodDef.Name, methodDef.Attributes, methodDef.ReturnType);
137175

138-
if (methodDef.Name == "RecordHit")
176+
foreach (var parameter in methodDef.Parameters)
139177
{
140-
foreach (var parameter in methodDef.Parameters)
141-
{
142-
methodOnCustomType.Parameters.Add(new ParameterDefinition(module.ImportReference(parameter.ParameterType)));
143-
}
178+
methodOnCustomType.Parameters.Add(new ParameterDefinition(module.ImportReference(parameter.ParameterType)));
144179
}
145180

146181
foreach (var variable in methodDef.Body.Variables)

src/coverlet.template/ModuleTrackerTemplate.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,20 @@ public static class ModuleTrackerTemplate
2222

2323
static ModuleTrackerTemplate()
2424
{
25-
AppDomain.CurrentDomain.ProcessExit += new EventHandler(UnloadModule);
26-
AppDomain.CurrentDomain.DomainUnload += new EventHandler(UnloadModule);
27-
2825
// At the end of the instrumentation of a module, the instrumenter needs to add code here
2926
// to initialize the static fields according to the values derived from the instrumentation of
3027
// the module.
3128
}
3229

30+
// A call to this method will be injected in the static constructor above for most cases. However, if the
31+
// current assembly is System.Private.CoreLib (or more specifically, defines System.AppDomain), a call directly
32+
// to UnloadModule will be injected in System.AppContext.OnProcessExit.
33+
public static void RegisterUnloadEvents()
34+
{
35+
AppDomain.CurrentDomain.ProcessExit += new EventHandler(UnloadModule);
36+
AppDomain.CurrentDomain.DomainUnload += new EventHandler(UnloadModule);
37+
}
38+
3339
public static void RecordHit(int hitLocationIndex)
3440
{
3541
// Make sure to avoid recording if this is a call to RecordHit within the AppDomain setup code in an

0 commit comments

Comments
 (0)