@@ -14,15 +14,21 @@ This Source Code Form is subject to the terms of the Mozilla Public
1414namespace PhpSerializerNET {
1515 internal class PhpDeserializer {
1616 private readonly PhpDeserializationOptions _options ;
17- private PhpSerializeToken _token ;
17+ private readonly PhpSerializeToken _token ;
18+
1819 private static readonly Dictionary < string , Type > TypeLookupCache = new ( ) {
1920 { "DateTime" , typeof ( PhpDateTime ) }
2021 } ;
22+ private static readonly object TypeLookupCacheSyncObject = new ( ) ;
2123
2224 private static readonly Dictionary < Type , Dictionary < string , PropertyInfo > > PropertyInfoCache = new ( ) ;
25+ private static readonly object PropertyInfoCacheSyncObject = new ( ) ;
2326
2427 private static Dictionary < Type , Dictionary < string , FieldInfo > > FieldInfoCache { get ; set ; } = new ( ) ;
28+ private static readonly object FieldInfoCacheCacheSyncObject = new ( ) ;
29+
2530 private static Dictionary < Type , Dictionary < string , FieldInfo > > EnumInfoCache { get ; set ; } = new ( ) ;
31+ private static readonly object EnumInfoCacheSyncObject = new ( ) ;
2632
2733 public PhpDeserializer ( string input , PhpDeserializationOptions options ) {
2834 _options = options ;
@@ -49,17 +55,24 @@ public T Deserialize<T>() {
4955 /// Can be useful for scenarios in which new types are loaded at runtime in between deserialization tasks.
5056 /// </summary>
5157 public static void ClearTypeCache ( ) {
52- TypeLookupCache . Clear ( ) ;
53- TypeLookupCache . Add ( "DateTime" , typeof ( PhpDateTime ) ) ;
58+ lock ( TypeLookupCache ) {
59+ TypeLookupCache . Clear ( ) ;
60+ TypeLookupCache . Add ( "DateTime" , typeof ( PhpDateTime ) ) ;
61+ }
5462 }
5563
5664 /// <summary>
5765 /// Reset the property info cache.
5866 /// Can be useful for scenarios in which new types are loaded at runtime in between deserialization tasks.
5967 /// </summary>
6068 public static void ClearPropertyInfoCache ( ) {
61- PropertyInfoCache . Clear ( ) ;
62- EnumInfoCache . Clear ( ) ;
69+ lock ( PropertyInfoCacheSyncObject ) {
70+ PropertyInfoCache . Clear ( ) ;
71+ }
72+
73+ lock ( EnumInfoCacheSyncObject ) {
74+ EnumInfoCache . Clear ( ) ;
75+ }
6376 }
6477
6578 private object DeserializeToken ( PhpSerializeToken token ) {
@@ -89,21 +102,30 @@ private object MakeClass(PhpSerializeToken token) {
89102 var typeName = token . Value ;
90103 object constructedObject ;
91104 Type targetType = null ;
92- if ( typeName != "sdtClass" && this . _options . EnableTypeLookup ) {
93- if ( TypeLookupCache . ContainsKey ( typeName ) ) {
94- targetType = TypeLookupCache [ typeName ] ;
95- } else {
96- foreach ( var assembly in AppDomain . CurrentDomain . GetAssemblies ( ) . Where ( p => ! p . IsDynamic ) ) {
97- // TODO: PhpClass attribute should win over classes who happen to have the name...?
98- targetType = assembly
99- . GetExportedTypes ( )
100- . FirstOrDefault ( y => y . Name == typeName || y . GetCustomAttribute < PhpClass > ( ) ? . Name == typeName ) ;
101- if ( targetType != null ) {
102- break ;
105+ if ( typeName != "stdClass" && this . _options . EnableTypeLookup ) {
106+ lock ( TypeLookupCacheSyncObject )
107+ {
108+ if ( TypeLookupCache . ContainsKey ( typeName ) ) {
109+ targetType = TypeLookupCache [ typeName ] ;
110+ } else {
111+ foreach ( var assembly in AppDomain . CurrentDomain . GetAssemblies ( ) . Where ( p => ! p . IsDynamic ) ) {
112+
113+ targetType = assembly
114+ . GetExportedTypes ( )
115+ . Select ( type => new { type , attribute = type . GetCustomAttribute < PhpClass > ( ) } )
116+ // PhpClass attribute should win over classes who happen to have the name
117+ . OrderBy ( c => c . attribute != null ? 0 : 1 )
118+ . Where ( y => y . type . Name == typeName || y . attribute ? . Name == typeName )
119+ . Select ( c => c . type )
120+ . FirstOrDefault ( ) ;
121+
122+ if ( targetType != null ) {
123+ break ;
124+ }
125+ }
126+ if ( this . _options . TypeCache . HasFlag ( TypeCacheFlag . ClassNames ) ) {
127+ TypeLookupCache . Add ( typeName , targetType ) ;
103128 }
104- }
105- if ( this . _options . TypeCache . HasFlag ( TypeCacheFlag . ClassNames ) ) {
106- TypeLookupCache . Add ( typeName , targetType ) ;
107129 }
108130 }
109131 }
@@ -198,7 +220,8 @@ private object DeserializeInteger(Type targetType, PhpSerializeToken token) {
198220 private object DeserializeDouble ( Type targetType , PhpSerializeToken token ) {
199221 if ( targetType == typeof ( double ) || targetType == typeof ( float ) ) {
200222 return token . ToDouble ( ) ;
201- } ;
223+ }
224+
202225 token . Value = token . Value switch {
203226 "INF" => double . PositiveInfinity . ToString ( CultureInfo . InvariantCulture ) ,
204227 "-INF" => double . NegativeInfinity . ToString ( CultureInfo . InvariantCulture ) ,
@@ -259,11 +282,13 @@ private object DeserializeTokenFromSimpleType(Type givenType, PhpSerializeToken
259282
260283 FieldInfo foundFieldInfo = null ;
261284 if ( this . _options . TypeCache . HasFlag ( TypeCacheFlag . PropertyInfo ) ) {
262- if ( ! EnumInfoCache . ContainsKey ( targetType ) ) {
263- EnumInfoCache . Add ( targetType , new ( ) ) ;
264- }
265- if ( EnumInfoCache [ targetType ] . ContainsKey ( token . Value ) ) {
266- foundFieldInfo = EnumInfoCache [ targetType ] [ token . Value ] ;
285+ lock ( EnumInfoCacheSyncObject ) {
286+ if ( ! EnumInfoCache . ContainsKey ( targetType ) ) {
287+ EnumInfoCache . Add ( targetType , new ( ) ) ;
288+ }
289+ if ( EnumInfoCache [ targetType ] . ContainsKey ( token . Value ) ) {
290+ foundFieldInfo = EnumInfoCache [ targetType ] [ token . Value ] ;
291+ }
267292 }
268293 }
269294
@@ -274,7 +299,9 @@ private object DeserializeTokenFromSimpleType(Type givenType, PhpSerializeToken
274299 . FirstOrDefault ( c => c . fieldInfo . Name == token . Value || c . phpPropertyAttribute != null && c . phpPropertyAttribute . Name == token . Value )
275300 ? . fieldInfo ;
276301 if ( this . _options . TypeCache . HasFlag ( TypeCacheFlag . PropertyInfo ) ) {
277- EnumInfoCache [ targetType ] . Add ( token . Value , foundFieldInfo ) ;
302+ lock ( EnumInfoCacheSyncObject ) {
303+ EnumInfoCache [ targetType ] . Add ( token . Value , foundFieldInfo ) ;
304+ }
278305 }
279306 }
280307
@@ -326,12 +353,14 @@ private object DeserializeTokenFromSimpleType(Type givenType, PhpSerializeToken
326353 private object MakeStruct ( Type targetType , PhpSerializeToken token ) {
327354 var result = Activator . CreateInstance ( targetType ) ;
328355 Dictionary < string , FieldInfo > fields ;
329- if ( FieldInfoCache . ContainsKey ( targetType ) ) {
330- fields = FieldInfoCache [ targetType ] ;
331- } else {
332- fields = targetType . GetFields ( ) . GetAllFields ( this . _options ) ;
333- if ( this . _options . TypeCache . HasFlag ( TypeCacheFlag . PropertyInfo ) ) {
334- FieldInfoCache . Add ( targetType , fields ) ;
356+ lock ( FieldInfoCacheCacheSyncObject ) {
357+ if ( FieldInfoCache . ContainsKey ( targetType ) ) {
358+ fields = FieldInfoCache [ targetType ] ;
359+ } else {
360+ fields = targetType . GetFields ( ) . GetAllFields ( this . _options ) ;
361+ if ( this . _options . TypeCache . HasFlag ( TypeCacheFlag . PropertyInfo ) ) {
362+ FieldInfoCache . Add ( targetType , fields ) ;
363+ }
335364 }
336365 }
337366
@@ -365,12 +394,14 @@ private object MakeStruct(Type targetType, PhpSerializeToken token) {
365394 private object MakeObject ( Type targetType , PhpSerializeToken token ) {
366395 var result = Activator . CreateInstance ( targetType ) ;
367396 Dictionary < string , PropertyInfo > properties ;
368- if ( PropertyInfoCache . ContainsKey ( targetType ) ) {
369- properties = PropertyInfoCache [ targetType ] ;
370- } else {
371- properties = targetType . GetProperties ( ) . GetAllProperties ( this . _options ) ;
372- if ( this . _options . TypeCache . HasFlag ( TypeCacheFlag . PropertyInfo ) ) {
373- PropertyInfoCache . Add ( targetType , properties ) ;
397+ lock ( PropertyInfoCacheSyncObject ) {
398+ if ( PropertyInfoCache . ContainsKey ( targetType ) ) {
399+ properties = PropertyInfoCache [ targetType ] ;
400+ } else {
401+ properties = targetType . GetProperties ( ) . GetAllProperties ( this . _options ) ;
402+ if ( this . _options . TypeCache . HasFlag ( TypeCacheFlag . PropertyInfo ) ) {
403+ PropertyInfoCache . Add ( targetType , properties ) ;
404+ }
374405 }
375406 }
376407
@@ -405,7 +436,7 @@ private object MakeObject(Type targetType, PhpSerializeToken token) {
405436 }
406437
407438 private object MakeArray ( Type targetType , PhpSerializeToken token ) {
408- var elementType = targetType . GetElementType ( ) ;
439+ var elementType = targetType . GetElementType ( ) ?? throw new InvalidOperationException ( "targetType.GetElementType() returned null" ) ;
409440 Array result = System . Array . CreateInstance ( elementType , token . Children . Length / 2 ) ;
410441
411442 var arrayIndex = 0 ;
0 commit comments