99using System . IO ;
1010using System . Runtime . CompilerServices ;
1111using System . Text ;
12+ using System . Threading ;
1213
1314namespace System . Resources
1415#if RESOURCES_EXTENSIONS
@@ -60,10 +61,11 @@ public sealed partial class
6061 // it make sense to use anything less than one page?
6162 private const int DefaultFileStreamBufferSize = 4096 ;
6263
63- private BinaryReader _store ; // backing store we're reading from.
64- // Used by RuntimeResourceSet and this class's enumerator. Maps
65- // resource name to a value, a ResourceLocator, or a
66- // LooselyLinkedManifestResource.
64+ // Backing store we're reading from. Usages outside of constructor
65+ // initialization must be protected by lock (this).
66+ private BinaryReader _store ;
67+ // Used by RuntimeResourceSet and this class's enumerator.
68+ // Accesses must be protected by lock(_resCache).
6769 internal Dictionary < string , ResourceLocator > ? _resCache ;
6870 private long _nameSectionOffset ; // Offset to name section of file.
6971 private long _dataSectionOffset ; // Offset to Data section of file.
@@ -88,7 +90,6 @@ public sealed partial class
8890 // Version number of .resources file, for compatibility
8991 private int _version ;
9092
91-
9293 public
9394#if RESOURCES_EXTENSIONS
9495 DeserializingResourceReader ( string fileName )
@@ -169,13 +170,16 @@ private unsafe void Dispose(bool disposing)
169170 }
170171 }
171172
172- internal static unsafe int ReadUnalignedI4 ( int * p )
173+ private static unsafe int ReadUnalignedI4 ( int * p )
173174 {
174175 return BinaryPrimitives . ReadInt32LittleEndian ( new ReadOnlySpan < byte > ( p , sizeof ( int ) ) ) ;
175176 }
176177
177178 private void SkipString ( )
178179 {
180+ // Note: this method assumes that it is called either during object
181+ // construction or within another method that locks on this.
182+
179183 int stringLength = _store . Read7BitEncodedInt ( ) ;
180184 if ( stringLength < 0 )
181185 {
@@ -234,6 +238,7 @@ public IDictionaryEnumerator GetEnumerator()
234238 return new ResourceEnumerator ( this ) ;
235239 }
236240
241+ // Called from RuntimeResourceSet
237242 internal ResourceEnumerator GetEnumeratorInternal ( )
238243 {
239244 return new ResourceEnumerator ( this ) ;
@@ -243,6 +248,7 @@ internal ResourceEnumerator GetEnumeratorInternal()
243248 // To read the data, seek to _dataSectionOffset + dataPos, then
244249 // read the resource type & data.
245250 // This does a binary search through the names.
251+ // Called from RuntimeResourceSet
246252 internal int FindPosForResource ( string name )
247253 {
248254 Debug . Assert ( _store != null , "ResourceReader is closed!" ) ;
@@ -327,6 +333,8 @@ internal int FindPosForResource(string name)
327333 private unsafe bool CompareStringEqualsName ( string name )
328334 {
329335 Debug . Assert ( _store != null , "ResourceReader is closed!" ) ;
336+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
337+
330338 int byteLen = _store . Read7BitEncodedInt ( ) ;
331339 if ( byteLen < 0 )
332340 {
@@ -459,68 +467,74 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
459467 }
460468
461469 // This takes a virtual offset into the data section and reads a String
462- // from that location.
463- // Anyone who calls LoadObject should make sure they take a lock so
464- // no one can cause us to do a seek in here.
470+ // from that location. Called from RuntimeResourceSet
465471 internal string ? LoadString ( int pos )
466472 {
467473 Debug . Assert ( _store != null , "ResourceReader is closed!" ) ;
468- _store . BaseStream . Seek ( _dataSectionOffset + pos , SeekOrigin . Begin ) ;
469- string ? s = null ;
470- int typeIndex = _store . Read7BitEncodedInt ( ) ;
471- if ( _version == 1 )
472- {
473- if ( typeIndex == - 1 )
474- return null ;
475- if ( FindType ( typeIndex ) != typeof ( string ) )
476- throw new InvalidOperationException ( SR . Format ( SR . InvalidOperation_ResourceNotString_Type , FindType ( typeIndex ) . FullName ) ) ;
477- s = _store . ReadString ( ) ;
478- }
479- else
474+
475+ lock ( this )
480476 {
481- ResourceTypeCode typeCode = ( ResourceTypeCode ) typeIndex ;
482- if ( typeCode != ResourceTypeCode . String && typeCode != ResourceTypeCode . Null )
477+ _store . BaseStream . Seek ( _dataSectionOffset + pos , SeekOrigin . Begin ) ;
478+ string ? s = null ;
479+ int typeIndex = _store . Read7BitEncodedInt ( ) ;
480+ if ( _version == 1 )
483481 {
484- string ? typeString ;
485- if ( typeCode < ResourceTypeCode . StartOfUserTypes )
486- typeString = typeCode . ToString ( ) ;
487- else
488- typeString = FindType ( typeCode - ResourceTypeCode . StartOfUserTypes ) . FullName ;
489- throw new InvalidOperationException ( SR . Format ( SR . InvalidOperation_ResourceNotString_Type , typeString ) ) ;
490- }
491- if ( typeCode == ResourceTypeCode . String ) // ignore Null
482+ if ( typeIndex == - 1 )
483+ return null ;
484+ if ( FindType ( typeIndex ) != typeof ( string ) )
485+ throw new InvalidOperationException ( SR . Format ( SR . InvalidOperation_ResourceNotString_Type , FindType ( typeIndex ) . FullName ) ) ;
492486 s = _store . ReadString ( ) ;
487+ }
488+ else
489+ {
490+ ResourceTypeCode typeCode = ( ResourceTypeCode ) typeIndex ;
491+ if ( typeCode != ResourceTypeCode . String && typeCode != ResourceTypeCode . Null )
492+ {
493+ string ? typeString ;
494+ if ( typeCode < ResourceTypeCode . StartOfUserTypes )
495+ typeString = typeCode . ToString ( ) ;
496+ else
497+ typeString = FindType ( typeCode - ResourceTypeCode . StartOfUserTypes ) . FullName ;
498+ throw new InvalidOperationException ( SR . Format ( SR . InvalidOperation_ResourceNotString_Type , typeString ) ) ;
499+ }
500+ if ( typeCode == ResourceTypeCode . String ) // ignore Null
501+ s = _store . ReadString ( ) ;
502+ }
503+ return s ;
493504 }
494- return s ;
495505 }
496506
497507 // Called from RuntimeResourceSet
498508 internal object ? LoadObject ( int pos )
499509 {
500- if ( _version == 1 )
501- return LoadObjectV1 ( pos ) ;
502- return LoadObjectV2 ( pos , out _ ) ;
510+ lock ( this )
511+ {
512+ return _version == 1 ? LoadObjectV1 ( pos ) : LoadObjectV2 ( pos , out _ ) ;
513+ }
503514 }
504515
516+ // Called from RuntimeResourceSet
505517 internal object ? LoadObject ( int pos , out ResourceTypeCode typeCode )
506518 {
507- if ( _version == 1 )
519+ lock ( this )
508520 {
509- object ? o = LoadObjectV1 ( pos ) ;
510- typeCode = ( o is string ) ? ResourceTypeCode . String : ResourceTypeCode . StartOfUserTypes ;
511- return o ;
521+ if ( _version == 1 )
522+ {
523+ object ? o = LoadObjectV1 ( pos ) ;
524+ typeCode = ( o is string ) ? ResourceTypeCode . String : ResourceTypeCode . StartOfUserTypes ;
525+ return o ;
526+ }
527+ return LoadObjectV2 ( pos , out typeCode ) ;
512528 }
513- return LoadObjectV2 ( pos , out typeCode ) ;
514529 }
515530
516531 // This takes a virtual offset into the data section and reads an Object
517532 // from that location.
518- // Anyone who calls LoadObject should make sure they take a lock so
519- // no one can cause us to do a seek in here.
520- internal object ? LoadObjectV1 ( int pos )
533+ private object ? LoadObjectV1 ( int pos )
521534 {
522535 Debug . Assert ( _store != null , "ResourceReader is closed!" ) ;
523536 Debug . Assert ( _version == 1 , ".resources file was not a V1 .resources file!" ) ;
537+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
524538
525539 try
526540 {
@@ -540,6 +554,8 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
540554
541555 private object ? _LoadObjectV1 ( int pos )
542556 {
557+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
558+
543559 _store . BaseStream . Seek ( _dataSectionOffset + pos , SeekOrigin . Begin ) ;
544560 int typeIndex = _store . Read7BitEncodedInt ( ) ;
545561 if ( typeIndex == - 1 )
@@ -596,10 +612,11 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
596612 }
597613 }
598614
599- internal object ? LoadObjectV2 ( int pos , out ResourceTypeCode typeCode )
615+ private object ? LoadObjectV2 ( int pos , out ResourceTypeCode typeCode )
600616 {
601617 Debug . Assert ( _store != null , "ResourceReader is closed!" ) ;
602618 Debug . Assert ( _version >= 2 , ".resources file was not a V2 (or higher) .resources file!" ) ;
619+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
603620
604621 try
605622 {
@@ -619,6 +636,8 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
619636
620637 private object ? _LoadObjectV2 ( int pos , out ResourceTypeCode typeCode )
621638 {
639+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
640+
622641 _store . BaseStream . Seek ( _dataSectionOffset + pos , SeekOrigin . Begin ) ;
623642 typeCode = ( ResourceTypeCode ) _store . Read7BitEncodedInt ( ) ;
624643
@@ -755,6 +774,7 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
755774 [ MemberNotNull ( nameof ( _typeNamePositions ) ) ]
756775 private void ReadResources ( )
757776 {
777+ Debug . Assert ( ! Monitor . IsEntered ( this ) ) ; // only called during init
758778 Debug . Assert ( _store != null , "ResourceReader is closed!" ) ;
759779
760780 try
@@ -777,6 +797,8 @@ private void ReadResources()
777797 [ MemberNotNull ( nameof ( _typeNamePositions ) ) ]
778798 private void _ReadResources ( )
779799 {
800+ Debug . Assert ( ! Monitor . IsEntered ( this ) ) ; // only called during init
801+
780802 // Read ResourceManager header
781803 // Check for magic number
782804 int magicNum = _store . ReadInt32 ( ) ;
@@ -962,6 +984,8 @@ private Type FindType(int typeIndex)
962984 "Custom readers as well as custom objects on the resources file are not observable by the trimmer and so required assemblies, types and members may be removed." ) ]
963985 private Type UseReflectionToGetType ( int typeIndex )
964986 {
987+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
988+
965989 long oldPos = _store . BaseStream . Position ;
966990 try
967991 {
@@ -1000,6 +1024,8 @@ private Type UseReflectionToGetType(int typeIndex)
10001024 private string TypeNameFromTypeCode ( ResourceTypeCode typeCode )
10011025 {
10021026 Debug . Assert ( typeCode >= 0 , "can't be negative" ) ;
1027+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
1028+
10031029 if ( typeCode < ResourceTypeCode . StartOfUserTypes )
10041030 {
10051031 Debug . Assert ( ! string . Equals ( typeCode . ToString ( ) , "LastPrimitive" ) , "Change ResourceTypeCode metadata order so LastPrimitive isn't what Enum.ToString prefers." ) ;
@@ -1077,31 +1103,31 @@ public DictionaryEntry Entry
10771103 if ( ! _currentIsValid ) throw new InvalidOperationException ( SR . InvalidOperation_EnumNotStarted ) ;
10781104 if ( _reader . _resCache == null ) throw new InvalidOperationException ( SR . ResourceReaderIsClosed ) ;
10791105
1080- string key ;
1106+ string key = _reader . AllocateStringForNameIndex ( _currentName , out _dataPosition ) ; // AllocateStringForNameIndex could lock on _reader
1107+
10811108 object ? value = null ;
1082- lock ( _reader )
1083- { // locks should be taken in the same order as in RuntimeResourceSet.GetObject to avoid deadlock
1084- lock ( _reader . _resCache )
1109+ // Lock the cache first, then the reader (in this case, we don't actually need to lock the reader and cache at the same time).
1110+ // Lock order MUST match RuntimeResourceSet.GetObject to avoid deadlock.
1111+ Debug . Assert ( ! Monitor . IsEntered ( _reader ) ) ;
1112+ lock ( _reader . _resCache )
1113+ {
1114+ if ( _reader . _resCache . TryGetValue ( key , out ResourceLocator locator ) )
10851115 {
1086- key = _reader . AllocateStringForNameIndex ( _currentName , out _dataPosition ) ; // AllocateStringForNameIndex could lock on _reader
1087- if ( _reader . _resCache . TryGetValue ( key , out ResourceLocator locator ) )
1088- {
1089- value = locator . Value ;
1090- }
1091- if ( value == null )
1092- {
1093- if ( _dataPosition == - 1 )
1094- value = _reader . GetValueForNameIndex ( _currentName ) ;
1095- else
1096- value = _reader . LoadObject ( _dataPosition ) ;
1097- // If enumeration and subsequent lookups happen very
1098- // frequently in the same process, add a ResourceLocator
1099- // to _resCache here. But WinForms enumerates and
1100- // just about everyone else does lookups. So caching
1101- // here may bloat working set.
1102- }
1116+ value = locator . Value ;
11031117 }
11041118 }
1119+ if ( value is null )
1120+ {
1121+ if ( _dataPosition == - 1 )
1122+ value = _reader . GetValueForNameIndex ( _currentName ) ;
1123+ else
1124+ value = _reader . LoadObject ( _dataPosition ) ;
1125+ // If enumeration and subsequent lookups happen very
1126+ // frequently in the same process, add a ResourceLocator
1127+ // to _resCache here (we'll also need to extend the lock block!).
1128+ // But WinForms enumerates and just about everyone else does lookups.
1129+ // So caching here may bloat working set.
1130+ }
11051131 return new DictionaryEntry ( key , value ) ;
11061132 }
11071133 }
0 commit comments