99using System . IO ;
1010using System . Runtime . CompilerServices ;
1111using System . Text ;
12+ using System . Threading ;
1213
1314namespace System . Resources
1415#if RESOURCES_EXTENSIONS
@@ -58,10 +59,11 @@ public sealed partial class
5859 // it make sense to use anything less than one page?
5960 private const int DefaultFileStreamBufferSize = 4096 ;
6061
61- private BinaryReader _store ; // backing store we're reading from.
62- // Used by RuntimeResourceSet and this class's enumerator. Maps
63- // resource name to a value, a ResourceLocator, or a
64- // LooselyLinkedManifestResource.
62+ // Backing store we're reading from. Usages outside of constructor
63+ // initialization must be protected by lock (this).
64+ private BinaryReader _store ;
65+ // Used by RuntimeResourceSet and this class's enumerator.
66+ // Accesses must be protected by lock(_resCache).
6567 internal Dictionary < string , ResourceLocator > ? _resCache ;
6668 private long _nameSectionOffset ; // Offset to name section of file.
6769 private long _dataSectionOffset ; // Offset to Data section of file.
@@ -86,7 +88,6 @@ public sealed partial class
8688 // Version number of .resources file, for compatibility
8789 private int _version ;
8890
89-
9091 public
9192#if RESOURCES_EXTENSIONS
9293 DeserializingResourceReader ( string fileName )
@@ -163,13 +164,16 @@ private unsafe void Dispose(bool disposing)
163164 }
164165 }
165166
166- internal static unsafe int ReadUnalignedI4 ( int * p )
167+ private static unsafe int ReadUnalignedI4 ( int * p )
167168 {
168169 return BinaryPrimitives . ReadInt32LittleEndian ( new ReadOnlySpan < byte > ( p , sizeof ( int ) ) ) ;
169170 }
170171
171172 private void SkipString ( )
172173 {
174+ // Note: this method assumes that it is called either during object
175+ // construction or within another method that locks on this.
176+
173177 int stringLength = _store . Read7BitEncodedInt ( ) ;
174178 if ( stringLength < 0 )
175179 {
@@ -228,6 +232,7 @@ public IDictionaryEnumerator GetEnumerator()
228232 return new ResourceEnumerator ( this ) ;
229233 }
230234
235+ // Called from RuntimeResourceSet
231236 internal ResourceEnumerator GetEnumeratorInternal ( )
232237 {
233238 return new ResourceEnumerator ( this ) ;
@@ -237,6 +242,7 @@ internal ResourceEnumerator GetEnumeratorInternal()
237242 // To read the data, seek to _dataSectionOffset + dataPos, then
238243 // read the resource type & data.
239244 // This does a binary search through the names.
245+ // Called from RuntimeResourceSet
240246 internal int FindPosForResource ( string name )
241247 {
242248 Debug . Assert ( _store != null , "ResourceReader is closed!" ) ;
@@ -321,6 +327,8 @@ internal int FindPosForResource(string name)
321327 private unsafe bool CompareStringEqualsName ( string name )
322328 {
323329 Debug . Assert ( _store != null , "ResourceReader is closed!" ) ;
330+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
331+
324332 int byteLen = _store . Read7BitEncodedInt ( ) ;
325333 if ( byteLen < 0 )
326334 {
@@ -453,68 +461,74 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
453461 }
454462
455463 // This takes a virtual offset into the data section and reads a String
456- // from that location.
457- // Anyone who calls LoadObject should make sure they take a lock so
458- // no one can cause us to do a seek in here.
464+ // from that location. Called from RuntimeResourceSet
459465 internal string ? LoadString ( int pos )
460466 {
461467 Debug . Assert ( _store != null , "ResourceReader is closed!" ) ;
462- _store . BaseStream . Seek ( _dataSectionOffset + pos , SeekOrigin . Begin ) ;
463- string ? s = null ;
464- int typeIndex = _store . Read7BitEncodedInt ( ) ;
465- if ( _version == 1 )
466- {
467- if ( typeIndex == - 1 )
468- return null ;
469- if ( FindType ( typeIndex ) != typeof ( string ) )
470- throw new InvalidOperationException ( SR . Format ( SR . InvalidOperation_ResourceNotString_Type , FindType ( typeIndex ) . FullName ) ) ;
471- s = _store . ReadString ( ) ;
472- }
473- else
468+
469+ lock ( this )
474470 {
475- ResourceTypeCode typeCode = ( ResourceTypeCode ) typeIndex ;
476- if ( typeCode != ResourceTypeCode . String && typeCode != ResourceTypeCode . Null )
471+ _store . BaseStream . Seek ( _dataSectionOffset + pos , SeekOrigin . Begin ) ;
472+ string ? s = null ;
473+ int typeIndex = _store . Read7BitEncodedInt ( ) ;
474+ if ( _version == 1 )
477475 {
478- string ? typeString ;
479- if ( typeCode < ResourceTypeCode . StartOfUserTypes )
480- typeString = typeCode . ToString ( ) ;
481- else
482- typeString = FindType ( typeCode - ResourceTypeCode . StartOfUserTypes ) . FullName ;
483- throw new InvalidOperationException ( SR . Format ( SR . InvalidOperation_ResourceNotString_Type , typeString ) ) ;
484- }
485- if ( typeCode == ResourceTypeCode . String ) // ignore Null
476+ if ( typeIndex == - 1 )
477+ return null ;
478+ if ( FindType ( typeIndex ) != typeof ( string ) )
479+ throw new InvalidOperationException ( SR . Format ( SR . InvalidOperation_ResourceNotString_Type , FindType ( typeIndex ) . FullName ) ) ;
486480 s = _store . ReadString ( ) ;
481+ }
482+ else
483+ {
484+ ResourceTypeCode typeCode = ( ResourceTypeCode ) typeIndex ;
485+ if ( typeCode != ResourceTypeCode . String && typeCode != ResourceTypeCode . Null )
486+ {
487+ string ? typeString ;
488+ if ( typeCode < ResourceTypeCode . StartOfUserTypes )
489+ typeString = typeCode . ToString ( ) ;
490+ else
491+ typeString = FindType ( typeCode - ResourceTypeCode . StartOfUserTypes ) . FullName ;
492+ throw new InvalidOperationException ( SR . Format ( SR . InvalidOperation_ResourceNotString_Type , typeString ) ) ;
493+ }
494+ if ( typeCode == ResourceTypeCode . String ) // ignore Null
495+ s = _store . ReadString ( ) ;
496+ }
497+ return s ;
487498 }
488- return s ;
489499 }
490500
491501 // Called from RuntimeResourceSet
492502 internal object ? LoadObject ( int pos )
493503 {
494- if ( _version == 1 )
495- return LoadObjectV1 ( pos ) ;
496- return LoadObjectV2 ( pos , out _ ) ;
504+ lock ( this )
505+ {
506+ return _version == 1 ? LoadObjectV1 ( pos ) : LoadObjectV2 ( pos , out _ ) ;
507+ }
497508 }
498509
510+ // Called from RuntimeResourceSet
499511 internal object ? LoadObject ( int pos , out ResourceTypeCode typeCode )
500512 {
501- if ( _version == 1 )
513+ lock ( this )
502514 {
503- object ? o = LoadObjectV1 ( pos ) ;
504- typeCode = ( o is string ) ? ResourceTypeCode . String : ResourceTypeCode . StartOfUserTypes ;
505- return o ;
515+ if ( _version == 1 )
516+ {
517+ object ? o = LoadObjectV1 ( pos ) ;
518+ typeCode = ( o is string ) ? ResourceTypeCode . String : ResourceTypeCode . StartOfUserTypes ;
519+ return o ;
520+ }
521+ return LoadObjectV2 ( pos , out typeCode ) ;
506522 }
507- return LoadObjectV2 ( pos , out typeCode ) ;
508523 }
509524
510525 // This takes a virtual offset into the data section and reads an Object
511526 // from that location.
512- // Anyone who calls LoadObject should make sure they take a lock so
513- // no one can cause us to do a seek in here.
514- internal object ? LoadObjectV1 ( int pos )
527+ private object ? LoadObjectV1 ( int pos )
515528 {
516529 Debug . Assert ( _store != null , "ResourceReader is closed!" ) ;
517530 Debug . Assert ( _version == 1 , ".resources file was not a V1 .resources file!" ) ;
531+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
518532
519533 try
520534 {
@@ -534,6 +548,8 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
534548
535549 private object ? _LoadObjectV1 ( int pos )
536550 {
551+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
552+
537553 _store . BaseStream . Seek ( _dataSectionOffset + pos , SeekOrigin . Begin ) ;
538554 int typeIndex = _store . Read7BitEncodedInt ( ) ;
539555 if ( typeIndex == - 1 )
@@ -586,10 +602,11 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
586602 }
587603 }
588604
589- internal object ? LoadObjectV2 ( int pos , out ResourceTypeCode typeCode )
605+ private object ? LoadObjectV2 ( int pos , out ResourceTypeCode typeCode )
590606 {
591607 Debug . Assert ( _store != null , "ResourceReader is closed!" ) ;
592608 Debug . Assert ( _version >= 2 , ".resources file was not a V2 (or higher) .resources file!" ) ;
609+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
593610
594611 try
595612 {
@@ -609,6 +626,8 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
609626
610627 private object ? _LoadObjectV2 ( int pos , out ResourceTypeCode typeCode )
611628 {
629+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
630+
612631 _store . BaseStream . Seek ( _dataSectionOffset + pos , SeekOrigin . Begin ) ;
613632 typeCode = ( ResourceTypeCode ) _store . Read7BitEncodedInt ( ) ;
614633
@@ -745,6 +764,7 @@ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
745764 [ MemberNotNull ( nameof ( _typeNamePositions ) ) ]
746765 private void ReadResources ( )
747766 {
767+ Debug . Assert ( ! Monitor . IsEntered ( this ) ) ; // only called during init
748768 Debug . Assert ( _store != null , "ResourceReader is closed!" ) ;
749769
750770 try
@@ -767,6 +787,8 @@ private void ReadResources()
767787 [ MemberNotNull ( nameof ( _typeNamePositions ) ) ]
768788 private void _ReadResources ( )
769789 {
790+ Debug . Assert ( ! Monitor . IsEntered ( this ) ) ; // only called during init
791+
770792 // Read ResourceManager header
771793 // Check for magic number
772794 int magicNum = _store . ReadInt32 ( ) ;
@@ -952,6 +974,8 @@ private Type FindType(int typeIndex)
952974 "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." ) ]
953975 private Type UseReflectionToGetType ( int typeIndex )
954976 {
977+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
978+
955979 long oldPos = _store . BaseStream . Position ;
956980 try
957981 {
@@ -984,6 +1008,8 @@ private Type UseReflectionToGetType(int typeIndex)
9841008 private string TypeNameFromTypeCode ( ResourceTypeCode typeCode )
9851009 {
9861010 Debug . Assert ( typeCode >= 0 , "can't be negative" ) ;
1011+ Debug . Assert ( Monitor . IsEntered ( this ) ) ; // uses _store
1012+
9871013 if ( typeCode < ResourceTypeCode . StartOfUserTypes )
9881014 {
9891015 Debug . Assert ( ! string . Equals ( typeCode . ToString ( ) , "LastPrimitive" ) , "Change ResourceTypeCode metadata order so LastPrimitive isn't what Enum.ToString prefers." ) ;
@@ -1061,31 +1087,31 @@ public DictionaryEntry Entry
10611087 if ( ! _currentIsValid ) throw new InvalidOperationException ( SR . InvalidOperation_EnumNotStarted ) ;
10621088 if ( _reader . _resCache == null ) throw new InvalidOperationException ( SR . ResourceReaderIsClosed ) ;
10631089
1064- string key ;
1090+ string key = _reader . AllocateStringForNameIndex ( _currentName , out _dataPosition ) ; // AllocateStringForNameIndex could lock on _reader
1091+
10651092 object ? value = null ;
1066- lock ( _reader )
1067- { // locks should be taken in the same order as in RuntimeResourceSet.GetObject to avoid deadlock
1068- lock ( _reader . _resCache )
1093+ // 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).
1094+ // Lock order MUST match RuntimeResourceSet.GetObject to avoid deadlock.
1095+ Debug . Assert ( ! Monitor . IsEntered ( _reader ) ) ;
1096+ lock ( _reader . _resCache )
1097+ {
1098+ if ( _reader . _resCache . TryGetValue ( key , out ResourceLocator locator ) )
10691099 {
1070- key = _reader . AllocateStringForNameIndex ( _currentName , out _dataPosition ) ; // AllocateStringForNameIndex could lock on _reader
1071- if ( _reader . _resCache . TryGetValue ( key , out ResourceLocator locator ) )
1072- {
1073- value = locator . Value ;
1074- }
1075- if ( value == null )
1076- {
1077- if ( _dataPosition == - 1 )
1078- value = _reader . GetValueForNameIndex ( _currentName ) ;
1079- else
1080- value = _reader . LoadObject ( _dataPosition ) ;
1081- // If enumeration and subsequent lookups happen very
1082- // frequently in the same process, add a ResourceLocator
1083- // to _resCache here. But WinForms enumerates and
1084- // just about everyone else does lookups. So caching
1085- // here may bloat working set.
1086- }
1100+ value = locator . Value ;
10871101 }
10881102 }
1103+ if ( value is null )
1104+ {
1105+ if ( _dataPosition == - 1 )
1106+ value = _reader . GetValueForNameIndex ( _currentName ) ;
1107+ else
1108+ value = _reader . LoadObject ( _dataPosition ) ;
1109+ // If enumeration and subsequent lookups happen very
1110+ // frequently in the same process, add a ResourceLocator
1111+ // to _resCache here (we'll also need to extend the lock block!).
1112+ // But WinForms enumerates and just about everyone else does lookups.
1113+ // So caching here may bloat working set.
1114+ }
10891115 return new DictionaryEntry ( key , value ) ;
10901116 }
10911117 }
0 commit comments