Webinar: Evaluation - 05.12
Usually, when developers make a new release of an assembly, they also change its version. Changing the version is particularly important when developing a library on which other projects depend. But what happens when developers don't change the library version? Let me tell you a short story about problems we encountered when using Microsoft libraries.
I'm closely involved in the development of the PVS-Studio C# analyzer. The analyzer uses 'Microsoft.Build.dll', 'Microsoft.Build.Framework.dll', etc. These libraries allow PVS-Studio to get various information from the project file. Thus, the analyzer can perform a deeper analysis of the projects.
Microsoft developers decided that the version for all these assemblies should always be 15.1. It doesn't matter if the public interface changes, if new types appear, or if old types change, the version should always be the same. The corresponding NuGet packages change versions, but the assemblies do not. "Why?" I inquired. The msbuild repository maintainer responded:
This is intentional, and allows API client applications to work with multiple versions of MSBuild.
Well, Microsoft developers are running the show. Anyway, this approach doesn't seem to bother anyone.
Or it does?
We received the message from our client, who reported a crash of the analyzer. With the help of a translator, we figured out that the required Microsoft.Build.Framework.Traits type was missing from one of our dependencies — 'Microsoft.Build.Framework.dll'.
As expected, we failed to reproduce the crash. The needed assembly is always installed in the folder with PVS-Studio.exe, and the required type is always included in this assembly. So, what was the matter?
Not long ago, we updated all the 'Microsoft.Build.*' dependencies to support the analysis of .NET 7 projects. We examined the previous versions of the analyzer and found out that the 'Microsoft.Build.Framework.dll' library doesn't have the required type. Could our client's analyzer have been updated incorrectly in some way?
Not at all. We asked our client to send us the dll file, and it was fine — the file contained the required type. Then we sent our client a special version of the analyzer that would log the paths used to load the assemblies at runtime. It turned out that the wrong library was loaded at runtime. That was not the library next to the executable file, but its 'analog' from the global assembly cache (GAC).
It's finally making sense!
That's what we have:
Testing this hypothesis was also quite simple: we needed to add an older release assembly to the GAC to reproduce the crash. Then something even more interesting happened: Visual Studio 2022 suddenly stopped working. When we opened it up, we got a message stating that something went wrong:
As a professional and experienced developer, I choose to ignore it and just clicked "yes", and the message was gone. Good thing. Then I opened the simplest console project, but it was in a dismal state:
I'm a simple man. I see 'unloaded', I click 'reload'.
Immediately I got a familiar message (not in German, though):
This was the exception that our client encountered.
This exception is also quite simple to reproduce:
Our client certainly had no intention of breaking their Visual Studio 2022. They even didn't have VS 2022, so they didn't notice its crash. Instead, the analyzer, which depends on the new MSBuild libraries and therefore always keeps them around, took the brunt of the crash.
Apparently, this assembly should be present in the GAC for some environment to work, so this assembly cannot be removed. Instead, our client updated the assembly in the GAC, and nothing seemed to break :).
I tweeted about this case and asked a question on Stack Overflow, but I got no response. It proved to be much more effective to raise the issue in the MSBuild repository.
In this issue, MSBuild maintainer Rainer Sigwald took some time to answer my questions, for which I am very grateful. In short, he told me the following:
We couldn't find any ways to bypass the GAC when loading an assembly at runtime :(
First, it's better not to add Microsoft.Build.* version 15.1 in the GAC. This may break MSBuild (and, just as importantly, this may break PVS-Studio).
Second, if possible, it is always better to update the assembly version if it has been changed. This way, you can protect your users from potential issues like those described in the article.
Third, if you are already using PVS-Studio to search for errors in your project and you encounter the problem described, you already know what the issue is and what to do.
That's all for today. Good luck!
0