Soporte de Visual Studio 2019 en PVS-Studio


El soporte para Visual Studio 2019 en PVS-Studio afectó inmediatamente a varios componentes diferentes: el complemento IDE en sí, la aplicación de análisis de línea de comandos, los analizadores C ++ y C #, así como varias utilidades. Hablaré brevemente sobre los problemas que encontramos al admitir la nueva versión del IDE y cómo resolverlos.

Antes de comenzar, quiero mirar un poco hacia atrás para rastrear el historial de soporte para versiones anteriores de Visual Studio, lo que dará una mejor comprensión de nuestra visión de la tarea y las decisiones tomadas en ciertas situaciones.

Comenzando con la primera versión del analizador PVS-Studio, en el que apareció el complemento para el entorno de Visual Studio (entonces era la versión de Visual Studio 2005), admitir nuevas versiones de Visual Studio fue una tarea bastante simple para nosotros: básicamente se redujo a actualizar el archivo de proyecto del complemento y dependencias de varias API de extensión de Visual Studio. A veces era necesario admitir adicionalmente nuevas características del lenguaje C ++, que el compilador de Visual C ++ estaba aprendiendo gradualmente, pero esto tampoco solía causar problemas inmediatamente antes del lanzamiento de la próxima edición de Visual Studio. Y solo había un analizador en PVS-Studio para los lenguajes C y C ++.

Todo cambió para el lanzamiento de Visual Studio 2017. Además del hecho de que muchas de las API de extensión para este IDE cambiaron muy significativamente en esta versión, después de la actualización tuvimos problemas para garantizar la compatibilidad con el trabajo del nuevo analizador C # que había aparecido en ese momento (así como nuestra nueva capa C ++). analizador para proyectos de MSBuild) con versiones anteriores de MSBuild \ Visual Studio.

Por lo tanto, antes de leer este artículo, le recomiendo que lea el artículo relacionado sobre el soporte de Visual Studio 2017: "Soporte de Visual Studio 2017 y Roslyn 2.0 en PVS-Studio: a veces, usar soluciones preparadas no es tan fácil como parece a primera vista ". El artículo mencionado anteriormente describe los problemas que encontramos la última vez, así como los esquemas de interacción de varios componentes (por ejemplo, PVS-Studio, MSBuild, Roslyn). Entender esta interacción será útil al leer este artículo.

En última instancia, la solución a estos problemas trajo cambios significativos a nuestro analizador y, como esperábamos, los nuevos enfoques que aplicamos permitirán que las versiones actualizadas de Visual Studio \ MSBuild sean mucho más fáciles y rápidas en el futuro. En parte, esta suposición ya ha sido confirmada por el lanzamiento de numerosas actualizaciones de Visual Studio 2017. ¿Este nuevo enfoque ayudó con el soporte de Visual Studio 2019? Sobre esto a continuación.

PVS-Studio Plugin para Visual Studio 2019


Todo comenzó, al parecer, no está mal. Fue bastante fácil portar el complemento a Visual Studio 2019, donde comenzó y funcionó bien. A pesar de esto, se revelaron 2 problemas de inmediato, que prometían problemas futuros.

La primera es la interfaz IVsSolutionWorkspaceService , que se utiliza para admitir el modo de carga de solución ligera, que, por cierto, se deshabilitó en una de las actualizaciones anteriores en Visual Studio 2017, se decoró con el atributo Desaprobado , que era solo una advertencia durante el ensamblaje, pero prometía más en el futuro problemas Microsoft introdujo rápidamente este modo y lo abandonó ... Nos ocupamos de este problema de manera bastante simple: nos negamos a usar la interfaz adecuada.

El segundo: al cargar Visual Studio con el complemento, apareció el siguiente mensaje: Visual Studio ha detectado una o más extensiones que están en riesgo o que no funcionan en una actualización VS de función.

Ver los registros de inicio de Visual Studio (archivo ActivityLog) finalmente salpicó la 'i':

Advertencia: La extensión 'PVS-Studio' usa la característica 'carga automática síncrona' de Visual Studio. Esta característica ya no será compatible en una futura actualización de Visual Studio 2019, momento en el que esta extensión no funcionará. Póngase en contacto con el proveedor de la extensión para obtener una actualización.

Para nosotros, esto significaba una cosa: cambiar la forma en que el complemento se carga en modo asíncrono. Espero que no esté molesto si no lo sobrecargo con detalles sobre cómo interactuar con las interfaces COM de Visual Studio, y repasaré los cambios brevemente.

