Skip to content

Commit 001f2eb

Browse files
Separate all command logic into a static class (#40)
* Separate all command logic into a static class Also throw when called from CLM * don't use records
1 parent 90480e0 commit 001f2eb

5 files changed

Lines changed: 235 additions & 71 deletions

File tree

src/ILAssembler/CilAssemblage.cs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Management.Automation;
4+
using System.Management.Automation.Language;
5+
using System.Reflection;
6+
using System.Reflection.Emit;
7+
8+
namespace ILAssembler
9+
{
10+
/// <summary>
11+
/// Provides a binary entry point for the assembler.
12+
/// </summary>
13+
/// <remarks>
14+
/// This class is currently non-public. If you have a use case in mind for
15+
/// it, please open an issue and I'll make it public.
16+
/// </remarks>
17+
internal static class CilAssemblage
18+
{
19+
private static readonly ConcurrentDictionary<(Type, ScriptBlockAst), DynamicMethod> s_methodCache = new();
20+
21+
public static Delegate CreateDelegate(ScriptBlockAst signature, ScriptBlockAst body)
22+
{
23+
return CreateDelegate(signature, body, skipCache: false);
24+
}
25+
26+
public static Delegate CreateDelegate(ScriptBlockAst signature, ScriptBlockAst body, bool skipCache)
27+
{
28+
var signatureParser = new MethodSignatureParser(
29+
rejectCtor: true,
30+
requireResolvableDeclaringType: false);
31+
32+
signature.Visit(signatureParser);
33+
var identifier = (MethodIdentifier)signatureParser.GetMemberIdentifier(signature.Extent);
34+
35+
Type delegateType = DelegateTypeFactory.GetDelegateType(identifier);
36+
return CreateDelegate(delegateType, body, skipCache);
37+
}
38+
39+
public static TDelegate CreateDelegate<TDelegate>(ScriptBlockAst body)
40+
where TDelegate : Delegate
41+
{
42+
return CreateDelegate<TDelegate>(body, skipCache: false);
43+
}
44+
45+
public static TDelegate CreateDelegate<TDelegate>(ScriptBlockAst body, bool skipCache)
46+
where TDelegate : Delegate
47+
{
48+
return (TDelegate)CreateDelegate(typeof(TDelegate), body, skipCache);
49+
}
50+
51+
public static Delegate CreateDelegate(Type delegateType, ScriptBlockAst body)
52+
{
53+
return CreateDelegate(delegateType, body, skipCache: false);
54+
}
55+
56+
public static Delegate CreateDelegate(Type delegateType, ScriptBlockAst body, bool skipCache)
57+
{
58+
return CreateCilMethod(delegateType, body, skipCache)
59+
.CreateDelegate(delegateType);
60+
}
61+
62+
private static DynamicMethod CreateCilMethod(Type delegateType, ScriptBlockAst body, bool skipCache = false)
63+
{
64+
if (skipCache)
65+
{
66+
return CreateCilMethod((delegateType, body));
67+
}
68+
69+
return s_methodCache.GetOrAdd(
70+
(delegateType, body),
71+
args => CreateCilMethod(args));
72+
}
73+
74+
private static DynamicMethod CreateCilMethod((Type delegateType, ScriptBlockAst body) args)
75+
{
76+
(Type delegateType, ScriptBlockAst body) = args;
77+
DynamicMethod method = CreateDynamicMethod(delegateType);
78+
CilAssembler.CompileTo(body, method);
79+
return method;
80+
}
81+
82+
private static DynamicMethod CreateDynamicMethod(ScriptBlockAst signature, out Type delegateType)
83+
{
84+
var signatureParser = new MethodSignatureParser(
85+
rejectCtor: true,
86+
requireResolvableDeclaringType: false);
87+
88+
signature.Visit(signatureParser);
89+
var identifier = (MethodIdentifier)signatureParser.GetMemberIdentifier(signature.Extent);
90+
91+
delegateType = DelegateTypeFactory.GetDelegateType(identifier);
92+
93+
return new DynamicMethod(
94+
identifier.Name,
95+
identifier.ReturnType.GetModifiedType(),
96+
identifier.Parameters.ToModifiedTypeArray(),
97+
typeof(CilAssemblage).Module,
98+
skipVisibility: true);
99+
}
100+
101+
private static DynamicMethod CreateDynamicMethod(Type delegateType)
102+
{
103+
if (!delegateType.IsSubclassOf(typeof(Delegate)))
104+
{
105+
throw new ArgumentException(SR.InvalidDelegateType, nameof(delegateType))
106+
.WithErrorInfo(
107+
nameof(SR.InvalidDelegateType),
108+
ErrorCategory.InvalidArgument,
109+
delegateType);
110+
}
111+
112+
MethodInfo? invokeMethod = delegateType.GetMethod(nameof(Action.Invoke));
113+
if (invokeMethod is null)
114+
{
115+
throw new ArgumentException(SR.DelegateTypeMissingInvoke, nameof(delegateType))
116+
.WithErrorInfo(
117+
nameof(SR.DelegateTypeMissingInvoke),
118+
ErrorCategory.InvalidArgument,
119+
delegateType);
120+
}
121+
122+
ParameterInfo[] parameters = invokeMethod.GetParameters();
123+
var parameterTypes = new Type[parameters.Length];
124+
for (int i = parameterTypes.Length - 1; i >= 0; i--)
125+
{
126+
parameterTypes[i] = parameters[i].ParameterType;
127+
}
128+
129+
return new DynamicMethod(
130+
invokeMethod.Name,
131+
invokeMethod.ReturnType,
132+
parameterTypes,
133+
typeof(CilAssemblage).Module,
134+
skipVisibility: true);
135+
}
136+
}
137+
}

src/ILAssembler/Commands/NewIlDelegateCommand.cs

Lines changed: 35 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,38 @@ public NewIlDelegateCommand()
3636

3737
protected override void BeginProcessing()
3838
{
39-
DynamicMethod? method;
39+
if (SessionState.LanguageMode is not PSLanguageMode.FullLanguage)
40+
{
41+
WriteError(
42+
new ErrorRecord(
43+
new PSInvalidOperationException(SR.FullLanguageRequired),
44+
nameof(SR.FullLanguageRequired),
45+
ErrorCategory.InvalidOperation,
46+
targetObject: null));
47+
return;
48+
}
49+
50+
Debug.Assert(Body is not null, "Engine should ensure body is not null.");
4051
try
4152
{
42-
method = CreateDynamicMethod();
43-
if (method is null)
53+
if (Signature is not null)
4454
{
55+
WriteObject(
56+
CilAssemblage.CreateDelegate(
57+
(ScriptBlockAst)Signature.Ast,
58+
(ScriptBlockAst)Body.Ast),
59+
enumerateCollection: false);
4560
return;
4661
}
4762

48-
CilAssembler.CompileTo(
49-
(ScriptBlockAst)Body!.Ast,
50-
method);
63+
Debug.Assert(
64+
DelegateType is not null,
65+
"Engine should ensure either Signature or DelegateType are not null.");
66+
67+
WriteObject(
68+
CilAssemblage.CreateDelegate(
69+
DelegateType,
70+
(ScriptBlockAst)Body.Ast));
5171
}
5272
catch (ILParseException ilParseException)
5373
{
@@ -57,7 +77,6 @@ protected override void BeginProcessing()
5777
ilParseException.Errors?.FirstOrDefault()?.ErrorId ?? "ILParseException",
5878
ErrorCategory.InvalidArgument,
5979
null));
60-
return;
6180
}
6281
catch (InvalidOperationException invalidOperation)
6382
{
@@ -76,11 +95,15 @@ protected override void BeginProcessing()
7695

7796
throw;
7897
}
79-
80-
Debug.Assert(DelegateType is not null, "DelegateType should not be null if method is not null.");
81-
try
98+
catch (ArgumentException argumentException)
8299
{
83-
WriteObject(method.CreateDelegate(DelegateType));
100+
if (argumentException.TryCreateErrorRecord(out ErrorRecord record))
101+
{
102+
WriteError(record);
103+
return;
104+
}
105+
106+
throw;
84107
}
85108
catch (BadImageFormatException badImageException)
86109
{
@@ -89,67 +112,8 @@ protected override void BeginProcessing()
89112
new PSArgumentException(SR.BadImageFormat, badImageException),
90113
nameof(SR.BadImageFormat),
91114
ErrorCategory.InvalidData,
92-
method));
115+
targetObject: null));
93116
}
94117
}
95-
96-
private DynamicMethod? CreateDynamicMethod()
97-
{
98-
if (DelegateType is not null)
99-
{
100-
if (!DelegateType.IsSubclassOf(typeof(Delegate)))
101-
{
102-
WriteError(
103-
new ErrorRecord(
104-
new PSArgumentException(SR.InvalidDelegateType, nameof(DelegateType)),
105-
nameof(SR.InvalidDelegateType),
106-
ErrorCategory.InvalidArgument,
107-
DelegateType));
108-
return null;
109-
}
110-
111-
MethodInfo? invokeMethod = DelegateType.GetMethod(nameof(Action.Invoke));
112-
if (invokeMethod is null)
113-
{
114-
WriteError(
115-
new ErrorRecord(
116-
new PSArgumentException(SR.DelegateTypeMissingInvoke, nameof(DelegateType)),
117-
nameof(SR.DelegateTypeMissingInvoke),
118-
ErrorCategory.InvalidArgument,
119-
DelegateType));
120-
return null;
121-
}
122-
123-
ParameterInfo[] parameters = invokeMethod.GetParameters();
124-
var parameterTypes = new Type[parameters.Length];
125-
for (int i = parameterTypes.Length - 1; i >= 0; i--)
126-
{
127-
parameterTypes[i] = parameters[i].ParameterType;
128-
}
129-
130-
return new DynamicMethod(
131-
invokeMethod.Name,
132-
invokeMethod.ReturnType,
133-
parameterTypes,
134-
typeof(NewIlDelegateCommand).Module,
135-
skipVisibility: true);
136-
}
137-
138-
var signatureParser = new MethodSignatureParser(
139-
rejectCtor: true,
140-
requireResolvableDeclaringType: false);
141-
142-
Signature!.Ast.Visit(signatureParser);
143-
var signature = (MethodIdentifier)signatureParser.GetMemberIdentifier(Signature!.Ast.Extent);
144-
145-
DelegateType = DelegateTypeFactory.GetDelegateType(signature);
146-
147-
return new DynamicMethod(
148-
signature.Name,
149-
signature.ReturnType.GetModifiedType(),
150-
signature.Parameters.ToModifiedTypeArray(),
151-
typeof(NewIlDelegateCommand).Module,
152-
skipVisibility: true);
153-
}
154118
}
155119
}

