Skip to content

Commit e758199

Browse files
committed
Rename & simplify types
* Calling `ByRefLikeProxy` et al. "proxies" could be misleading, since DynamicProxy proxies typically have the exact same public surface as the proxied types. This is not the case here, `ByRefLikeProxy` types come with their own distinct API. I am choosing "reference" because that's exactly what the types are. Alternatives considered were "value accessor" and "argument". The former would lead to long type names (`ByRefLikeValueAccessor`), and the latter would be inaccurate once we start using these types for `IInvocation.ReturnValue`, too. * There seems to be little benefit to having a parallel interface type hierarchy. On the contrary: users observing (say) a `SpanProxy` inst- ance in the debugger and then being told in an XML documentation comment to access it through the `ISpanProxy` interface doesn't seem particularly user-friendly. Let's go with the simplest solution: keep only the classes.
1 parent ff73c80 commit e758199

6 files changed

Lines changed: 118 additions & 156 deletions

File tree

src/Castle.Core/DynamicProxy/Internal/ByRefLikeProxy.cs renamed to src/Castle.Core/DynamicProxy/ByRefLikeReference.cs

Lines changed: 96 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717
#nullable enable
1818
#pragma warning disable CS8500
1919

20-
namespace Castle.DynamicProxy.Internal
20+
namespace Castle.DynamicProxy
2121
{
2222
using System;
2323
using System.ComponentModel;
24+
using System.Diagnostics.CodeAnalysis;
2425
using System.Threading;
2526

27+
using Castle.DynamicProxy.Internal;
28+
2629
// This file contains a set of `unsafe` types used at runtime by DynamicProxy proxies to represent by-ref-like values
2730
// in an `IInvocation`. Such values live exclusively on the evaluation stack and therefore cannot be boxed. Thus they are
2831
// in principle incompatible with `IInvocation` and we need to replace them with something else... namely these types here.
@@ -38,42 +41,46 @@ namespace Castle.DynamicProxy.Internal
3841
//
3942
// *) Unmanaged pointers can be safe when used to reference stack-allocated objects. However, that is only true
4043
// when they point into "live" stack frames. That is, they MUST NOT reference parameters or local variables
41-
// of methods that have already finished executing. This is why we have the `ByRefLikeProxy.Invalidate` method:
42-
// DynamicProxy (or whatever else instantiated a `ByRefLikeProxy` object to point at a method parameter or local
44+
// of methods that have already finished executing. This is why we have the `ByRefLikeReference.Invalidate` method:
45+
// DynamicProxy (or whatever else instantiated a `ByRefLikeReference` object to point at a method parameter or local
4346
// variable) must invoke this method before said method returns (or tail-calls).
4447
//
4548
// *) The `checkType` / `checkPtr` arguments of `GetPtr` or `Invalidate`, respectively, have two purposes:
4649
//
47-
// 1. DynamicProxy, or whatever else instantiated a `ByRefLikeProxy`, is expected to know at all times what
50+
// 1. DynamicProxy, or whatever else instantiated a `ByRefLikeReference`, is expected to know at all times what
4851
// exactly each instance references. These parameters make it harder for anyone to use the type directly
4952
// if they didn't also instantiate it themselves.
5053
//
5154
// 2. `checkPtr` of `Invalidate` attempts to prevent re-use of a referenced storage location for another
52-
// similarly-typed local variable by the JIT. DynamicProxy typically instantiates `ByRefLikeProxy` instances
55+
// similarly-typed local variable by the JIT. DynamicProxy typically instantiates `ByRefLikeReference` instances
5356
// at the start of intercepted method bodies, and it invokes `Invalidate` at the very end, meaning that
5457
// the address of the local/parameter is taken at each method boundary, meaning that static analysis should
5558
// never during the whole method see the local/parameter as "no longer in use". (This may be a little
5659
// paranoid, since the CoreCLR JIT probably exempts so-called "address-exposed" locals from reuse anyway.)
5760
//
5861
// *) Finally, we only ever access the unmanaged pointer field through `Volatile` or `Interlocked` to better guard
59-
// against cases where someone foolishly copied a `ByRefLikeProxy` instance out of the `IInvocation.Arguments`
62+
// against cases where someone foolishly copied a `ByRefLikeReference` instance out of the `IInvocation.Arguments`
6063
// and uses it from another thread.
6164
//
62-
// As far as I can reason, `ByRefLikeProxy` et al. should be safe to use IFF they are never copied out from an
65+
// As far as I can reason, `ByRefLikeReference` et al. should be safe to use IFF they are never copied out from an
6366
// `IInvocation`, and IFF DynamicProxy succeeds in destructing them and erasing them from the `IInvocation` right
6467
// before the intercepted method finishes executing.
6568

6669
/// <summary>
67-
/// Do not use! Only DynamicProxy internals may interact with this class type directly.
70+
/// Do not use! This type should only be used by DynamicProxy internals.
6871
/// </summary>
69-
[CLSCompliant(false)]
7072
[EditorBrowsable(EditorBrowsableState.Never)]
71-
public unsafe class ByRefLikeProxy
73+
public unsafe class ByRefLikeReference
7274
{
7375
private readonly Type type;
7476
private nint ptr;
7577

76-
public ByRefLikeProxy(Type type, void* ptr)
78+
/// <summary>
79+
/// Do not use! This constructor should only be called by DynamicProxy internals.
80+
/// </summary>
81+
[CLSCompliant(false)]
82+
[EditorBrowsable(EditorBrowsableState.Never)]
83+
public ByRefLikeReference(Type type, void* ptr)
7784
{
7885
if (type.IsByRefLikeSafe() == false)
7986
{
@@ -89,6 +96,11 @@ public ByRefLikeProxy(Type type, void* ptr)
8996
this.ptr = (nint)ptr;
9097
}
9198

99+
/// <summary>
100+
/// Do not use! This method should only be called by DynamicProxy internals.
101+
/// </summary>
102+
[CLSCompliant(false)]
103+
[EditorBrowsable(EditorBrowsableState.Never)]
92104
public void* GetPtr(Type checkType)
93105
{
94106
if (checkType != type)
@@ -111,6 +123,11 @@ public ByRefLikeProxy(Type type, void* ptr)
111123
return ptr;
112124
}
113125

126+
/// <summary>
127+
/// Do not use! This method should only be called by DynamicProxy internals.
128+
/// </summary>
129+
[CLSCompliant(false)]
130+
[EditorBrowsable(EditorBrowsableState.Never)]
114131
public void Invalidate(void* checkPtr)
115132
{
116133
var ptr = (void*)Interlocked.CompareExchange(ref this.ptr, (nint)null, (nint)checkPtr);
@@ -124,15 +141,29 @@ public void Invalidate(void* checkPtr)
124141

125142
#if NET9_0_OR_GREATER
126143
/// <summary>
127-
/// Access instances of this type through the public-facing <see cref="IByRefLikeProxy{TByRefLike}"/> interface.
128-
/// Only DynamicProxy internals may interact with this class type directly.
144+
/// Permits indirect access to by-ref-like argument values during method interception.
129145
/// </summary>
130-
[CLSCompliant(false)]
131-
[EditorBrowsable(EditorBrowsableState.Never)]
132-
public unsafe class ByRefLikeProxy<TByRefLike> : ByRefLikeProxy, IByRefLikeProxy<TByRefLike>
146+
/// <remarks>
147+
/// Instances of by-ref-like (<c>ref struct</c>) types live exclusively on the evaluation stack.
148+
/// Therefore, they cannot be boxed and put into the <see langword="object"/>-typed <see cref="IInvocation.Arguments"/> array.
149+
/// DynamicProxy replaces these unboxable values with <see cref="ByRefLikeReference{TByRefLike}"/> references
150+
/// (or, in the case of spans, with <see cref="SpanReference{T}"/> or <see cref="ReadOnlySpanReference{T}"/>),
151+
/// which grant you indirect read/write access to the actual values.
152+
/// <para>
153+
/// These references are only valid for the duration of the intercepted method call.
154+
/// Any attempt to use it beyond that will result in a <see cref="AccessViolationException"/>.
155+
/// </para>
156+
/// </remarks>
157+
/// <typeparam name="TByRefLike">A by-ref-like (<c>ref struct</c>) type.</typeparam>
158+
public unsafe class ByRefLikeReference<TByRefLike> : ByRefLikeReference
133159
where TByRefLike : struct, allows ref struct
134160
{
135-
public ByRefLikeProxy(Type type, void* ptr)
161+
/// <summary>
162+
/// Do not use! This constructor should only be called by DynamicProxy internals.
163+
/// </summary>
164+
[CLSCompliant(false)]
165+
[EditorBrowsable(EditorBrowsableState.Never)]
166+
public ByRefLikeReference(Type type, void* ptr)
136167
: base(type, ptr)
137168
{
138169
if (type != typeof(TByRefLike))
@@ -151,26 +182,33 @@ public ref TByRefLike Value
151182
}
152183
#endif
153184

154-
#if !NET9_0_OR_GREATER
155-
/// <summary>
156-
/// Access instances of this type through the public-facing <see cref="IReadOnlySpanProxy{T}"/> interface.
157-
/// Only DynamicProxy internals may interact with this class type directly.
158-
/// </summary>
159-
#else
160185
/// <summary>
161-
/// Access instances of this type through either of the public-facing
162-
/// <see cref="IReadOnlySpanProxy{T}"/> or <see cref="IByRefLikeProxy{TByRefLike}"/> interfaces.
163-
/// Only DynamicProxy internals may interact with this class type directly.
186+
/// Permits indirect access to <see cref="ReadOnlySpan{T}"/>-typed argument values during method interception.
164187
/// </summary>
165-
#endif
166-
[CLSCompliant(false)]
167-
[EditorBrowsable(EditorBrowsableState.Never)]
168-
public unsafe class ReadOnlySpanProxy<T> : ByRefLikeProxy, IReadOnlySpanProxy<T>
188+
/// <remarks>
189+
/// <see cref="ReadOnlySpan{T}"/> is a by-ref-like (<c>ref struct</c>) type, which means that
190+
/// instances of it live exclusively on the evaluation stack. Therefore, they cannot be boxed
191+
/// and put into the <see langword="object"/>-typed <see cref="IInvocation.Arguments"/> array.
192+
/// DynamicProxy replaces these unboxable values with instances of <see cref="ReadOnlySpanReference{T}"/>,
193+
/// which grant you indirect read/write access to the actual value.
194+
/// <para>
195+
/// These references are only valid for the duration of the intercepted method call.
196+
/// Any attempt to use it beyond that will result in a <see cref="AccessViolationException"/>.
197+
/// </para>
198+
/// </remarks>
199+
public unsafe class ReadOnlySpanReference<T>
169200
#if NET9_0_OR_GREATER
170-
, IByRefLikeProxy<ReadOnlySpan<T>>
201+
: ByRefLikeReference<ReadOnlySpan<T>>
202+
#else
203+
: ByRefLikeReference
171204
#endif
172205
{
173-
public ReadOnlySpanProxy(Type type, void* ptr)
206+
/// <summary>
207+
/// Do not use! This constructor should only be called by DynamicProxy internals.
208+
/// </summary>
209+
[CLSCompliant(false)]
210+
[EditorBrowsable(EditorBrowsableState.Never)]
211+
public ReadOnlySpanReference(Type type, void* ptr)
174212
: base(type, ptr)
175213
{
176214
if (type != typeof(ReadOnlySpan<T>))
@@ -179,35 +217,44 @@ public ReadOnlySpanProxy(Type type, void* ptr)
179217
}
180218
}
181219

220+
#if !NET9_0_OR_GREATER
182221
public ref ReadOnlySpan<T> Value
183222
{
184223
get
185224
{
186225
return ref *(ReadOnlySpan<T>*)GetPtrNocheck();
187226
}
188227
}
228+
#endif
189229
}
190230

191-
#if !NET9_0_OR_GREATER
192-
/// <summary>
193-
/// Access instances of this type through the public-facing <see cref="ISpanProxy{T}"/> interface.
194-
/// Only DynamicProxy internals may interact with this class type directly.
195-
/// </summary>
196-
#else
197231
/// <summary>
198-
/// Access instances of this type through either of the public-facing
199-
/// <see cref="ISpanProxy{T}"/> or <see cref="IByRefLikeProxy{TByRefLike}"/> interfaces.
200-
/// Only DynamicProxy internals may interact with this class type directly.
232+
/// Permits indirect access to <see cref="Span{T}"/>-typed argument values during method interception.
201233
/// </summary>
202-
#endif
203-
[CLSCompliant(false)]
204-
[EditorBrowsable(EditorBrowsableState.Never)]
205-
public unsafe class SpanProxy<T> : ByRefLikeProxy, ISpanProxy<T>
234+
/// <remarks>
235+
/// <see cref="Span{T}"/> is a by-ref-like (<c>ref struct</c>) type, which means that
236+
/// instances of it live exclusively on the evaluation stack. Therefore, they cannot be boxed
237+
/// and put into the <see langword="object"/>-typed <see cref="IInvocation.Arguments"/> array.
238+
/// DynamicProxy replaces these unboxable values with instances of <see cref="SpanReference{T}"/>,
239+
/// which grant you indirect read/write access to the actual value.
240+
/// <para>
241+
/// These references are only valid for the duration of the intercepted method call.
242+
/// Any attempt to use it beyond that will result in a <see cref="AccessViolationException"/>.
243+
/// </para>
244+
/// </remarks>
245+
public unsafe class SpanReference<T>
206246
#if NET9_0_OR_GREATER
207-
, IByRefLikeProxy<Span<T>>
247+
: ByRefLikeReference<Span<T>>
248+
#else
249+
: ByRefLikeReference
208250
#endif
209251
{
210-
public SpanProxy(Type type, void* ptr)
252+
/// <summary>
253+
/// Do not use! This constructor should only be called by DynamicProxy internals.
254+
/// </summary>
255+
[CLSCompliant(false)]
256+
[EditorBrowsable(EditorBrowsableState.Never)]
257+
public SpanReference(Type type, void* ptr)
211258
: base(type, ptr)
212259
{
213260
if (type != typeof(Span<T>))
@@ -216,13 +263,15 @@ public SpanProxy(Type type, void* ptr)
216263
}
217264
}
218265

266+
#if !NET9_0_OR_GREATER
219267
public ref Span<T> Value
220268
{
221269
get
222270
{
223271
return ref *(Span<T>*)GetPtrNocheck();
224272
}
225273
}
274+
#endif
226275
}
227276
}
228277

src/Castle.Core/DynamicProxy/Generators/Emitters/SimpleAST/ConvertArgumentFromObjectExpression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public void Emit(ILGenerator gen)
5050
{
5151
gen.Emit(OpCodes.Ldtoken, dereferencedArgumentType);
5252
gen.Emit(OpCodes.Call, TypeMethods.GetTypeFromHandle);
53-
gen.Emit(OpCodes.Call, ByRefLikeProxyMethods.GetPtr);
53+
gen.Emit(OpCodes.Call, ByRefLikeReferenceMethods.GetPtr);
5454
gen.Emit(OpCodes.Ldobj, dereferencedArgumentType);
5555
}
5656
else

src/Castle.Core/DynamicProxy/Generators/InvocationTypeGenerator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ public void CopyOut(out IExpression[] arguments, out LocalReference?[] byRefArgu
287287

288288
IExpression dereferencedArgument;
289289

290-
// Note that we don't need special logic for by-ref-like values / `ByRefLikeProxy` here,
290+
// Note that we don't need special logic for by-ref-like values / `ByRefLikeReference` here,
291291
// since `ConvertArgumentFromObjectExpression` knows how to deal with those.
292292

293293
dereferencedArgument = new ConvertArgumentFromObjectExpression(
@@ -322,7 +322,7 @@ public void CopyIn(LocalReference?[] byRefArguments)
322322
#if FEATURE_BYREFLIKE
323323
if (localCopy.Type.IsByRefLikeSafe())
324324
{
325-
// For by-ref-like values, a `ByRefLikeProxy` has previously been placed in `IInvocation.Arguments`.
325+
// For by-ref-like values, a `ByRefLikeReference` has previously been placed in `IInvocation.Arguments`.
326326
// We must not replace that proxy, but use it to update the referenced by-ref-like parameter:
327327
method.CodeBuilder.AddStatement(
328328
new AssignStatement(
@@ -332,7 +332,7 @@ public void CopyIn(LocalReference?[] byRefArguments)
332332
ThisExpression.Instance,
333333
InvocationMethods.GetArgumentValue,
334334
new LiteralIntExpression(i)),
335-
ByRefLikeProxyMethods.GetPtr,
335+
ByRefLikeReferenceMethods.GetPtr,
336336
new TypeTokenExpression(localCopy.Type)),
337337
localCopy.Type),
338338
localCopy));
@@ -354,7 +354,7 @@ public void SetReturnValue(LocalReference returnValue)
354354
{
355355
#if FEATURE_BYREFLIKE
356356
// TODO: For by-ref-like return values, we will need to read `IInvocation.ReturnValue`
357-
// and set the return value via pointer indirection (`ByRefLikeProxy.GetPtr`).
357+
// and set the return value via pointer indirection (`ByRefLikeReference.GetPtr`).
358358
#endif
359359

360360
method.CodeBuilder.AddStatement(new MethodInvocationExpression(

0 commit comments

Comments
 (0)