Microsoft tiene un artículo sobre la creación de complementos cargados asincrónicamente: " Cómo: Usar AsyncPackage para cargar VSPackages en segundo plano ". Al mismo tiempo, era obvio para todos que el asunto no se limitaría a estos cambios.

Uno de los principales cambios es el método de carga, o más bien, la inicialización. Anteriormente, la inicialización necesaria se realizaba en dos métodos: el método Inicializado anulado de nuestra clase de herencia de Paquete y el método OnShellPropertyChange . La necesidad de transferir parte de la lógica al método OnShellPropertyChange se debe al hecho de que cuando el complemento se carga sincrónicamente, Visual Studio aún no se puede cargar e inicializar completamente, y como resultado de esto, no todas las acciones necesarias podrían realizarse en la etapa de inicialización del complemento. Una opción para resolver este problema es esperar a que Visual Studio salga del estado 'zombie' y retrase estas acciones. Esta es la lógica y se ha procesado en OnShellPropertyChange con una comprobación del estado 'zombie'.

En la clase abstracta AsyncPackage , de la que se heredan los complementos cargados asincrónicamente, el método Initialize tiene un modificador sellado , por lo que la inicialización se debe realizar en el método Inicializado Inicializado , que se realizó. También tuvimos que cambiar la lógica con el seguimiento del estado 'zombie' de Visual Studio, porque dejamos de recibir esta información en el complemento. Sin embargo, una serie de acciones que debían realizarse después de que se inicializó el complemento no desaparecieron. La solución fue utilizar el método OnPackageLoaded de la interfaz IVsPackageLoadEvents , donde se realizaron acciones que requerían una ejecución diferida.

Otro problema que surge lógicamente del hecho de la carga asíncrona del complemento es la ausencia de comandos del complemento PVS-Studio al momento de iniciar Visual Studio. Cuando abre el registro del analizador haciendo doble clic desde el administrador de archivos (si necesita abrirlo a través de Visual Studio), se lanzó la versión necesaria de devenv.exe con el comando para abrir el informe del analizador. El comando de lanzamiento se parecía a esto:

"C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\Common7\IDE\devenv.exe" /command "PVSStudio.OpenAnalysisReport C:\Users\vasiliev\source\repos\ConsoleApp\ConsoleApp.plog" 

El indicador "/ command" aquí se usa para invocar un comando registrado en Visual Studio. Ahora este enfoque no funcionó, ya que los comandos no estuvieron disponibles hasta que se descargó el complemento. Como resultado, tuve que parar en la "muleta" al analizar la línea de lanzamiento de devenv.exe después de cargar el complemento, y si hay una representación de cadena del comando para abrir el registro, de hecho, cargar el registro. Por lo tanto, en este caso, al negarse a utilizar la interfaz "correcta" para trabajar con comandos, fue posible mantener la funcionalidad necesaria al retrasar la carga del registro hasta que el complemento se haya cargado por completo.

Fuh, parece estar resuelto y todo funciona, todo se carga y se abre correctamente, no hay advertencias, finalmente.

Y luego sucede lo inesperado: Pavel (¡hola!) Instala un complemento, después de lo cual pregunta por qué no hicimos una carga asincrónica.

Decir que nos sorprendió, por no decir nada, ¿cómo? No, de verdad, aquí está la nueva versión del complemento instalado, aquí está el mensaje de que el paquete se puede descargar de forma síncrona. Instalamos con Alexander (y hola a usted también) la misma versión del complemento en nuestras máquinas: todo está bien. Nada está claro: decidimos ver qué versiones de las bibliotecas PVS-Studio se cargaron en Visual Studio. Y de repente resulta que se usan las versiones de las bibliotecas PVS-Studio para Visual Studio 2017, a pesar del hecho de que la versión correcta de las bibliotecas está en el paquete VSIX, para Visual Studio 2019.

Después de jugar con VSIXInstaller, logré encontrar la causa del problema: la caché del paquete. La teoría también fue confirmada por el hecho de que al restringir los derechos de acceso al paquete en el caché (C: \ ProgramData \ Microsoft \ VisualStudio \ Packages) VSIXInstaller escribió información de error en el registro. Sorprendentemente, si no hay ningún error, no se escribe en el registro ninguna información sobre el hecho de que el paquete está instalado desde el caché.

Nota Al estudiar el comportamiento de VSIXInstaller y las bibliotecas relacionadas, se señaló a sí mismo que es genial que Roslyn y MSBuild tengan código fuente abierto, lo que hace que sea conveniente leer, depurar y rastrear la lógica del trabajo.

