Skip to content

Commit 662129e

Browse files
jborean93Amro Khalil
authored andcommitted
Add MethodInvocation trace for overload tracing (PowerShell#21320)
1 parent e038e68 commit 662129e

File tree

3 files changed

+186
-0
lines changed

3 files changed

+186
-0
lines changed

src/System.Management.Automation/engine/parser/Compiler.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,9 @@ internal static class CachedReflectionInfo
477477
internal static readonly MethodInfo PSSetMemberBinder_SetAdaptedValue =
478478
typeof(PSSetMemberBinder).GetMethod(nameof(PSSetMemberBinder.SetAdaptedValue), StaticFlags);
479479

480+
internal static readonly MethodInfo PSTraceSource_WriteLine =
481+
typeof(PSTraceSource).GetMethod(nameof(PSTraceSource.WriteLine), InstanceFlags, new[] { typeof(string), typeof(object) });
482+
480483
internal static readonly MethodInfo PSVariableAssignmentBinder_CopyInstanceMembersOfValueType =
481484
typeof(PSVariableAssignmentBinder).GetMethod(nameof(PSVariableAssignmentBinder.CopyInstanceMembersOfValueType), StaticFlags);
482485

src/System.Management.Automation/engine/runtime/Binding/Binders.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6536,6 +6536,13 @@ public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, Dynam
65366536

65376537
internal sealed class PSInvokeMemberBinder : InvokeMemberBinder
65386538
{
6539+
[TraceSource("MethodInvocation", "Traces the invocation of .NET methods.")]
6540+
internal static readonly PSTraceSource MethodInvocationTracer =
6541+
PSTraceSource.GetTracer(
6542+
"MethodInvocation",
6543+
"Traces the invocation of .NET methods.",
6544+
false);
6545+
65396546
private static readonly SearchValues<string> s_whereSearchValues = SearchValues.Create(
65406547
["Where", "PSWhere"],
65416548
StringComparison.OrdinalIgnoreCase);
@@ -6956,6 +6963,17 @@ internal static DynamicMetaObject InvokeDotNetMethod(
69566963
expr = Expression.Block(expr, ExpressionCache.AutomationNullConstant);
69576964
}
69586965

6966+
if (MethodInvocationTracer.IsEnabled)
6967+
{
6968+
expr = Expression.Block(
6969+
Expression.Call(
6970+
Expression.Constant(MethodInvocationTracer),
6971+
CachedReflectionInfo.PSTraceSource_WriteLine,
6972+
Expression.Constant("Invoking method: {0}"),
6973+
Expression.Constant(result.methodDefinition)),
6974+
expr);
6975+
}
6976+
69596977
// If we're calling SteppablePipeline.{Begin|Process|End}, we don't want
69606978
// to wrap exceptions - this is very much a special case to help error
69616979
// propagation and ensure errors are attributed to the correct code (the

test/powershell/Modules/Microsoft.PowerShell.Utility/Trace-Command.Tests.ps1

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,171 @@ Describe "Trace-Command" -tags "CI" {
8484
}
8585
}
8686

87+
Context "MethodInvocation traces" {
88+
89+
BeforeAll {
90+
$filePath = Join-Path $TestDrive 'testtracefile.txt'
91+
92+
class MyClass {
93+
MyClass() {}
94+
MyClass([int]$arg) {}
95+
96+
[void]Method() { return }
97+
[void]Method([string]$arg) { return }
98+
[void]Method([int]$arg) { return }
99+
100+
[string]ReturnMethod() { return "foo" }
101+
102+
static [void]StaticMethod() { return }
103+
static [void]StaticMethod([string]$arg) { return }
104+
}
105+
106+
# C# classes support more features than pwsh classes
107+
Add-Type -TypeDefinition @'
108+
namespace TraceCommandTests;
109+
110+
public sealed class OverloadTests
111+
{
112+
public int PropertySetter { get; set; }
113+
114+
public OverloadTests() {}
115+
public OverloadTests(int value)
116+
{
117+
PropertySetter = value;
118+
}
119+
120+
public void GenericMethod<T>()
121+
{}
122+
123+
public T GenericMethodWithArg<T>(T obj) => obj;
124+
125+
public void MethodWithDefault(string arg1, int optional = 1)
126+
{}
127+
128+
public void MethodWithOut(out int val)
129+
{
130+
val = 1;
131+
}
132+
133+
public void MethodWithRef(ref int val)
134+
{
135+
val = 1;
136+
}
137+
}
138+
'@
139+
}
140+
141+
AfterEach {
142+
Remove-Item $filePath -Force -ErrorAction SilentlyContinue
143+
}
144+
145+
It "Traces instance method" {
146+
$myClass = [MyClass]::new()
147+
Trace-Command -Name MethodInvocation -Expression {
148+
$myClass.Method(1)
149+
} -FilePath $filePath
150+
Get-Content $filePath | Should -BeLike "*Invoking method: void Method(int arg)"
151+
}
152+
153+
It "Traces static method" {
154+
Trace-Command -Name MethodInvocation -Expression {
155+
[MyClass]::StaticMethod(1)
156+
} -FilePath $filePath
157+
Get-Content $filePath | Should -BeLike "*Invoking method: static void StaticMethod(string arg)"
158+
}
159+
160+
It "Traces method with return type" {
161+
$myClass = [MyClass]::new()
162+
Trace-Command -Name MethodInvocation -Expression {
163+
$myClass.ReturnMethod()
164+
} -FilePath $filePath
165+
Get-Content $filePath | Should -BeLike "*Invoking method: string ReturnMethod()"
166+
}
167+
168+
It "Traces constructor" {
169+
Trace-Command -Name MethodInvocation -Expression {
170+
[TraceCommandTests.OverloadTests]::new("1234")
171+
} -FilePath $filePath
172+
Get-Content $filePath | Should -BeLike "*Invoking method: TraceCommandTests.OverloadTests new(int value)"
173+
}
174+
175+
It "Traces Property setter invoked as a method" {
176+
$obj = [TraceCommandTests.OverloadTests]::new()
177+
Trace-Command -Name MethodInvocation -Expression {
178+
$obj.set_PropertySetter(1234)
179+
} -FilePath $filePath
180+
Get-Content $filePath | Should -BeLike "*Invoking method: void set_PropertySetter(int value)"
181+
}
182+
183+
It "Traces generic method" {
184+
$obj = [TraceCommandTests.OverloadTests]::new()
185+
Trace-Command -Name MethodInvocation -Expression {
186+
$obj.GenericMethod[int]()
187+
} -FilePath $filePath
188+
Get-Content $filePath | Should -BeLike "*Invoking method: void GenericMethod``[int``]()"
189+
}
190+
191+
It "Traces generic method with argument" {
192+
$obj = [TraceCommandTests.OverloadTests]::new()
193+
Trace-Command -Name MethodInvocation -Expression {
194+
$obj.GenericMethodWithArg("foo")
195+
} -FilePath $filePath
196+
Get-Content $filePath | Should -BeLike "*Invoking method: string GenericMethodWithArg``[string``](string obj)"
197+
}
198+
199+
It "Traces .NET call with default value" {
200+
$obj = [TraceCommandTests.OverloadTests]::new()
201+
Trace-Command -Name MethodInvocation -Expression {
202+
$obj.MethodWithDefault("foo")
203+
} -FilePath $filePath
204+
Get-Content $filePath | Should -BeLike "*Invoking method: void MethodWithDefault(string arg1, int optional = 1)"
205+
}
206+
207+
It "Traces method with ref argument" {
208+
$obj = [TraceCommandTests.OverloadTests]::new()
209+
$v = 1
210+
211+
Trace-Command -Name MethodInvocation -Expression {
212+
$obj.MethodWithRef([ref]$v)
213+
} -FilePath $filePath
214+
# [ref] goes through the binder so will trigger the first trace
215+
Get-Content $filePath | Select-Object -Skip 1 | Should -BeLike "*Invoking method: void MethodWithRef(``[ref``] int val)"
216+
}
217+
218+
It "Traces method with out argument" {
219+
$obj = [TraceCommandTests.OverloadTests]::new()
220+
$v = 1
221+
222+
Trace-Command -Name MethodInvocation -Expression {
223+
$obj.MethodWithOut([ref]$v)
224+
} -FilePath $filePath
225+
# [ref] goes through the binder so will trigger the first trace
226+
Get-Content $filePath | Select-Object -Skip 1 | Should -BeLike "*Invoking method: void MethodWithOut(``[ref``] int val)"
227+
}
228+
229+
It "Traces a binding error" {
230+
Trace-Command -Name MethodInvocation -Expression {
231+
# try/catch is used as error formatter will hit the trace as well
232+
try {
233+
[System.Runtime.InteropServices.Marshal]::SizeOf([int])
234+
}
235+
catch {
236+
# Satisfy codefactor
237+
$_ | Out-Null
238+
}
239+
} -FilePath $filePath
240+
# type fqn is used, the wildcard avoids hardcoding that
241+
Get-Content $filePath | Should -BeLike "*Invoking method: static int SizeOf``[System.RuntimeType, *``](System.RuntimeType, * structure)"
242+
}
243+
244+
It "Traces LINQ call" {
245+
Trace-Command -Name MethodInvocation -Expression {
246+
[System.Linq.Enumerable]::Union([int[]]@(1, 2), [int[]]@(3, 4))
247+
} -FilePath $filePath
248+
Get-Content $filePath | Should -BeLike "*Invoking method: static System.Collections.Generic.IEnumerable``[int``] Union``[int``](System.Collections.Generic.IEnumerable``[int``] first, System.Collections.Generic.IEnumerable``[int``] second)"
249+
}
250+
}
251+
87252
Context "Trace-Command tests for code coverage" {
88253

89254
BeforeAll {

0 commit comments

Comments
 (0)