Skip to content

Commit 96b1594

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

File tree

2 files changed

+54
-10
lines changed

2 files changed

+54
-10
lines changed

src/coverlet.core/Instrumentation/Instrumenter.cs

Lines changed: 45 additions & 7 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)
@@ -76,6 +77,7 @@ private void InstrumentModule()
7677

7778
using (var module = ModuleDefinition.ReadModule(stream, parameters))
7879
{
80+
var containsAppContext = module.GetType(nameof(System), nameof(AppContext)) != null;
7981
var types = module.GetTypes();
8082
AddCustomModuleTrackerToModule(module);
8183

@@ -95,13 +97,49 @@ private void InstrumentModule()
9597
}
9698

9799
// Fixup the custom tracker class constructor, according to all instrumented types
100+
if (_customTrackerRegisterUnloadEventsMethod == null)
101+
{
102+
_customTrackerRegisterUnloadEventsMethod = new MethodReference(
103+
nameof(ModuleTrackerTemplate.RegisterUnloadEvents), module.TypeSystem.Void, _customTrackerTypeDef);
104+
}
105+
98106
Instruction lastInstr = _customTrackerClassConstructorIl.Body.Instructions.Last();
107+
108+
if (!containsAppContext)
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 (containsAppContext)
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);
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);
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)
@@ -166,8 +201,11 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module)
166201
else
167202
{
168203
// Move to the custom type
169-
instr.Operand = new MethodReference(
170-
methodReference.Name, methodReference.ReturnType, _customTrackerTypeDef);
204+
var updatedMethodReference = new MethodReference(methodReference.Name, methodReference.ReturnType, _customTrackerTypeDef);
205+
foreach (var parameter in methodReference.Parameters)
206+
updatedMethodReference.Parameters.Add(new ParameterDefinition(parameter.Name, parameter.Attributes, module.ImportReference(parameter.ParameterType)));
207+
208+
instr.Operand = updatedMethodReference;
171209
}
172210
}
173211
else if (instr.Operand is FieldReference fieldReference)

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)