Como resultado, sucedió lo siguiente: al instalar el complemento, VSIXInstaller vio que el paquete correspondiente ya estaba en la caché (había un paquete .vsix para Visual Studio 2017), y lo usó en lugar del paquete instalado real durante la instalación. Por qué esto no tiene en cuenta las restricciones / requisitos descritos en .vsixmanifest (por ejemplo, la versión de Visual Studio para la que puede instalar la extensión) es una pregunta abierta. Debido a esto, resultó que, aunque .vsixmanifest contenía las restricciones necesarias, el complemento diseñado para Visual Studio 2017 se instaló en Visual Studio 2019.

Lo peor es que una instalación de este tipo rompió el gráfico de dependencia de Visual Studio, y aunque externamente podría parecer que el entorno de desarrollo funcionaba bien, de hecho, todo estaba muy mal. Era imposible instalar y desinstalar extensiones, realizar actualizaciones, etc. El proceso de 'recuperación' también fue bastante desagradable, porque fue necesario eliminar la extensión (los archivos correspondientes), así como editar manualmente los archivos de configuración que almacenan información sobre el paquete instalado. En general, no es lo suficientemente agradable.

Para resolver este problema y evitar situaciones similares en el futuro, se decidió crear un GUID para el nuevo paquete para separar exactamente los paquetes de Visual Studio 2017 y Visual Studio 2019 (no existe tal problema con los paquetes más antiguos, y siempre usaron un GUID común).

Y como estábamos hablando de sorpresas desagradables, mencionaré una cosa más: después de actualizar a la Vista previa 2, el elemento del menú 'se movió' en la pestaña 'Extensiones'. Parece que está bien, pero el acceso a las funciones del complemento se ha vuelto menos conveniente. En versiones posteriores de Visual Studio 2019, incluida la versión de lanzamiento, este comportamiento se ha conservado. No encontré ninguna mención de esta 'característica' en el momento de su lanzamiento en la documentación o el blog.

Ahora, parece que todo funciona, y con el soporte de plug-in para Visual Studio 2019 está terminado. El día después del lanzamiento de PVS-Studio 7.02 con soporte para Visual Studio 2019, resultó que esto no era así: se encontró otro problema con el complemento asíncrono. Para el usuario, esto podría verse así: al abrir una ventana con los resultados del análisis (o comenzar el análisis), nuestra ventana a veces se mostraba "vacía": no contenía contenido: botones, una tabla con advertencias del analizador, etc.

De hecho, este problema a veces se repitió en el curso del trabajo. Sin embargo, se repitió solo en una máquina y comenzó a aparecer solo después de actualizar Visual Studio en una de las primeras versiones de 'Vista previa'; hubo sospechas de que algo se había roto durante la instalación / actualización. Con el tiempo, sin embargo, el problema dejó de repetirse incluso en esta máquina, y decidimos que "se reparó por sí mismo". Resultó que no, tan afortunado. Más precisamente, sin suerte.

El asunto resultó estar en el orden de inicialización de la ventana del entorno en sí (el descendiente de la clase ToolWindowPane ) y su contenido (de hecho, nuestro control con la cuadrícula y los botones). Bajo ciertas condiciones, la inicialización del control ocurrió antes de la inicialización del panel, y a pesar de que todo funcionó sin errores, el método FindToolWindowAsync (crear una ventana en la primera llamada) funcionó correctamente, pero el control permaneció invisible. Lo arreglamos agregando una inicialización diferida para nuestro control al código de relleno del panel.

Soporte C # 8.0


Usar Roslyn como base para el analizador tiene una ventaja significativa: no es necesario mantener manualmente nuevas construcciones de lenguaje. Todo esto es compatible e implementado en el marco de las bibliotecas Microsoft.CodeAnalysis: utilizamos resultados listos para usar. Por lo tanto, el soporte para la nueva sintaxis se implementa actualizando las bibliotecas.

Por supuesto, en lo que respecta al análisis estático, aquí ya tiene que hacer todo usted mismo, en particular, para procesar nuevas construcciones de lenguaje. Sí, obtenemos el nuevo árbol de sintaxis automáticamente al usar la versión más reciente de Roslyn, pero debemos enseñarle al analizador cómo percibir y procesar los nodos nuevos / modificados del árbol.

