-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add API to get an AssemblyName that doesn't throw on a native binary #21785
Comments
There is an issue somewhere with a proposed implementation, but I cannot find it right now. |
@atsushikan do you support this proposal? If so please add api-ready-for-review label. |
A couple of questions:
|
It should throw for things like a null path, malformed path, a non-existent file, sharing violation when reading the file. If its successfully able to open the file then I'd expect it to not throw. Returning null as sentinel seems reasonable. Ultimately this one seems like an FxDC nit. I didn't have the proposed implementation. Perhaps its over in CoreCLR repo? |
Ok, marked api-ready-for-review. Not implying I'm signing up for the implementation :-) |
@atsushikan as area owner you are responsible for the API surface - doing first level API review, making sure it is in line with area plans. Area owners are NOT responsible to implement every single bug fix / API request. Hope that makes sense :) |
API review: (channeling @KrzysztofCwalina's insights from history) If it is syntax of exception handling, we should talk to Roslyn team. Actionable items:
Deferring decision until we have more info. |
Primary driver was debuggability. Current API will result in an exception even in the normal case. See dotnet/standard#326 /cc @KirillOsenkov to add his opinion. Note that |
Yes, debuggability is really bad. I think we should rethink the approach of not having Try methods just because they let us avoid a first-chance exception. I should write a blog post about first-chance exceptions, a lot of people don't realize a few important things there. Basically in a codebase where you're disciplined about first-chance exceptions there's a magnificent way to find hidden bugs - have a FirstChanceException handler with a whitelist of known benign ones. This catches bugs like crazy. If the framework itself doesn't play along, it renders this entire class of bug finding ineffective. I can't tell you how many bugs and crashes in Roslyn we've prevented because we adopted this policy. It's hard to overstate. |
The .NET platform was designed in a way that it does not play along with first change exceptions debugging. The technique works in C++ because of a completely different philosophy for the design of error reporting (more error codes). I am skeptical that we can make it first chance exceptions friendly 20 years after it was conceived. Every now and there there are people who push for various local "fixes" that IMO only make the platform inconsistent without actually solving the problem, i.e we won't be able to undo the exceptions we already throw. |
@KrzysztofCwalina But you can move the platform forward by adding non-throwing versions of APIs - and new consumers will use them by default, and existing consumers will change to use as they churn those areas. No difference from having int.Parse in v1 and adding TryParse in future versions.
This doesn't match my recollection, nor past usage, here's a couple of examples: https://docs.microsoft.com/en-us/dotnet/api/system.threading.thread.trysetapartmentstate?view=netframework-4.7#System_Threading_Thread_TrySetApartmentState_System_Threading_ApartmentState_ You could also put "File.Exists" in the same bucket here - as it doesn't leak any exceptions, but swallows them for convenience. |
IMHO, this catching bugs is indicative of catching all exceptions way too aggressively. Sadly, most programs are very, very guilty of that. But I do think it's fine to offer non-throwing alternatives as a debugging aid and not just for perf. |
Another example is the |
I think it's important to note that the goal is not to eliminate exceptions, it's to eliminate exceptions on non-exceptional control paths. If a call is expected, and allowed, to fail it would be far better to utilize some other error mechanism. |
Exactly. It's about non-avoidable exceptions vs. easily avoidable exceptions. I only want to reduce the easily avoidable exceptions like when Guid.TryParse was added to complement Guid.Parse. |
It's like the 10th time we are rehashing this. It always ends with people proposing the mythical "exceptional condition" and then not being able to define what it means. The reason is that whether some error is exceptional or not depends on the call site, not on the API being called. parsing a literal int is very different than parsing an int from a file, yet these two operations use the same API. Secondly, as I said before, we already have throwing APIs, and adding Try alternatives will not make them go away, programs will keep throwing, and debug-ability will be affected. At the same time we will introduce lots of inconsistencies, duplication, and problems that come with error codes. I do think we should improve C# syntax for exception handling, and to improve how first change exception debugging works (with some ideas mentioned in this thread, e.g. better filtering), but as to whether we use exceptions or error codes for errors in .NET, IMO the ship has sailed. |
Keep in mind, I'm not advocating for error codes, heaven forbid. I'm not advocating for anything drastic. All I'm saying is that when we introduced Guid.TryParse, it was a wonderful thing, it was the right thing to do, and when VS switched to using it in a few places it was a significant improvement in the quality of life of people debugging. Every little bit helps. It's OK that the old APIs stay. But we want to have a choice in case an exception is easily avoidable and really expected for a quite common class of inputs. |
Try* pattern is a poors-man-error-code; it has very similar effect on the platform. We decided to use error codes/try in rare circumstances when exceptions are too slow, and accept the negatives, because both: a) there is no known solution to perf problem, b) the perf problem is limited to a small set of APIs (e.g. low level parsing routines, basic datastructure accessors, etc.). The debug-ability problem is not limited to any particular API. |
As it's a language wide problem don't we need a new tool to achieve that ? For instance, a new keyword try
{
return AssemblyName.GetAssemblyName(sourcePath);
}
swallow(BadImageFormatException)
{
return null;
} Depending one the type of exceptions swallowed, static analyzers could warn the programmer. |
There are various methods in the framework that can throw a BadImageFormatException if the file is not a valid assembly. Instead of adding Try-overloads to all these functions, wouldn't it be better to have generic function (IsValidAssembly, CheckAssembly, ...) that checks if a file represents a valid assembly. Just like I might do a check to File.Exists before calling GetAssemblyName, I could also check this new method to differentiate between a valid assembly and some random file. Of course this has the disadvantage of having to open the file various times, but if that is a concern, you can always go back to try-catch. |
VS (and other UI apps I've worked on) catch everything - I imagine that will always be the case. It used to be that breaking on first chance exceptions in VS would find bugs - unfortunately some components throw them excessively now. First chance can also be helpful when the exception is caught and rethrown asynchronously. For console apps you are probably right. |
This isn't my experience (just anecdata of course). At least with just my code switched on. Even without JMC, there are often only a few exception types that throw in the "normal" case and those you can filter out. Tooling and language improvements have a long lead time. I don't think if we offer a Try version of this method, it will lead to some slippery slope. We can have a budget of just a few a year :) |
File.Exists is vulnerable to race conditions- just embrace it and catch the exception at whatever level is responsible to know how it wants to respond to it.
I'm stealing this. 😆 |
FYI: The API review discussion was recorded - see https://youtu.be/W_r6oG7nnK4?t=680 (15 min duration) |
For me, it is syntax and debug-ability. Interesting discussion in API review. |
Yeah, I agree with have a problem with syntax and to some extent debug ability. It would be good to fix both. @jaredpar and I worked on a project trying to address the syntax issues, and I think it was very promissing. For example, what if you could do: x = try MethodThatThrows() else 5;
// or
try MethodThatThrows() else SomeOtherMethod(); |
For the debug-ability issue maybe we should allow attributes on catch statements, allowing to give further information to debugger (similar to DebuggerStepThroughAttribute). That being said I'll let smart people make the necessary modifications 😸 |
This discussion seems similar to |
Relevant: dotnet/csharplang#220, dotnet/csharplang#464, dotnet/csharplang#786, dotnet/roslyn#16072, https://github.com/dotnet/roslyn/issues/16078. My opinion on it, like many others’, has always been negative: dotnet/roslyn#16072 (comment) |
I feel like whe should focus on the debug-ability problem. Maybe in another issue?
Remains the debug-ability issue, hence "my not going to happen" proposal. |
@KrzysztofCwalina how would you feel about |
If the only debugability problem we have in the Framework is throwing GetAssemblyName, then we should just add whether we need/want. But I think the issue is general; it's not just about GetAssemblyName. |
@KrzysztofCwalina I had a similar idea of avoiding all of these TryParse like methods, that jnm2 referenced about statement level exception handling dotnet/csharplang#965 Take a look there are potential improvements for: |
This conversation does belong elsewhere but my concern with amending the language to make this simpler is that the majority of developers won't know when not to use this. It'll not only make ignoring exceptions much easier than it is currently, it'll make ignoring exceptions much easier than having to dealing with them. I worry that enshrining such behavior directly in the language would serve as encouragement that this is the appropriate pattern to adopt for exception handling in general. Also, why isn't there a corresponding issue for this on https://github.com/dotnet/csharplang/? If @jaredpar is actively working on it enough to build out a prototype it would seem "championed" enough. |
Another thing occurred to me last night. The Conflating the |
With VS a lot of the first chance exceptions thrown (usually thousands of them) all boiled down to a small set of actual methods that did not have TryXXX versions. I don't think a general solution is needed. I think we could actually get quite far just by targetting the few bad cases, and updating the higher levels to use those instead. |
Do we have the list? |
May I suggest that you start a new VS instance under debugger with first-chance exceptions on. It is educational and worth doing at least once as the best way to familiarize yourself with the problem. Try creating a C# console app, editing, building. |
Not of the top of my head. And I'm not really using VS anymore. But it's not hard to collect just by running the experiment. |
These are the ones that I'm aware of:
|
@davkean Lol! My list is identical. |
there are few enough badly behaved apis which encourage throw-and-catch that i routinely rework code to avoid using them - i was reminded of this issue today when a coworker mentioned avoiding some uses of “new XmlSerializer()” for the same reason. if you can get near to 0 first-chance exceptions it’s a great aid to debuggability, and every little feature like TryGetAssemblyName helps. please don’t discard the idea just because it isn’t a performance issue! |
Yes, forgot about that one, added it to the list. |
-> Future as not needed for 2.1 |
Due to 5.0 schedule constraints, this is being moved to Future. |
System.Reflection.Metadata is part of .NET Core surface now, so this is no longer concern. System.Reflection.Metadata is the right way to solve this going forward. Also, System.Reflection.Metadata is faster and lighter weight API than |
I've seen patterns like this in countless tools:
It would be better if we could just do:
API signature:
By making this a new API we don't change the behavior of the existing API. By making the API determine if a file is an assembly and returning the name we avoid having to open the file twice.
The other alternative available today requires using System.Reflection.Metadata which is not part of netstandard and requires redistributing a sizeable dependency (800KB), or writing your own PE parsing code (which requires opening the file twice).
/cc @gkhanna79
The text was updated successfully, but these errors were encountered: