Skip to content

Commit 48ccf5d

Browse files
committed
Define internal ByRefLikeProxy classes & public-facing IByRefLikeProxy interfaces
1 parent 02cce94 commit 48ccf5d

2 files changed

Lines changed: 324 additions & 0 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2004-2026 Castle Project - http://www.castleproject.org/
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#if FEATURE_BYREFLIKE
16+
17+
#nullable enable
18+
19+
using System;
20+
21+
namespace Castle.DynamicProxy
22+
{
23+
#if NET9_0_OR_GREATER
24+
/// <summary>
25+
/// Permits indirect access to by-ref-like argument values during method interception.
26+
/// </summary>
27+
/// <remarks>
28+
/// Instances of by-ref-like (<c>ref struct</c>) types live exclusively on the evaluation stack.
29+
/// Therefore, they cannot be boxed and put into the <see langword="object"/>-typed <see cref="IInvocation.Arguments"/> array.
30+
/// DynamicProxy replaces these unboxable values with <see cref="IByRefLikeProxy{TByRefLike}"/> references
31+
/// (or alternatively, in the case of spans, with <see cref="ISpanProxy{T}"/> or <see cref="IReadOnlySpanProxy{T}"/>),
32+
/// which grant you indirect read/write access to the actual values.
33+
/// <para>
34+
/// These references are only valid for the duration of the intercepted method call.
35+
/// Any attempt to use it beyond that will result in a <see cref="AccessViolationException"/>.
36+
/// </para>
37+
/// </remarks>
38+
/// <typeparam name="TByRefLike">A by-ref-like (<c>ref struct</c>) type.</typeparam>
39+
public interface IByRefLikeProxy<TByRefLike> where TByRefLike : struct, allows ref struct
40+
{
41+
TByRefLike Get();
42+
void Set(in TByRefLike value);
43+
}
44+
#endif
45+
46+
/// <summary>
47+
/// Permits indirect access to <see cref="ReadOnlySpan{T}"/>-typed argument values during method interception.
48+
/// </summary>
49+
/// <remarks>
50+
/// <see cref="ReadOnlySpan{T}"/> is a by-ref-like (<c>ref struct</c>) type, which means that
51+
/// instances of it live exclusively on the evaluation stack. Therefore, they cannot be boxed
52+
/// and put into the <see langword="object"/>-typed <see cref="IInvocation.Arguments"/> array.
53+
/// DynamicProxy replaces these unboxable values with <see cref="IReadOnlySpanProxy{T}"/> references,
54+
/// which grant you indirect read/write access to the actual value.
55+
/// <para>
56+
/// These references are only valid for the duration of the intercepted method call.
57+
/// Any attempt to use it beyond that will result in a <see cref="AccessViolationException"/>.
58+
/// </para>
59+
/// </remarks>
60+
public interface IReadOnlySpanProxy<T>
61+
{
62+
ReadOnlySpan<T> Get();
63+
void Set(in ReadOnlySpan<T> value);
64+
}
65+
66+
/// <summary>
67+
/// Permits indirect access to <see cref="Span{T}"/>-typed argument values during method interception.
68+
/// </summary>
69+
/// <remarks>
70+
/// <see cref="Span{T}"/> is a by-ref-like (<c>ref struct</c>) type, which means that
71+
/// instances of it live exclusively on the evaluation stack. Therefore, they cannot be boxed
72+
/// and put into the <see langword="object"/>-typed <see cref="IInvocation.Arguments"/> array.
73+
/// DynamicProxy replaces these unboxable values with <see cref="ISpanProxy{T}"/> references,
74+
/// which grant you indirect read/write access to the actual value.
75+
/// <para>
76+
/// These references are only valid for the duration of the intercepted method call.
77+
/// Any attempt to use it beyond that will result in a <see cref="AccessViolationException"/>.
78+
/// </para>
79+
/// </remarks>
80+
public interface ISpanProxy<T>
81+
{
82+
Span<T> Get();
83+
void Set(in Span<T> value);
84+
}
85+
}
86+
87+
#endif
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// Copyright 2004-2026 Castle Project - http://www.castleproject.org/
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#if FEATURE_BYREFLIKE
16+
17+
#nullable enable
18+
#pragma warning disable CS8500
19+
20+
namespace Castle.DynamicProxy.Internal
21+
{
22+
using System;
23+
using System.ComponentModel;
24+
using System.Threading;
25+
26+
// This file contains a set of `unsafe` types used at runtime by DynamicProxy proxies to represent by-ref-like values
27+
// in an `IInvocation`. Such values live exclusively on the evaluation stack and therefore cannot be boxed. Thus they are
28+
// in principle incompatible with `IInvocation` and we need to replace them with something else... namely these types here.
29+
//
30+
// What follows are the safety considerations that went into the design of these types.
31+
//
32+
// *) These types use unmanaged pointers (`void*`) to reference storage locations (of by-ref-like method parameters).
33+
//
34+
// *) Unmanaged pointers are generally unsafe when used to reference unpinned heap-allocated objects.
35+
// These types here should NEVER reference heap-allocated objects. We attempt to enforce this by asking for the
36+
// `type` of the storage location, and throw for anything other than by-ref-like types (which by definition cannot
37+
// live on the heap).
38+
//
39+
// *) Unmanaged pointers can be safe when used to reference stack-allocated objects. However, that is only true
40+
// 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
43+
// variable) must invoke this method before said method returns (or tail-calls).
44+
//
45+
// *) The `checkType` / `checkPtr` arguments of `GetPtr` or `Invalidate`, respectively, have two purposes:
46+
//
47+
// 1. DynamicProxy, or whatever else instantiated a `ByRefLikeProxy`, is expected to know at all times what
48+
// exactly each instance references. These parameters make it harder for anyone to use the type directly
49+
// if they didn't also instantiate it themselves.
50+
//
51+
// 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
53+
// at the start of intercepted method bodies, and it invokes `Invalidate` at the very end, meaning that
54+
// the address of the local/parameter is taken at each method boundary, meaning that static analysis should
55+
// never during the whole method see the local/parameter as "no longer in use". (This may be a little
56+
// paranoid, since the CoreCLR JIT probably exempts so-called "address-exposed" locals from reuse anyway.)
57+
//
58+
// *) 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`
60+
// and uses it from another thread.
61+
//
62+
// As far as I can reason, `ByRefLikeProxy` et al. should be safe to use IFF they are never copied out from an
63+
// `IInvocation`, and IFF DynamicProxy succeeds in destructing them and erasing them from the `IInvocation` right
64+
// before the intercepted method finishes executing.
65+
66+
/// <summary>
67+
/// Do not use! Only DynamicProxy internals may interact with this class type directly.
68+
/// </summary>
69+
[CLSCompliant(false)]
70+
[EditorBrowsable(EditorBrowsableState.Never)]
71+
public unsafe class ByRefLikeProxy
72+
{
73+
private readonly Type type;
74+
private nint ptr;
75+
76+
public ByRefLikeProxy(Type type, void* ptr)
77+
{
78+
if (type.IsByRefLikeSafe() == false)
79+
{
80+
throw new ArgumentOutOfRangeException(nameof(type));
81+
}
82+
83+
if (ptr == null)
84+
{
85+
throw new ArgumentNullException(nameof(ptr));
86+
}
87+
88+
this.type = type;
89+
this.ptr = (nint)ptr;
90+
}
91+
92+
public void* GetPtr(Type checkType)
93+
{
94+
if (checkType != type)
95+
{
96+
throw new AccessViolationException();
97+
}
98+
99+
return GetPtrNocheck();
100+
}
101+
102+
internal void* GetPtrNocheck()
103+
{
104+
var ptr = (void*)Volatile.Read(ref this.ptr);
105+
106+
if (ptr == null)
107+
{
108+
throw new AccessViolationException();
109+
}
110+
111+
return ptr;
112+
}
113+
114+
public void Invalidate(void* checkPtr)
115+
{
116+
var ptr = (void*)Interlocked.CompareExchange(ref this.ptr, (nint)null, (nint)checkPtr);
117+
118+
if (ptr == null || checkPtr != ptr)
119+
{
120+
throw new AccessViolationException();
121+
}
122+
}
123+
}
124+
125+
#if NET9_0_OR_GREATER
126+
/// <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.
129+
/// </summary>
130+
[CLSCompliant(false)]
131+
[EditorBrowsable(EditorBrowsableState.Never)]
132+
public unsafe class ByRefLikeProxy<TByRefLike> : ByRefLikeProxy, IByRefLikeProxy<TByRefLike>
133+
where TByRefLike : struct, allows ref struct
134+
{
135+
public ByRefLikeProxy(Type type, void* ptr)
136+
: base(type, ptr)
137+
{
138+
if (type != typeof(TByRefLike))
139+
{
140+
throw new ArgumentOutOfRangeException(nameof(type));
141+
}
142+
}
143+
144+
public TByRefLike Get()
145+
{
146+
return *(TByRefLike*)GetPtrNocheck();
147+
}
148+
149+
public void Set(in TByRefLike value)
150+
{
151+
*(TByRefLike*)GetPtrNocheck() = value;
152+
}
153+
}
154+
#endif
155+
156+
#if !NET9_0_OR_GREATER
157+
/// <summary>
158+
/// Access instances of this type through the public-facing <see cref="IReadOnlySpanProxy{TByRefLike}"/> interface.
159+
/// Only DynamicProxy internals may interact with this class type directly.
160+
/// </summary>
161+
#else
162+
/// <summary>
163+
/// Access instances of this type through either of the public-facing
164+
/// <see cref="IReadOnlySpanProxy{TByRefLike}"/> or <see cref="IByRefLikeProxy{TByRefLike}"/> interfaces.
165+
/// Only DynamicProxy internals may interact with this class type directly.
166+
/// </summary>
167+
#endif
168+
[CLSCompliant(false)]
169+
[EditorBrowsable(EditorBrowsableState.Never)]
170+
public unsafe class ReadOnlySpanProxy<T> : ByRefLikeProxy, IReadOnlySpanProxy<T>
171+
#if NET9_0_OR_GREATER
172+
, IByRefLikeProxy<ReadOnlySpan<T>>
173+
#endif
174+
{
175+
public ReadOnlySpanProxy(Type type, void* ptr)
176+
: base(type, ptr)
177+
{
178+
if (type != typeof(ReadOnlySpan<T>))
179+
{
180+
throw new ArgumentOutOfRangeException(nameof(type));
181+
}
182+
}
183+
184+
public ReadOnlySpan<T> Get()
185+
{
186+
return *(ReadOnlySpan<T>*)GetPtrNocheck();
187+
}
188+
189+
public void Set(in ReadOnlySpan<T> value)
190+
{
191+
*(ReadOnlySpan<T>*)GetPtrNocheck() = value;
192+
}
193+
}
194+
195+
#if !NET9_0_OR_GREATER
196+
/// <summary>
197+
/// Access instances of this type through the public-facing <see cref="ISpanProxy{TByRefLike}"/> interface.
198+
/// Only DynamicProxy internals may interact with this class type directly.
199+
/// </summary>
200+
#else
201+
/// <summary>
202+
/// Access instances of this type through either of the public-facing
203+
/// <see cref="ISpanProxy{TByRefLike}"/> or <see cref="IByRefLikeProxy{TByRefLike}"/> interfaces.
204+
/// Only DynamicProxy internals may interact with this class type directly.
205+
/// </summary>
206+
#endif
207+
[CLSCompliant(false)]
208+
[EditorBrowsable(EditorBrowsableState.Never)]
209+
public unsafe class SpanProxy<T> : ByRefLikeProxy, ISpanProxy<T>
210+
#if NET9_0_OR_GREATER
211+
, IByRefLikeProxy<Span<T>>
212+
#endif
213+
{
214+
public SpanProxy(Type type, void* ptr)
215+
: base(type, ptr)
216+
{
217+
if (type != typeof(Span<T>))
218+
{
219+
throw new ArgumentOutOfRangeException(nameof(type));
220+
}
221+
}
222+
223+
public Span<T> Get()
224+
{
225+
return *(Span<T>*)GetPtrNocheck();
226+
}
227+
228+
public void Set(in Span<T> value)
229+
{
230+
*(Span<T>*)GetPtrNocheck() = value;
231+
}
232+
}
233+
}
234+
235+
#pragma warning restore CS8500
236+
237+
#endif

0 commit comments

Comments
 (0)