Creo que la innovación más comentada en C # 8 son los tipos de referencia anulables. No escribiré sobre ellos aquí: este es un tema bastante amplio digno de un artículo separado (que ya está en proceso de redacción). En general, hasta ahora hemos decidido ignorar las anotaciones anulables en nuestro mecanismo de flujo de datos (es decir, las entendemos, analizamos y omitimos). El hecho es que, a pesar del tipo de referencia no anulable de la variable, aún puede escribir nulo en ella de manera bastante simple (o por error), lo que puede conducir a NRE al desreferenciar el enlace correspondiente. En este caso, nuestro analizador puede ver un error similar y advertir sobre el uso de una referencia potencialmente nula (por supuesto, si ve tal asignación en el código) a pesar del tipo de referencia no anulable de la variable.

Quiero señalar que el uso de tipos de referencia anulables y la sintaxis que lo acompaña abre la posibilidad de escribir código muy interesante. Para nosotros, llamamos a esto la "sintaxis emocional". El siguiente código se compila bastante bien:

 obj.Calculate(); obj?.Calculate(); obj.Calculate(); obj!?.Calculate(); obj!!!.Calculate(); 

Por cierto, durante el curso de mi trabajo encontré un par de formas de 'llenar' Visual Studio usando la nueva sintaxis. El hecho es que no puedes limitar el número de caracteres a uno cuando pones '!'. Es decir, puede escribir no solo un código del formulario:

 object temp = null! 

pero también:

 object temp = null!!!; 

Puedes pervertir, seguir adelante y escribir así:

 object temp = null!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!; 

Este código se compila correctamente. Pero si solicita información sobre el árbol de sintaxis utilizando el Visualizador de sintaxis del SDK de la plataforma del compilador de .NET, Visual Studio se bloqueará.

Puede obtener información sobre el problema desde el Visor de eventos:

 Faulting application name: devenv.exe, version: 16.0.28803.352, time stamp: 0x5cc37012 Faulting module name: WindowsBase.ni.dll, version: 4.8.3745.0, time stamp: 0x5c5bab63 Exception code: 0xc00000fd Fault offset: 0x000c9af4 Faulting process id: 0x3274 Faulting application start time: 0x01d5095e7259362e Faulting application path: C:\Program Files (x86)\ Microsoft Visual Studio\2019\Community\Common7\IDE\devenv.exe Faulting module path: C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\ WindowsBase\4480dfedf0d7b4329838f4bbf953027d\WindowsBase.ni.dll Report Id: 66d41eb2-c658-486d-b417-02961d9c3e4f Faulting package full name: Faulting package-relative application ID: 

Si va más allá y aumenta el número de signos de exclamación varias veces, Visual Studio se reducirá por sí solo: ya no se necesita la ayuda de Syntax Visualizer. Las bibliotecas Microsoft.CodeAnalysis y el compilador csc.exe tampoco resumen este código.

Por supuesto, estos son ejemplos sintéticos, pero aún así este hecho me pareció divertido.

Conjunto de herramientas


Nota Una vez más me enfrento al problema de traducir la palabra 'evaluación' en el contexto de una conversación sobre proyectos de MSBuild. La traducción, que parecía tener el significado más cercano y al mismo tiempo sonaba normal, era "construir un modelo de proyecto". Si tiene opciones de traducción alternativas, puede escribirme, será interesante leer.

Era obvio que actualizar el conjunto de herramientas sería la tarea más lenta. Más precisamente, lo parecía desde el principio, pero ahora me inclino a creer que lo más problemático fue el soporte de complementos. En particular, esto se debió al conjunto de herramientas ya existente y al mecanismo para construir el modelo de proyecto MSBuild, que ahora funcionaba con éxito, aunque requería expansión. No es necesario escribir algoritmos desde cero simplificando enormemente la tarea. Nuestra apuesta por "nuestro" conjunto de herramientas, realizada en la etapa de soporte de Visual Studio 2017, se justificó una vez más.

Tradicionalmente, todo comienza con la actualización de los paquetes NuGet. En la pestaña de administración de paquetes NuGet para soluciones, hay un botón 'Actualizar' ... que no funciona. Al actualizar todos los paquetes, surgieron múltiples conflictos de versiones, y resolverlos todos parecía no ser muy correcto. Una forma más dolorosa, pero, al parecer, más confiable es actualizar 'pieza por pieza' los paquetes Microsoft.Build / Microsoft.CodeAnalysis objetivo.

Una de las diferencias se identificó inmediatamente mediante pruebas de reglas de diagnóstico: la estructura del árbol de sintaxis para un nodo ya existente ha cambiado. Está bien, corregido rápidamente.

Permítame recordarle que durante el trabajo probamos analizadores (C #, C ++, Java) en proyectos de código abierto. Esto le permite probar bien las reglas de diagnóstico: encontrar, por ejemplo, falsos positivos u obtener una idea de qué otros casos no se han considerado (reduzca el número de falsos negativos). Estas pruebas también ayudan a rastrear la posible regresión en la etapa inicial de actualización de bibliotecas / conjunto de herramientas. Y esta vez no fue la excepción, ya que surgieron varios problemas.

Un problema fue el deterioro del comportamiento dentro de las bibliotecas de CodeAnalysis. Más específicamente, en varios proyectos en el código de la biblioteca, se produjeron excepciones durante varias operaciones: obtención de información semántica, apertura de proyectos, etc.

Los lectores atentos del artículo sobre el soporte de Visual Studio 2017 recuerdan que nuestro kit de distribución tiene un código auxiliar: el archivo MSBuild.exe tiene un tamaño de 0 bytes.

Esta vez tuve que ir más allá: ahora el kit de distribución también contiene apéndices de compilación vacíos: csc.exe, vbc.exe, VBCSCompiler.exe. Por qué El camino a esto comenzó con el análisis de uno de los proyectos en la base de prueba, en el que aparecieron los 'diffs' de los informes: faltaban varias advertencias al usar la nueva versión del analizador.

El problema resultó ser símbolos de compilación condicional: al analizar un proyecto utilizando la nueva versión del analizador, algunos de los símbolos se extrajeron incorrectamente. Para comprender mejor qué causó este problema, tuve que sumergirme en las bibliotecas de Roslyn.

Para analizar los caracteres de compilación condicional, use el método GetDefineConstantsSwitch de la clase Csc de la biblioteca Microsoft.Build.Tasks.CodeAnalysis . El análisis se realiza utilizando el método String.Split en varios delimitadores:

 string[] allIdentifiers = originalDefineConstants.Split(new char[] { ',', ';', ' ' }); 

Este método de análisis funciona bien, todos los símbolos de compilación condicional necesarios se extraen con éxito. Excavando más.

El siguiente punto clave es la llamada al método ComputePathToTool de la clase ToolTask . Este método crea la ruta al archivo ejecutable ( csc.exe ) y comprueba su presencia. Si el archivo existe, se devuelve la ruta, de lo contrario se devuelve un valor nulo .

Código de llamada:

 .... string pathToTool = ComputePathToTool(); if (pathToTool == null) { // An appropriate error should have been logged already. return false; } .... 

Dado que no hay un archivo csc.exe (parece que, ¿por qué lo necesitamos?), PathToTool en esta etapa es nulo , y el método actual ( ToolTask.Execute ) completa su ejecución con el resultado falso . Como resultado, se ignoran los resultados de la tarea, incluidos los símbolos de compilación condicional resultantes.

Bueno, veamos qué sucede si coloca el archivo csc.exe en la ubicación esperada.

En este caso, pathToTool indica la ubicación real del archivo existente y la ejecución del método ToolTask.Execute continúa. El siguiente punto clave es la llamada al método ManagedCompiler.ExecuteTool . Y comienza de la siguiente manera:

 protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { if (ProvideCommandLineArgs) { CommandLineArgs = GetArguments(commandLineCommands, responseFileCommands) .Select(arg => new TaskItem(arg)).ToArray(); } if (SkipCompilerExecution) { return 0; } .... } 

La propiedad SkipCompilerExecution es verdadera (lógicamente, en realidad no estamos compilando). Como resultado, el método de llamada (ya mencionado ToolTask.Execute ) verifica que el código de retorno del método ExecuteTool sea ​​0 y, de ser así, completa su ejecución con el valor verdadero . Lo que tenía detrás de csc.exe estaba allí: el compilador real o 'Guerra y paz' ​​de Leo Tolstoi en forma de texto no importa.

Como resultado, el problema principal surge del hecho de que la secuencia de pasos se define en el siguiente orden:

  • verificar la existencia del compilador;
  • verificar si el compilador necesita ser iniciado;

No al revés. Los stubs del compilador resuelven con éxito este problema.

Bueno, ¿cómo surgieron los caracteres de la compilación exitosa si no se detectó el archivo csc.exe (y se ignoró el resultado de la tarea)?

Hay un método para este caso: CSharpCommandLineParser.ParseConditionalCompilationSymbols de la biblioteca Microsoft.CodeAnalysis.CSharp . El análisis también se realiza mediante el método String.Split con varios delimitadores:

 string[] values = value.Split(new char[] { ';', ',' } /*, StringSplitOptions.RemoveEmptyEntries*/); 

¿Nota la diferencia con el conjunto de delimitadores del método Csc.GetDefineConstantsSwitch ? En este caso, el espacio en blanco no es un separador. Por lo tanto, si los caracteres de compilación condicional se escribieron con un espacio, este método los analizará incorrectamente.

Esta situación surgió en proyectos problemáticos: los caracteres de compilación condicional se escribieron en ellos con un espacio y se analizaron con éxito usando GetDefineConstantsSwitch , pero no ParseConditionalCompilationSymbols .

Otro problema que se reveló después de actualizar las bibliotecas fue el deterioro del comportamiento en varios casos, en particular, en proyectos que no se recopilaron. Surgieron problemas en las bibliotecas Microsoft.CodeAnalysis y se nos devolvieron en forma de varias excepciones: ArgumentNullException (algunos registradores internos no se inicializaron), NullReferenceException y otros.

Quiero hablar sobre uno de estos problemas a continuación: me pareció bastante interesante.

Encontramos este problema al verificar la última versión del proyecto Roslyn: se lanzó una excepción NullReferenceException desde el código de una de las bibliotecas. Debido a suficiente información detallada sobre la ubicación del problema, rápidamente encontramos el código del problema y, por interés, decidimos intentar ver si el problema se repite cuando se trabaja desde Visual Studio.

Bueno, fue posible reproducirlo en Visual Studio (el experimento se realizó en Visual Studio 16.0.3). Para hacer esto, necesitamos una definición de clase de la siguiente forma:

 class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } } 

