@@ -207,6 +207,177 @@ public void ClearReflectionCache_ShouldClearBothCaches()
207207 Assert . Equal ( 0 , propCache . Count ) ;
208208 }
209209
210+ #region Pointer Access Tests
211+
212+ // Test entity with pointer fields and properties
213+ private unsafe class TestEntityWithPointers
214+ {
215+ #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value
216+ public int PublicField ;
217+ public int * PointerField ;
218+ public void * VoidPointerField ;
219+ #pragma warning restore CS0649
220+ public int PublicProp { get ; set ; }
221+ public unsafe int * PointerProp { get ; set ; }
222+ public unsafe void * VoidPointerProp { get ; set ; }
223+ }
224+
225+ // Converter that allows pointer access (default behavior)
226+ private class PointerAllowedConverter : BaseReflectionConverter < TestEntityWithPointers >
227+ {
228+ public override bool AllowPointerFieldsAccess => true ;
229+ public override bool AllowPointerPropertiesAccess => true ;
230+
231+ protected override SerializedMember InternalSerialize (
232+ Reflector reflector , object ? obj , Type type , string ? name , bool recursive ,
233+ BindingFlags flags , int depth , Logs ? logs , ILogger ? logger , SerializationContext ? context )
234+ => new SerializedMember ( ) ;
235+
236+ protected override bool SetValue (
237+ Reflector reflector , ref object ? instance , Type type , JsonElement ? element ,
238+ int depth , Logs ? logs , ILogger ? logger )
239+ => true ;
240+
241+ public override bool SetField (
242+ Reflector reflector , ref object ? instance , Type type , FieldInfo field ,
243+ SerializedMember ? member , int depth , Logs ? logs , BindingFlags flags , ILogger ? logger )
244+ => true ;
245+
246+ public override bool SetProperty (
247+ Reflector reflector , ref object ? instance , Type type , PropertyInfo prop ,
248+ SerializedMember ? member , int depth , Logs ? logs , BindingFlags flags , ILogger ? logger )
249+ => true ;
250+ }
251+
252+ // Converter that disallows pointer access
253+ private class PointerDisallowedConverter : BaseReflectionConverter < TestEntityWithPointers >
254+ {
255+ public override bool AllowPointerFieldsAccess => false ;
256+ public override bool AllowPointerPropertiesAccess => false ;
257+
258+ protected override SerializedMember InternalSerialize (
259+ Reflector reflector , object ? obj , Type type , string ? name , bool recursive ,
260+ BindingFlags flags , int depth , Logs ? logs , ILogger ? logger , SerializationContext ? context )
261+ => new SerializedMember ( ) ;
262+
263+ protected override bool SetValue (
264+ Reflector reflector , ref object ? instance , Type type , JsonElement ? element ,
265+ int depth , Logs ? logs , ILogger ? logger )
266+ => true ;
267+
268+ public override bool SetField (
269+ Reflector reflector , ref object ? instance , Type type , FieldInfo field ,
270+ SerializedMember ? member , int depth , Logs ? logs , BindingFlags flags , ILogger ? logger )
271+ => true ;
272+
273+ public override bool SetProperty (
274+ Reflector reflector , ref object ? instance , Type type , PropertyInfo prop ,
275+ SerializedMember ? member , int depth , Logs ? logs , BindingFlags flags , ILogger ? logger )
276+ => true ;
277+ }
278+
279+ [ Fact ]
280+ public void AllowPointerFieldsAccess_WhenTrue_ShouldIncludePointerFields ( )
281+ {
282+ // Arrange
283+ var converter = new PointerAllowedConverter ( ) ;
284+ var reflector = new Reflector ( ) ;
285+ var type = typeof ( TestEntityWithPointers ) ;
286+ var flags = BindingFlags . Public | BindingFlags . Instance ;
287+
288+ // Act
289+ var fields = converter . GetSerializableFields ( reflector , type , flags ) ? . ToList ( ) ;
290+
291+ // Assert
292+ Assert . NotNull ( fields ) ;
293+ Assert . Equal ( 3 , fields . Count ) ; // PublicField, PointerField, VoidPointerField
294+ Assert . Contains ( fields , f => f . Name == nameof ( TestEntityWithPointers . PublicField ) ) ;
295+ Assert . Contains ( fields , f => f . Name == nameof ( TestEntityWithPointers . PointerField ) ) ;
296+ Assert . Contains ( fields , f => f . Name == nameof ( TestEntityWithPointers . VoidPointerField ) ) ;
297+ }
298+
299+ [ Fact ]
300+ public void AllowPointerFieldsAccess_WhenFalse_ShouldExcludePointerFields ( )
301+ {
302+ // Arrange
303+ var converter = new PointerDisallowedConverter ( ) ;
304+ var reflector = new Reflector ( ) ;
305+ var type = typeof ( TestEntityWithPointers ) ;
306+ var flags = BindingFlags . Public | BindingFlags . Instance ;
307+
308+ // Act
309+ var fields = converter . GetSerializableFields ( reflector , type , flags ) ? . ToList ( ) ;
310+
311+ // Assert
312+ Assert . NotNull ( fields ) ;
313+ Assert . Single ( fields ) ; // Only PublicField
314+ Assert . Contains ( fields , f => f . Name == nameof ( TestEntityWithPointers . PublicField ) ) ;
315+ Assert . DoesNotContain ( fields , f => f . Name == nameof ( TestEntityWithPointers . PointerField ) ) ;
316+ Assert . DoesNotContain ( fields , f => f . Name == nameof ( TestEntityWithPointers . VoidPointerField ) ) ;
317+ }
318+
319+ [ Fact ]
320+ public void AllowPointerPropertiesAccess_WhenTrue_ShouldIncludePointerProperties ( )
321+ {
322+ // Arrange
323+ var converter = new PointerAllowedConverter ( ) ;
324+ var reflector = new Reflector ( ) ;
325+ var type = typeof ( TestEntityWithPointers ) ;
326+ var flags = BindingFlags . Public | BindingFlags . Instance ;
327+
328+ // Act
329+ var props = converter . GetSerializableProperties ( reflector , type , flags ) ? . ToList ( ) ;
330+
331+ // Assert
332+ Assert . NotNull ( props ) ;
333+ Assert . Equal ( 3 , props . Count ) ; // PublicProp, PointerProp, VoidPointerProp
334+ Assert . Contains ( props , p => p . Name == nameof ( TestEntityWithPointers . PublicProp ) ) ;
335+ Assert . Contains ( props , p => p . Name == nameof ( TestEntityWithPointers . PointerProp ) ) ;
336+ Assert . Contains ( props , p => p . Name == nameof ( TestEntityWithPointers . VoidPointerProp ) ) ;
337+ }
338+
339+ [ Fact ]
340+ public void AllowPointerPropertiesAccess_WhenFalse_ShouldExcludePointerProperties ( )
341+ {
342+ // Arrange
343+ var converter = new PointerDisallowedConverter ( ) ;
344+ var reflector = new Reflector ( ) ;
345+ var type = typeof ( TestEntityWithPointers ) ;
346+ var flags = BindingFlags . Public | BindingFlags . Instance ;
347+
348+ // Act
349+ var props = converter . GetSerializableProperties ( reflector , type , flags ) ? . ToList ( ) ;
350+
351+ // Assert
352+ Assert . NotNull ( props ) ;
353+ Assert . Single ( props ) ; // Only PublicProp
354+ Assert . Contains ( props , p => p . Name == nameof ( TestEntityWithPointers . PublicProp ) ) ;
355+ Assert . DoesNotContain ( props , p => p . Name == nameof ( TestEntityWithPointers . PointerProp ) ) ;
356+ Assert . DoesNotContain ( props , p => p . Name == nameof ( TestEntityWithPointers . VoidPointerProp ) ) ;
357+ }
358+
359+ [ Fact ]
360+ public void AllowPointerFieldsAccess_DefaultValue_ShouldBeTrue ( )
361+ {
362+ // Arrange
363+ var converter = new TestableReflectionConverter ( ) ;
364+
365+ // Assert
366+ Assert . True ( converter . AllowPointerFieldsAccess ) ;
367+ }
368+
369+ [ Fact ]
370+ public void AllowPointerPropertiesAccess_DefaultValue_ShouldBeTrue ( )
371+ {
372+ // Arrange
373+ var converter = new TestableReflectionConverter ( ) ;
374+
375+ // Assert
376+ Assert . True ( converter . AllowPointerPropertiesAccess ) ;
377+ }
378+
379+ #endregion
380+
210381 // Helper methods to access private caches via reflection
211382 private LruCache < ( Type , BindingFlags ) , FieldInfo [ ] > GetFieldCache ( object converter )
212383 {
0 commit comments