Reference resolutions changes in Code Analysis and FxCop – Part 2
Previously, I mentioned the reference resolution changes that were made in Code Analysis for 2008 and FxCop 1.36. Unfortunately, not long after we shipped Visual Studio 2008, we found an uncommon scenario in which the strong name matching prevented Code Analysis/FxCop from analyzing a binary even though all assemblies used for compilation were present.
The scenario occurs when you have something similar to the following:
As you can see above, Assembly 1.0.0.0 has a reference to VSLangProj 8.0.0.0 and EnvDTE 8.0.0.0. Whereas, VSLangProj 8.0.0.0 has a reference to EnvDTE 7.0.3300.0.
Surprisingly, the C# compiler actually allows you to compile the above assembly against VSLangProj with a later version of EnvDTE. It allows this, by substituting the 7.0.3300.0 version of the EnvDTE types exposed by VSLangProj, with their 8.0.0.0 equivalent. However, when doing this, it stills adds a reference in the assembly manifest to both versions of EnvDTE; 7.0.3300.0 and 8.0.0.0, even though you never explicitly added a reference to the former.
In contrast to the compiler, both Code Analysis and the CLR itself by default do not consider types from a later version of an assembly to be the same as their equivalent from an earlier version. In other words, the compiler behavior and the runtime behavior are in direct conflict of each other. In fact, the only way to get such an assembly to load in the CLR, is to add a binding redirect from EnvDTE 7.0.3300.0 –> 8.0.0.0.
When you attempt to run Code Analysis/FxCop over an assembly with references similar to above, you might encounter two different errors with the same underlying cause:
The first error complains about a missing reference:
error : CA0058 : The referenced assembly 'EnvDTE, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' could not be found. This assembly is required for analysis and was referenced by: 'Assembly.dll'.
Whereas, the second error complains about failing to load the assembly:
error : CA0055 : Could not load Assembly.dll.
error : CA0052 : No targets were selected.
Opening the [AssemblyName]CodeAnalysisLog.xml in output directory will contain something similar to:
<Exceptions>
<Exception Keyword="CA0055" Kind="AssemblyLoad">
<Type>Microsoft.FxCop.Common.AssemblyLoadException</Type>
<ExceptionMessage>Could not load Target.exe.</ExceptionMessage>
<InnerType>Microsoft.FxCop.Sdk.InvalidMetadataException</InnerType>
<InnerExceptionMessage>The following error was encountered while reading module 'Assembly': Could not resolve member reference: VSLangProj.BuildManager::get_ContainingProject.</InnerExceptionMessage>
<InnerStackTrace> at Microsoft.FxCop.Sdk.Reader.HandleError(ModuleNode mod, String errorMessage)
at Microsoft.FxCop.Sdk.Reader.GetMemberFromRef(Int32 i, TypeNodeCollection& varArgTypes, Int32 numGenericArgs)
at Microsoft.FxCop.Sdk.Reader.GetMethodDefOrRef(Int32 codedIndex)
at Microsoft.FxCop.Sdk.Reader.GetTypeMembers(TypeNode type, Object handle)
at Microsoft.FxCop.Sdk.TypeNode.get_Members()
at Microsoft.FxCop.Engines.Introspection.LoadVisitor.VisitType(TypeNode type, TargetType target)
at Microsoft.FxCop.Engines.Introspection.BaseVisitor.VisitTypes(TypeNodeCollection types, TargetNamespaceDictionary targets)
at Microsoft.FxCop.Engines.Introspection.LoadVisitor.VisitModule(ModuleNode module, TargetModule target)
at Microsoft.FxCop.Engines.Introspection.BaseVisitor.VisitAssembly(AssemblyNode assembly, TargetFile target)
at Microsoft.FxCop.Engines.Introspection.LoadVisitor.VisitAssembly(AssemblyNode assembly, TargetFile target)
at Microsoft.FxCop.Engines.Introspection.LoadVisitor.Load(TargetFile target, Boolean buildTree, Boolean queueItems, AssemblyNode loadedAssembly)
at Microsoft.FxCop.Engines.Introspection.IntrospectionAnalysisEngine.LoadTargets(TargetFile target)
at Microsoft.FxCop.Common.EngineManager.LoadTargets(TargetFile target, Boolean resetCounts, String loadEngine)</InnerStackTrace>
</Exception>
<Exception Keyword="CA0052" Kind="Engine">
<Type>Microsoft.FxCop.Sdk.FxCopException</Type>
<ExceptionMessage>No targets were selected.</ExceptionMessage>
</Exception>
</Exceptions>
Luckily, there are a couple of workarounds for those that run into this:
- Add a reference to the earlier version of the assembly (ie above this would be EnvDTE 7.0.3300.0), removing the reference to the later version.
Unfortunately, if you need features only available in the later version or the earlier version of the assembly is not always available (if you are hitting this with EnvDTE, then this is most likely the case), this option will not work. In those cases, use the next option.
- Use an unsupported configuration setting and change Code Analysis/FxCop’s resolving logic:
- Using a text editor, open the FxCopCmd.exe.config file located in %PROGRAMFILES%\Microsoft Visual Studio 9.0\Team Tools\Static Analysis Tools\FxCop (Code Analysis) or %PROGRAMFILES%\Microsoft FxCop 1.36 (FxCop)
- Change the AssemblyReferenceResolveMode from StrongName to StrongNameIgnoringVersion
- Save the text file
- If using FxCop standalone, repeat the same procedure for FxCop.exe.config
Once you have changed this setting, Code Analysis/FxCop will start treating the references similar to how the compiler does. Taking above as an example, types in EnvDTE 8.0.0.0 will be considered a match for their 7.0.3300.0 counterparts.
In future versions, it is likely that Code Analysis/FxCop will move to a model that closely mimics the C# compiler’s /reference switch, which will allow it to dynamically choose the correct resolving mode based on the available references.