También necesitaremos Syntax Visualizer (parte del SDK de .NET Compiler Platform). Es necesario solicitar TypeSymbol (elemento de menú "View TypeSymbol (if any)") desde el nodo del árbol de sintaxis de tipo ConstantPatternSyntax ( nulo ). Después de eso, Visual Studio se reiniciará, y en el Visor de eventos puede ver información sobre el problema, en particular, encontrar el seguimiento de la pila:

 Application: devenv.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.NullReferenceException at Microsoft.CodeAnalysis.CSharp.ConversionsBase. ClassifyImplicitBuiltInConversionSlow( Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, System.Collections.Generic.HashSet'1 <Microsoft.CodeAnalysis.DiagnosticInfo> ByRef) at Microsoft.CodeAnalysis.CSharp.ConversionsBase.ClassifyBuiltInConversion( Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, System.Collections.Generic.HashSet'1 <Microsoft.CodeAnalysis.DiagnosticInfo> ByRef) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoForNode( Microsoft.CodeAnalysis.CSharp.BoundNode, Microsoft.CodeAnalysis.CSharp.BoundNode, Microsoft.CodeAnalysis.CSharp.BoundNode) at Microsoft.CodeAnalysis.CSharp.MemberSemanticModel.GetTypeInfoWorker( Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.SyntaxTreeSemanticModel.GetTypeInfoWorker( Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfo( Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoFromNode( Microsoft.CodeAnalysis.SyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoCore( Microsoft.CodeAnalysis.SyntaxNode, System.Threading.CancellationToken) .... 

Como puede ver, la causa del problema es la desreferenciación de la referencia nula.

Como mencioné anteriormente, encontramos el mismo problema durante la prueba del analizador. Si utiliza las bibliotecas de depuración Microsoft.CodeAnalysis para compilar el analizador, puede llegar al lugar exacto depurando solicitando TypeSymbol desde el nodo deseado en el árbol de sintaxis.

Como resultado, llegamos al método ClassifyImplicitBuiltInConversionSlow mencionado en el seguimiento de la pila anterior :

 private Conversion ClassifyImplicitBuiltInConversionSlow( TypeSymbol source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); if (source.SpecialType == SpecialType.System_Void || destination.SpecialType == SpecialType.System_Void) { return Conversion.NoConversion; } Conversion conversion = ClassifyStandardImplicitConversion(source, destination, ref useSiteDiagnostics); if (conversion.Exists) { return conversion; } return Conversion.NoConversion; } 

El problema es que el parámetro de destino es nulo en este caso. En consecuencia, al llamar a destination.SpecialType, se genera una NullReferenceException .Sí, Debug.Assert es más alto que desreferenciar , pero esto no es suficiente, ya que de hecho no protege contra nada, solo ayuda a identificar el problema en las versiones de depuración de las bibliotecas. O no ayuda.

Cambios en la construcción de un modelo de proyectos C ++


Aquí no sucedió nada particularmente interesante: los algoritmos antiguos no requerían modificaciones significativas, de lo que sería interesante hablar. Hubo, quizás, dos puntos en los que tiene sentido detenerse.

Primero, tuvimos que modificar los algoritmos que dependen del valor de ToolsVersion para escribirse en formato numérico. Sin entrar en detalles, hay varios casos en los que necesita comparar conjuntos de herramientas y elegir, por ejemplo, una nueva versión más actual. Esta versión, respectivamente, tenía un valor numérico más alto. Se calculó que ToolsVersion, correspondiente a la nueva versión de MSBuild / Visual Studio, será igual a 16.0. Cualquiera que sea el caso ... Por razones de interés, cito una tabla sobre cómo los valores de varias propiedades cambiaron en diferentes versiones de Visual Studio:
Nombre del producto visual studio
Número de versión de Visual Studio
Versión de herramientas
Versión PlatformToolset
Visual studio 2010
10,0
4.0 4.0
100
Visual studio 2012
11,0
4.0 4.0
110
Visual studio 2013
12,0
12,0
120
Visual studio 2015
14.0
14.0
140
Visual studio 2017
15,0
15,0
141
Visual studio 2019
16,0
Actual
142

El chiste, por supuesto, está desactualizado, pero no puede evitar recordar cambios en las versiones de Windows y Xbox para comprender que predecir valores futuros (sin importar el nombre y la versión), en el caso de Microsoft, es algo inestable. :)

La solución fue bastante simple: introdujo la priorización de conjuntos de herramientas (asignación de una entidad de prioridad separada).

El segundo punto son los problemas cuando se trabaja en Visual Studio 2017 o en un entorno adyacente (por ejemplo, la presencia de la variable de entorno VisualStudioVersion ). El hecho es que calcular los parámetros necesarios para construir un modelo de un proyecto C ++ es mucho más complicado que construir un modelo de un proyecto .NET. En el caso de .NET, utilizamos nuestro propio conjunto de herramientas y el valor correspondiente de ToolsVersion. En el caso de C ++, podemos construir tanto en nuestros propios conjuntos de herramientas existentes como en el sistema. A partir de Build Tools en Visual Studio 2017, los conjuntos de herramientas se registran en el archivo MSBuild.exe.config, no en el registro. En consecuencia, no podemos obtenerlos de la lista general de conjuntos de herramientas (por ejemplo, a través de Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.Toolsets ), a diferencia de esos conjuntos de herramientas que se registran en el registro (correspondiente a <= Visual Studio 2015) .

Como consecuencia de todo lo anterior, no funcionará construir un modelo de proyecto usando ToolsVersion 15.0 , ya que el sistema no verá el conjunto de herramientas necesario. Conjunto de herramientas más actual: actual- estará disponible al mismo tiempo, ya que este es nuestro propio conjunto de herramientas, por lo tanto, no hay tal problema para Visual Studio 2019. La solución resultó ser simple y permitió resolver el problema sin cambiar los algoritmos existentes para construir el modelo del proyecto, agregando otro a la lista de sus propios conjuntos de herramientas, Actual , 15.0 .

Cambios en la construcción de un modelo de proyectos C # .NET Core


En el marco de esta tarea, se resolvieron 2 problemas a la vez, ya que resultaron estar relacionados:

  • después de agregar el conjunto de herramientas 'Actual', el análisis de los proyectos .NET Core para Visual Studio 2017 dejó de funcionar;
  • El análisis de proyectos de .NET Core en un sistema donde al menos una versión de Visual Studio no estaba instalada no funcionó.

El problema en ambos casos era el mismo: algunos de los archivos .targets / .props básicos se buscaron de forma incorrecta. Esto llevó al hecho de que no era posible construir un modelo de proyecto utilizando nuestro conjunto de herramientas.

En ausencia de Visual Studio, podría ver dicho error (con la versión anterior de toolset'a - 15.0 ):

 The imported project "C:\Windows\Microsoft.NET\Framework64\ 15.0\Microsoft.Common.props" was not found. 

Al compilar el modelo C # .NET Core del proyecto en Visual Studio 2017, puede ver el siguiente problema (con la versión actual del conjunto de herramientas, Actual ):

 The imported project "C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\MSBuild\Current\Microsoft.Common.props" was not found. .... 

Dado que los problemas son similares (pero se ve así), puedes intentar matar dos pájaros de un tiro.

A continuación describo cómo se resolvió este problema sin entrar en detalles técnicos. Estos mismos detalles (sobre la construcción de modelos de proyectos C # .NET Core, así como el cambio de la construcción de modelos en nuestro conjunto de herramientas) están esperando en uno de nuestros futuros artículos. Por cierto, si lee detenidamente el texto anterior, puede notar que esta es la segunda referencia a futuros artículos. :)

Entonces, ¿cómo resolvimos este problema? La solución fue expandir nuestro propio conjunto de herramientas a expensas de los principales archivos .targets / .props del SDK de .NET Core ( Sdk.props , Sdk.targets ). Esto nos permitió tener más control sobre la situación, más flexibilidad en la gestión de importaciones, así como en la construcción de un modelo de proyectos .NET Core en general. Sí, nuestro conjunto de herramientas ha vuelto a crecer un poco, y también tuvimos que agregar lógica para configurar los proyectos de entorno necesarios para construir el modelo .NET Core, pero parece que valió la pena.

Anteriormente, el principio del trabajo al crear un modelo de proyectos .NET Core era el siguiente: simplemente solicitamos esta construcción, y luego todo funcionó a expensas de MSBuild.

Ahora, cuando hemos tomado más control en nuestras propias manos, se ve un poco diferente:

  • preparación del entorno necesario para construir un modelo de proyectos .NET Core;
  • construcción modelo:
    • inicio de construcción utilizando archivos .targets / .props de nuestro conjunto de herramientas'a;
    • construcción continua usando archivos externos.

De los pasos descritos anteriormente, es obvio que establecer el entorno necesario tiene dos objetivos principales:
  • inicie la construcción de modelos utilizando archivos .targets / .props de su propio conjunto de herramientas;
  • redirigir más operaciones a archivos externos .targets / .props.

Para buscar archivos .targets / .props necesarios para construir un modelo de proyectos .NET Core, se utiliza una biblioteca especial: Microsoft.DotNet.MSBuildSdkResolver. El inicio de la construcción utilizando archivos de nuestro conjunto de herramientas se resolvió mediante el uso de una variable de entorno especial utilizada por esta biblioteca: sugerimos dónde importar los archivos necesarios (de nuestro conjunto de herramientas). Como la biblioteca es parte de nuestra distribución, no hay temor de que la lógica cambie repentinamente y deje de funcionar.

Ahora los archivos Sdk se importan primero de nuestro conjunto de herramientas, y dado que podemos cambiarlos fácilmente, el control de la lógica adicional de construir el modelo pasa a nuestras manos. Por lo tanto, podemos determinar por nosotros mismos qué archivos deben importarse y de dónde. Esto también se aplica a Microsoft.Common.props mencionado anteriormente. Importamos este y otros archivos básicos de nuestro propio conjunto de herramientas con confianza en su disponibilidad y contenido.

Después de eso, después de haber completado las importaciones necesarias y establecer una serie de propiedades, transferimos el control adicional de la construcción de modelos al .NET Core SDK real, donde tienen lugar el resto de las acciones necesarias.

Conclusión


En general, el soporte para Visual Studio 2019 fue más fácil que el soporte para Visual Studio 2017, que, según lo veo, se debe a varios factores. Primero, Microsoft no cambió tantas cosas como entre Visual Studio 2015 y Visual Studio 2017. Sí, cambiamos el conjunto de herramientas principal, comenzamos a orientar los complementos para Visual Studio en asincronía, pero de todos modos. El segundo, ya teníamos una solución lista con nuestro propio conjunto de herramientas y modelos de proyectos de construcción: no había necesidad de inventar todo de nuevo, era suficiente solo para expandir la solución existente. El soporte relativamente simple para analizar proyectos de .NET Core para nuevas condiciones (así como para casos de análisis en una máquina donde no hay instancias de Visual Studio instaladas) debido a la expansión de nuestro sistema de construcción de modelos de proyectos también da la esperanza de que tomamos la decisión correcta.Habiendo decidido tomar el control sobre ti mismo.

Pero aún así, me gustaría repetir un pensamiento que estaba en el artículo anterior nuevamente: a veces, usar soluciones preparadas no es tan simple como parece a primera vista.



Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Sergey Vasiliev. Soporte de Visual Studio 2019 en PVS-Studio

Source: https://habr.com/ru/post/454738/


All Articles