-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quick initial comments and thoughts.
// file, just return a Stream pointing to that block of memory. | ||
unsafe | ||
{ | ||
stream = new UnmanagedMemoryStream(ums.PositionPointer, length, length, FileAccess.Read); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a bug in the original ResourceReader
code. The ResourceReader
ctor assumes that any instance of an UnmanagedMemoryStream
passed in to its ctor must correspond to memory that will never be freed for the lifetime of the application, because a non-lifetime-tracked pointer to that memory is being exposed to the outside world via this method. While this is true for resources loaded from assemblies (where the assembly isn't in an unloadable context), this is not generally a safe assumption to make.
I don't think we need to address this now since this code has existed for 20 years without issue, but it's definitely a bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed this is a bad assumption. UMS doesn't provide any way to rent out a ref count to its pointer. Probably need a better check to make sure the UMS points to the embedded resources if we wanted to avoid this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to address this now since this code has existed for 20 years without issue, but it's definitely a bug.
This was fixed recently: dotnet/coreclr#22925
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, we can't take advantage of that in the netstandard build, but can in the corelib version.
stream = new MemoryStream(bytes, false); | ||
} | ||
|
||
value = Activator.CreateInstance(type, new object[] { stream }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be as dangerous as BinaryFormatter
. I think we said this was intended to operate over trusted data, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct: resources are assumed to be as trusted as executable code. Even before we get here ResourceManager had the opportunity to do something similar based on the resources header (the extension point we're using here): https://github.com/dotnet/coreclr/blob/8252365bfb7651a5473e98d3bdaeaa8cb22c0ec3/src/System.Private.CoreLib/shared/System/Resources/ManifestBasedResourceGroveler.cs#L255
The point of this change is not to make resources more trusted, its to enable non-primitive resources without relying on BinaryFormatter.
Here are some alternate options:
|
The current PR has this problem too because of it compiles the CoreLib code into the NuGet package. FWIW, I do not see a problem with duplicating the code. It is simplest and most flexible. |
I was trying to draw the distinction that at the limit with the current PR we wouldn't rely on the duplicated code. We could imagine dead-ending the downlevel implementation then only servicing the latest version. In that case there would not be duplication.
Are you referring to the current PR or the alternate option 1? |
Option 1. The CoreFX implementation style tends to prefer compiling the same code into multiple places with different shapes. We have somewhere between 2MB and 5 MBs worth of C# under Common that is compiled into more than assembly. Adding 100kB of resource handling to this pile does not sound like a big deal. (I do not have strong opinion on whether option 1 is better than current PR. It is different style, but about as good.) |
27319a3
to
a789723
Compare
So I'll plan to discuss this option + alternate 1 in FXDC. Before I write up a proposal, can I get feedback on naming and factoring? Currently I chose Here are some other options:
I'd also like feedback on assembly factoring. Currently I've placed the reader and writer in separate assemblies since the writer is a build time component and reader is runtime. I could combine them if folks don't think the separation saves much. The size of the writer is currently 23KB and its dependencies are a subset of the reader's. |
Now that I've started to consume this, I'd rather move away from In MSBuild (left over from resgen) there is a I like |
I prefer to use System.Resources.Extensions and even to have the new library named same. extensions really reflect it is a resource extension which I think this is really the case. Having extensions name will allow us in the future to add more stuff there without a need to recreate a new library. For prefix, I think |
Does the reader need to be separate NuGet package assembly? Can it be inbox only? |
It must be inbox at least for WindowsDesktop. It could be inbox on the base shared framework if that's what folks think makes sense. It needs to sit rather high in the stack due to the dependencies on BinaryFormatter, and TypeConverter. I need a nuget package for the writer so that it can be used by MSBuild running on desktop inside VS. MSBuild can target many frameworks which is the reason why I also have a package for the reader. I'd prefer it not go inbox, or at least not in a way that freezes the API surface as we might want to iterate on that before it is done.
Both these names read a little unusual to me: |
Poking around this code a bit more I came up with an Option 4 above. #36906 (comment). I think its interesting and may experiment with it a bit. @tarekgh do you know of many scenarios for folks directly accessing the reader, or do you think it would be reasonable to lift the converter behavior out into a custom ResourceSet? |
The scenarios that kind of common is people use ResourceReader with the ResourceSet. I saw some little cases of using the reader by itself. We may try to get some more data about that. But in general using ResiurceSet is a good idea. |
I'm not so sure of the viability of only implementing a custom ResourceSet since it wouldn't be able to access ResourceReader internals to provide the same perf characteristics that people get today. I'll leave it as an option but I'm less enthusiastic after digging into that code. |
05161ea
to
eb5a8b3
Compare
81169f5
to
df1eaa2
Compare
Ok, this should be ready now. |
src/System.Resources.Extensions/src/System/Resources/Extensions/DeserializingResourceReader.cs
Show resolved
Hide resolved
src/System.Resources.Extensions/src/System/Resources/Extensions/DeserializingResourceReader.cs
Outdated
Show resolved
Hide resolved
src/System.Resources.Extensions/src/System/Resources/Extensions/DeserializingResourceReader.cs
Show resolved
Hide resolved
src/System.Resources.Extensions/src/System/Resources/Extensions/SerializationFormat.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some minor comments/questions. other than that, LGTM
We don't want to use the runtime type identity when doing type checks or writing types as this may change. Instead, check-in hard-coded strings that match the type identity. Add a test case to ensure the resources we generate match what we test the reader against in the resource manager case (also to track any change to the binary format).
* Add System.Resources.Binary.Reader|Writer * Fix ResourceWriter tests * Test fixes and PR feedback * More test fixes * Add packages for System.Resources.Binary.* * Suppress duplicate types in System.Resources.Binary.* * Test refactoring and adding RuntimeResourceSet It turns out me must have our own ResourceSet since the CoreLib resource set doesn't expose a constructor that takes an IResourceReader. I've shared the code since it does a bit of non-trivial caching. * Don't use auto-property initializers for platfrom sensitive test data For some reason I thought these lazy-initialized the properties but they don't. As a result we were hitting the platform sensitive code even when we never called the getter. Switch to an expression instead. * Only use Drawing converters on Windows * Fix test failures * Don't leak System.Private.CoreLib into resources * Make sure RuntimeResourceSet doesn't call ResourceReader(IResourceReader) * WIP * Rename types in System.Resources.Extensions Leave RuntimeResourceSet internal as it doesn't need to be public. * Update packages * Respond to API review feedback Remove abstraction for ResourceReader/Writer: just reuse the source. Remove non-essential members. * Clean up * Further cleanup * Further cleanup * Review feedback * Ensure we have stable type names in Resources.Extensions We don't want to use the runtime type identity when doing type checks or writing types as this may change. Instead, check-in hard-coded strings that match the type identity. Add a test case to ensure the resources we generate match what we test the reader against in the resource manager case (also to track any change to the binary format). Signed-off-by: dotnet-bot <[email protected]>
* Add System.Resources.Binary.Reader|Writer * Fix ResourceWriter tests * Test fixes and PR feedback * More test fixes * Add packages for System.Resources.Binary.* * Suppress duplicate types in System.Resources.Binary.* * Test refactoring and adding RuntimeResourceSet It turns out me must have our own ResourceSet since the CoreLib resource set doesn't expose a constructor that takes an IResourceReader. I've shared the code since it does a bit of non-trivial caching. * Don't use auto-property initializers for platfrom sensitive test data For some reason I thought these lazy-initialized the properties but they don't. As a result we were hitting the platform sensitive code even when we never called the getter. Switch to an expression instead. * Only use Drawing converters on Windows * Fix test failures * Don't leak System.Private.CoreLib into resources * Make sure RuntimeResourceSet doesn't call ResourceReader(IResourceReader) * WIP * Rename types in System.Resources.Extensions Leave RuntimeResourceSet internal as it doesn't need to be public. * Update packages * Respond to API review feedback Remove abstraction for ResourceReader/Writer: just reuse the source. Remove non-essential members. * Clean up * Further cleanup * Further cleanup * Review feedback * Ensure we have stable type names in Resources.Extensions We don't want to use the runtime type identity when doing type checks or writing types as this may change. Instead, check-in hard-coded strings that match the type identity. Add a test case to ensure the resources we generate match what we test the reader against in the resource manager case (also to track any change to the binary format). Signed-off-by: dotnet-bot <[email protected]>
* Add System.Resources.Binary.Reader|Writer * Fix ResourceWriter tests * Test fixes and PR feedback * More test fixes * Add packages for System.Resources.Binary.* * Suppress duplicate types in System.Resources.Binary.* * Test refactoring and adding RuntimeResourceSet It turns out me must have our own ResourceSet since the CoreLib resource set doesn't expose a constructor that takes an IResourceReader. I've shared the code since it does a bit of non-trivial caching. * Don't use auto-property initializers for platfrom sensitive test data For some reason I thought these lazy-initialized the properties but they don't. As a result we were hitting the platform sensitive code even when we never called the getter. Switch to an expression instead. * Only use Drawing converters on Windows * Fix test failures * Don't leak System.Private.CoreLib into resources * Make sure RuntimeResourceSet doesn't call ResourceReader(IResourceReader) * WIP * Rename types in System.Resources.Extensions Leave RuntimeResourceSet internal as it doesn't need to be public. * Update packages * Respond to API review feedback Remove abstraction for ResourceReader/Writer: just reuse the source. Remove non-essential members. * Clean up * Further cleanup * Further cleanup * Review feedback * Ensure we have stable type names in Resources.Extensions We don't want to use the runtime type identity when doing type checks or writing types as this may change. Instead, check-in hard-coded strings that match the type identity. Add a test case to ensure the resources we generate match what we test the reader against in the resource manager case (also to track any change to the binary format). Signed-off-by: dotnet-bot <[email protected]>
* Add System.Resources.Binary.Reader|Writer * Fix ResourceWriter tests * Test fixes and PR feedback * More test fixes * Add packages for System.Resources.Binary.* * Suppress duplicate types in System.Resources.Binary.* * Test refactoring and adding RuntimeResourceSet It turns out me must have our own ResourceSet since the CoreLib resource set doesn't expose a constructor that takes an IResourceReader. I've shared the code since it does a bit of non-trivial caching. * Don't use auto-property initializers for platfrom sensitive test data For some reason I thought these lazy-initialized the properties but they don't. As a result we were hitting the platform sensitive code even when we never called the getter. Switch to an expression instead. * Only use Drawing converters on Windows * Fix test failures * Don't leak System.Private.CoreLib into resources * Make sure RuntimeResourceSet doesn't call ResourceReader(IResourceReader) * WIP * Rename types in System.Resources.Extensions Leave RuntimeResourceSet internal as it doesn't need to be public. * Update packages * Respond to API review feedback Remove abstraction for ResourceReader/Writer: just reuse the source. Remove non-essential members. * Clean up * Further cleanup * Further cleanup * Review feedback * Ensure we have stable type names in Resources.Extensions We don't want to use the runtime type identity when doing type checks or writing types as this may change. Instead, check-in hard-coded strings that match the type identity. Add a test case to ensure the resources we generate match what we test the reader against in the resource manager case (also to track any change to the binary format). Commit migrated from dotnet/corefx@da36ea2
This adds a new pair of IResourceWriter & IResourceReader named BinaryResourceWriter and BinaryResourceReader.
The primary difference between these and the base reader/writer is that they support more formats for pre-serialized data. The benefit of using pre-serialized data is that we don't need to create live-objects during the resource writing phase. This will enable us to fix dotnet/msbuild#2221 in a source comaptible way.
MSBuild will adopt the new BinaryResourceWriter to pass through resource data from resx directly to resources, rather than reading the resx, creating live objects, and directly serializing those.
The Writer is designed in such a way that it only requires the new reader when any of the new-pass-through formats are used. Otherwise it will write the .resources in a format that is compatible with the existing built-in ResourceReader.
The writer must target
netstandard
so that it can be used by MSBuild when building from visual studio. The reader targetsnetstandard
so that it other frameworks might benefit from this improved resource build process, but it is a secondary requirement.Since these new types are largely just an extension of the existing ResourceWriter and ResourceReader functionality I unsealed the base types and added the minimal amount of virtuals to enable this. To benefit from the same code-sharing I made the
netstandard
implementation carry a copy of the base ResourceReader and ResourceWriter types. I have hidden this derivation from the reference assemblies since it cannot be made universally (types are sealed innetstandard2.0
) and it's not particularly useful as an "is a" relationship.I did investigate trying to implement this functionality by wrapping the existing ResourceReader/ResourceWriter types (and wrapping their format) but I couldn't do so cleanly without duplicating a resourceId to type-table map in the outer format. This would bloat the resources considerably and add quite a lot of complexity, so I avoided it.
This is currently in draft. I'd like feedback on the names and the strategy. @stephentoub @jkotas @tarekgh @rainersigwald @nguerrera