src/ILAssembler/ErrorRecordInfo.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Management.Automation;
2+
3+
namespace ILAssembler
4+
{
5+
internal sealed class ErrorRecordInfo
6+
{
7+
public ErrorRecordInfo(string id, ErrorCategory category, object? targetObject)
8+
{
9+
Id = id;
10+
Category = category;
11+
TargetObject = targetObject;
12+
}
13+
14+
public string Id { get; }
15+
16+
public ErrorCategory Category { get; }
17+
18+
public object? TargetObject { get; }
19+
}
20+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.Management.Automation;
3+
4+
namespace ILAssembler
5+
{
6+
internal static class ExceptionExtensions
7+
{
8+
public static TException WithErrorInfo<TException>(
9+
this TException exception,
10+
string? id = null,
11+
ErrorCategory category = ErrorCategory.NotSpecified,
12+
object? targetObject = null)
13+
where TException : Exception
14+
{
15+
exception.Data[typeof(ErrorRecordInfo)] = new ErrorRecordInfo(
16+
id ?? typeof(TException).Name,
17+
category,
18+
targetObject);
19+
20+
return exception;
21+
}
22+
23+
public static bool TryCreateErrorRecord(this Exception exception, out ErrorRecord errorRecord)
24+
{
25+
object? data = exception.Data[typeof(ErrorRecordInfo)];
26+
if (data is not ErrorRecordInfo info)
27+
{
28+
errorRecord = null!;
29+
return false;
30+
}
31+
32+
errorRecord = new ErrorRecord(
33+
exception,
34+
info.Id,
35+
info.Category,
36+
info.TargetObject);
37+
return true;
38+
}
39+
}
40+
}

src/ILAssembler/SR.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,7 @@
285285
<data name="BadImageFormat" xml:space="preserve">
286286
<value>The CLR reported an error while attempting to create a delegate for this method. See Get-Help about_BadImageFormat for more information.</value>
287287
</data>
288+
<data name="FullLanguageRequired" xml:space="preserve">
289+
<value>This command cannot be invoked in constrained or no language mode.</value>
290+
</data>
288291
</root>

0 commit comments

Comments
 (0)