Suporte do Visual Studio 2019 no PVS-Studio


O suporte ao Visual Studio 2019 no PVS-Studio afetou imediatamente vários componentes diferentes: o próprio plug-in IDE, aplicativo de análise de linha de comando, analisadores C ++ e C #, além de vários utilitários. Vou falar brevemente sobre os problemas que encontramos no suporte à nova versão do IDE e como resolvê-los.

Antes de começar, quero olhar um pouco para trás para rastrear o histórico de suporte para versões anteriores do Visual Studio, que dará uma melhor compreensão de nossa visão da tarefa e das decisões tomadas em determinadas situações.

Começando com a primeira versão do analisador PVS-Studio, na qual o plug-in para o ambiente do Visual Studio apareceu (era a versão do Visual Studio 2005), oferecer suporte a novas versões do Visual Studio era uma tarefa bastante simples para nós - basicamente se resumia à atualização do arquivo de projeto do plug-in e dependências de várias APIs de extensão do Visual Studio. Às vezes, era necessário oferecer suporte adicional a novos recursos da linguagem C ++, que o compilador do Visual C ++ estava aprendendo gradualmente, mas isso também geralmente não causava problemas imediatamente antes do lançamento da próxima edição do Visual Studio. E havia apenas um analisador no PVS-Studio na época - para as linguagens C e C ++.

Tudo mudou para o lançamento do Visual Studio 2017. Além do fato de muitas APIs de extensão desse IDE terem mudado significativamente nesta versão, após a atualização, tivemos problemas para garantir a compatibilidade com versões anteriores do trabalho do novo analisador C # que havia aparecido naquele momento (assim como nossa nova camada C ++ analisador para projetos MSBuild) com versões mais antigas do MSBuild \ Visual Studio.

Portanto, antes de ler este artigo, recomendo que você leia o artigo relacionado sobre o suporte ao Visual Studio 2017: "Suporte ao Visual Studio 2017 e Roslyn 2.0 no PVS-Studio: às vezes, usar soluções prontas não é tão fácil quanto parece à primeira vista ". O artigo mencionado acima descreve os problemas que encontramos pela última vez, bem como os esquemas de interação de vários componentes (por exemplo, PVS-Studio, MSBuild, Roslyn). Compreender essa interação será útil ao ler este artigo.

Por fim, a solução para esses problemas trouxe mudanças significativas ao nosso analisador e, como esperávamos, as novas abordagens que aplicamos possibilitarão o suporte a versões atualizadas do Visual Studio \ MSBuild com muito mais facilidade e rapidez no futuro. Em parte, essa suposição já foi confirmada pelo lançamento de inúmeras atualizações do Visual Studio 2017. Essa nova abordagem ajudou no suporte do Visual Studio 2019? Sobre isso abaixo.

PVS-Studio Plugin para Visual Studio 2019


Tudo começou, ao que parece, não é ruim. Foi fácil o suficiente para portar o plug-in para o Visual Studio 2019, onde ele começou e funcionou bem. Apesar disso, 2 problemas foram imediatamente revelados, o que prometeu problemas futuros.

A primeira é a interface IVsSolutionWorkspaceService , usada para oferecer suporte ao modo Lightweight Solution Load, que, a propósito, foi desabilitado em uma das atualizações anteriores no Visual Studio 2017, foi decorado com o atributo Deprecated , que era apenas um aviso durante a montagem, mas prometia mais no futuro problemas A Microsoft introduziu rapidamente esse modo e o abandonou ... Lidamos com esse problema de maneira simples - recusamos usar a interface apropriada.

A segunda - ao carregar o Visual Studio com o plug-in, a seguinte mensagem apareceu: O Visual Studio detectou uma ou mais extensões que estão em risco ou não estão funcionando em um recurso VS update.

A exibição dos logs de inicialização do Visual Studio (arquivo ActivityLog) finalmente pontilhou o 'i':

Aviso: a extensão 'PVS-Studio' usa o recurso 'carregamento automático síncrono' do Visual Studio. Esse recurso não será mais suportado em uma atualização futura do Visual Studio 2019, quando a extensão não funcionará. Entre em contato com o fornecedor da extensão para obter uma atualização.

Para nós, isso significava uma coisa: alterar a maneira como o plug-in é carregado no modo assíncrono. Espero que você não fique chateado se não sobrecarregar você com detalhes sobre a interação com interfaces COM do Visual Studio, e examinarei as alterações brevemente.

A Microsoft possui um artigo sobre a criação de plugins assincronamente carregados: " Como: usar o AsyncPackage para carregar o VSPackages em segundo plano ". Ao mesmo tempo, era óbvio para todos que o assunto não se limitaria a essas mudanças.

Uma das principais mudanças é o método de carregamento, ou melhor, a inicialização. Anteriormente, a inicialização necessária acontecia em dois métodos - o método Initialize substituído da nossa classe de herança Package e o método OnShellPropertyChange . A necessidade de transferir parte da lógica para o método OnShellPropertyChange se deve ao fato de que quando o plug-in é carregado de forma síncrona, o Visual Studio ainda não pode ser totalmente carregado e inicializado e, como resultado, nem todas as ações necessárias podem ser executadas no estágio de inicialização do plug-in. Uma opção para resolver esse problema é aguardar o Visual Studio sair do estado de 'zumbi' e atrasar essas ações. Essa é a lógica e foi renderizada no OnShellPropertyChange com uma verificação do estado 'zumbi'.

Na classe abstrata AsyncPackage , da qual os plugins carregados de forma assíncrona são herdados, o método Initialize possui um modificador selado ; portanto, a inicialização deve ser feita no método InitializeAsync substituído, que foi feito. Também tivemos que mudar a lógica com o rastreamento do estado 'zumbi' do Visual Studio, porque paramos de receber essas informações no plug-in. No entanto, várias ações que precisavam ser executadas após a inicialização do plug-in não desapareceram. A solução foi usar o método OnPackageLoaded da interface IVsPackageLoadEvents , onde foram executadas ações que exigiam execução adiada.

Outro problema que surge logicamente do fato do carregamento assíncrono do plug-in é a ausência de comandos do plug-in PVS-Studio no momento de iniciar o Visual Studio. Quando você abre o log do analisador clicando duas vezes no gerenciador de arquivos (se precisar abri-lo pelo Visual Studio), a versão necessária do devenv.exe foi iniciada com o comando para abrir o relatório do analisador. O comando de inicialização se parecia com isso:

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

O sinalizador "/ command" aqui é usado para invocar um comando registrado no Visual Studio. Agora, essa abordagem não funcionou, pois os comandos não estavam disponíveis até o download do plug-in. Como resultado, tive que parar na "muleta" ao analisar a linha de inicialização do devenv.exe após carregar o plug-in e, se houver uma representação em cadeia do comando para abrir o log - na verdade, carregando o log. Portanto, nesse caso, tendo se recusado a usar a interface “correta” para trabalhar com comandos, foi possível manter a funcionalidade necessária atrasando o carregamento do log até que o plug-in esteja totalmente carregado.

Parece que foi resolvido e tudo funciona - tudo carrega e abre corretamente, não há avisos - finalmente.

E então o inesperado acontece - Pavel (olá!) Instala um plug-in, após o qual ele pergunta por que não fizemos carregamento assíncrono?

Dizer que ficamos surpresos - sem dizer nada - como assim? Não, sério - aqui está a nova versão do plug-in instalada, aqui está a mensagem de que o pacote pode ser baixado de forma síncrona. Instalamos com Alexander (e olá para você também) a mesma versão do plugin em nossas máquinas - está tudo bem. Nada está claro - decidimos ver quais versões das bibliotecas PVS-Studio foram carregadas no Visual Studio. E, de repente, acontece que as versões das bibliotecas PVS-Studio para Visual Studio 2017 são usadas, apesar de a versão correta das bibliotecas estar no pacote VSIX - para o Visual Studio 2019.

Tendo mexido com o VSIXInstaller, consegui encontrar a causa do problema - o cache do pacote. A teoria também foi confirmada pelo fato de que, ao restringir os direitos de acesso ao pacote no cache (C: \ ProgramData \ Microsoft \ VisualStudio \ Packages), o VSIXInstaller gravou informações de erro no log. Surpreendentemente, se não houver erro, nenhuma informação sobre o fato de o pacote estar instalado a partir do cache não será gravada no log.

Nota Estudando o comportamento do VSIXInstaller e das bibliotecas relacionadas, ele observou consigo mesmo que é muito legal que Roslyn e MSBuild tenham código-fonte aberto que facilita a leitura, depuração e rastreamento da lógica do trabalho.

Como resultado, aconteceu o seguinte - ao instalar o plug-in, o VSIXInstaller viu que o pacote correspondente já estava no cache (havia um pacote .vsix para o Visual Studio 2017) e o usou em vez do pacote instalado real durante a instalação. Por que isso não leva em conta as restrições / requisitos descritos em .vsixmanifest (por exemplo, a versão do Visual Studio para a qual você pode instalar a extensão) é uma questão em aberto. Por esse motivo, apesar de o .vsixmanifest conter as restrições necessárias, o plug-in projetado para o Visual Studio 2017 foi instalado no Visual Studio 2019.

O pior é que essa instalação quebrou o gráfico de dependência do Visual Studio e, embora externamente possa parecer que o ambiente de desenvolvimento estava funcionando bem, na verdade tudo estava muito ruim. Era impossível instalar e desinstalar extensões, fazer atualizações e assim por diante. O processo de 'recuperação' também foi bastante desagradável, porque foi necessário excluir a extensão (os arquivos correspondentes) e editar manualmente os arquivos de configuração que armazenam informações sobre o pacote instalado. Em geral - não é agradável o suficiente.

Para resolver esse problema e evitar situações semelhantes no futuro, foi decidido criar um GUID para o novo pacote, a fim de separar completamente os pacotes do Visual Studio 2017 e do Visual Studio 2019 (não existe esse problema com pacotes mais antigos, e eles sempre usavam um GUID comum).

E como estávamos conversando sobre surpresas desagradáveis, mencionarei mais uma coisa: depois de atualizar para a Visualização 2, o item de menu 'mudou' na guia 'Extensões'. Parece que está tudo bem, mas o acesso às funções do plug-in se tornou menos conveniente. Nas versões subseqüentes do Visual Studio 2019, incluindo a versão de lançamento, esse comportamento foi preservado. Não encontrei nenhuma menção a esse 'recurso' no momento do seu lançamento na documentação ou no blog.

Agora, ao que parece, tudo funciona e com o suporte ao plug-in para o Visual Studio 2019 está concluído. No dia seguinte ao lançamento do PVS-Studio 7.02 com suporte ao Visual Studio 2019, descobriu-se que não era assim - outro problema com o plug-in assíncrono foi encontrado. Para o usuário, isso poderia ser assim: ao abrir uma janela com os resultados da análise (ou iniciar a análise), nossa janela às vezes era exibida “vazia” - ela não continha conteúdo: botões, uma tabela com avisos do analisador, etc.

De fato, esse problema foi repetido algumas vezes no decorrer do trabalho. No entanto, ele foi repetido apenas em uma máquina e começou a aparecer somente após a atualização do Visual Studio em uma das primeiras versões do 'Preview' - havia suspeitas de que algo havia quebrado durante a instalação / atualização. Com o tempo, no entanto, o problema deixou de ser repetido, mesmo nesta máquina, e decidimos que ela "era reparada por si mesma". Acabou que não - tão sortudo. Mais precisamente, sem sorte.

O problema acabou na ordem de inicialização da própria janela do ambiente (o descendente da classe ToolWindowPane ) e seu conteúdo (na verdade, nosso controle com a grade e os botões). Sob certas condições, a inicialização do controle ocorreu antes da inicialização do painel e, apesar de tudo funcionar sem erros, o método FindToolWindowAsync (criando uma janela na primeira chamada) funcionou corretamente, mas o controle permaneceu invisível. Corrigimos isso adicionando a inicialização lenta para nosso controle ao código de preenchimento do painel.

Suporte C # 8.0


Usar o Roslyn como base para o analisador tem uma vantagem significativa - não há necessidade de manter manualmente novas construções de linguagem. Tudo isso é suportado e implementado na estrutura das bibliotecas Microsoft.CodeAnalysis - usamos resultados prontos. Portanto, o suporte para a nova sintaxe é implementado atualizando as bibliotecas.

Obviamente, no que diz respeito à análise estática, aqui você já precisa fazer tudo sozinho, em particular, para processar novas construções de linguagem. Sim, obtemos a nova árvore de sintaxe automaticamente usando a versão mais recente do Roslyn, mas precisamos ensinar ao analisador como perceber e processar nós novos / alterados da árvore.

Eu acho que a inovação mais comentada no C # 8 são os tipos de referência anuláveis. Não vou escrever sobre eles aqui - este é um tópico bastante amplo, digno de um artigo separado (que já está em processo de escrita). Em geral, até agora decidimos ignorar anotações anuláveis ​​em nosso mecanismo de fluxo de dados (ou seja, entendemos, analisamos e ignoramos). O fato é que, apesar do tipo de referência não anulável da variável, você ainda pode escrever nulo nela de maneira bastante simples (ou por engano), o que pode levar ao NRE ao desreferenciar o link correspondente. Nesse caso, nosso analisador pode ver um erro semelhante e emitir um aviso sobre o uso de uma referência potencialmente nula (é claro, se houver uma atribuição desse tipo no código), apesar do tipo de referência não nulo da variável.

Quero observar que o uso de tipos de referência anuláveis ​​e a sintaxe que acompanha abrem a possibilidade de escrever código muito interessante. Para nós mesmos, chamamos isso de 'sintaxe emocional'. O código abaixo compila muito bem:

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

A propósito, durante o curso do meu trabalho, encontrei algumas maneiras de 'preencher' o Visual Studio usando a nova sintaxe. O fato é que você não pode limitar o número de caracteres a um quando coloca '!'. Ou seja, você pode escrever não apenas um código do formulário:

 object temp = null! 

mas também:

 object temp = null!!!; 

Você pode perverter, vá em frente e escreva assim:

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

Este código é compilado com sucesso. Mas se você solicitar informações sobre a árvore de sintaxe usando o Visualizador de Sintaxe do .NET Compiler Platform SDK, o Visual Studio falhará.

Você pode obter informações sobre o problema no Visualizador 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: 

Se você aumentar ainda mais o número de pontos de exclamação várias vezes, o Visual Studio cairá por si só - a ajuda do Syntax Visualizer não será mais necessária. As bibliotecas Microsoft.CodeAnalysis e o compilador csc.exe também não digerem esse código.

Claro, esses são exemplos sintéticos, mas ainda assim esse fato me pareceu engraçado.

Conjunto de ferramentas


Nota Mais uma vez, enfrento o problema de traduzir a palavra 'avaliação' no contexto de uma conversa sobre projetos do MSBuild. A tradução, que parecia mais próxima do significado e ao mesmo tempo parecia normal, estava “construindo um modelo de projeto”. Se você tem opções alternativas de tradução - pode escrever para mim, será interessante ler.

Era óbvio que atualizar o conjunto de ferramentas seria a tarefa mais demorada. Mais precisamente, parecia assim desde o início, mas agora estou inclinado a acreditar que o mais problemático foi o suporte ao plugin. Em particular, isso ocorreu devido ao conjunto de ferramentas já existente e ao mecanismo de criação do modelo de projeto MSBuild, que funcionava com sucesso agora, embora exigisse expansão. Não há necessidade de escrever algoritmos do zero simplificou bastante a tarefa. Nossa aposta no conjunto de ferramentas "our", feita na fase de suporte ao Visual Studio 2017, foi justificada mais uma vez.

Tradicionalmente, tudo começa com a atualização dos pacotes NuGet. Na guia de gerenciamento de pacotes do NuGet para soluções, há um botão 'Atualizar' ... que não funciona. Ao atualizar todos os pacotes, surgiram vários conflitos de versão, e resolvê-los todos parecia não estar muito correto. Uma maneira mais dolorosa, mas, ao que parece, mais confiável é atualizar 'peça por peça' os pacotes de destino Microsoft.Build / Microsoft.CodeAnalysis.

Uma das diferenças foi identificada imediatamente por testes de regras de diagnóstico - a estrutura da árvore de sintaxe para um nó já existente foi alterada. Está tudo bem, corrigido rapidamente.

Deixe-me lembrá-lo de que durante o trabalho testamos analisadores (C #, C ++, Java) em projetos de código aberto. Isso permite que você teste bem as regras de diagnóstico - encontre, por exemplo, falsos positivos ou tenha uma idéia do que outros casos não foram considerados (reduza o número de falsos negativos). Esses testes também ajudam a rastrear a possível regressão no estágio inicial da atualização das bibliotecas / conjunto de ferramentas. E desta vez não foi exceção, pois vários problemas surgiram.

Um problema foi a deterioração do comportamento nas bibliotecas CodeAnalysis. Mais especificamente, em vários projetos no código da biblioteca, ocorreram exceções durante várias operações - obtenção de informações semânticas, abertura de projetos etc.

Os leitores atentos do artigo sobre o suporte do Visual Studio 2017 lembram que nosso kit de distribuição possui um esboço - o arquivo MSBuild.exe tem 0 bytes de tamanho.

Dessa vez, tive que ir além - agora o kit de distribuição também contém stubs de compilador vazios - csc.exe, vbc.exe, VBCSCompiler.exe. Porque O caminho para isso começou com a análise de um dos projetos na base de testes, no qual apareceram os 'diffs' dos relatórios - vários avisos estavam ausentes ao usar a nova versão do analisador.

O problema acabou sendo símbolos de compilação condicional - ao analisar um projeto usando a nova versão do analisador, alguns dos símbolos foram extraídos incorretamente. Para entender melhor o que causou esse problema, tive que mergulhar nas bibliotecas de Roslyn.

Para analisar caracteres de compilação condicional, use o método GetDefineConstantsSwitch da classe Csc da biblioteca Microsoft.Build.Tasks.CodeAnalysis . A análise é realizada usando o método String.Split em vários delimitadores:

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

Esse método de análise funciona bem, todos os símbolos de compilação condicional necessários são extraídos com êxito. Cavando mais.

O próximo ponto-chave é a chamada para o método ComputePathToTool da classe ToolTask . Esse método cria o caminho para o arquivo executável ( csc.exe ) e verifica sua presença. Se o arquivo existir, o caminho para ele será retornado, caso contrário, nulo será retornado.

Código do chamador:

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

Como não há arquivo csc.exe (ao que parece - por que precisamos dele?), O PathToTool nesse estágio é nulo e o método atual ( ToolTask.Execute ) termina sua execução com o resultado false . Como resultado, os resultados da tarefa, incluindo os símbolos de compilação condicional resultantes, são ignorados.

Bem, vamos ver o que acontece se você colocar o arquivo csc.exe no local esperado.

Nesse caso, pathToTool indica o local real do arquivo existente e a execução do método ToolTask.Execute continua. O próximo ponto-chave é a chamada para o método ManagedCompiler.ExecuteTool . E começa da seguinte maneira:

 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; } .... } 

A propriedade SkipCompilerExecution é verdadeira (logicamente, na verdade não estamos compilando). Como resultado, o método de chamada (já mencionado ToolTask.Execute ) verifica se o código de retorno do método ExecuteTool é 0 e, nesse caso, conclui sua execução com o valor true . O que você tinha por trás do csc.exe estava lá - o compilador real ou 'Guerra e Paz' de Leo Tolstoi em forma de texto não importa.

Como resultado, o principal problema decorre do fato de que a sequência de etapas é definida na seguinte ordem:

  • verifique a existência do compilador;
  • verifique se o compilador precisa ser iniciado;

não o contrário. Os stubs do compilador resolvem com êxito esse problema.

Bem, como surgiram os caracteres da compilação bem-sucedida se o arquivo csc.exe não foi detectado (e o resultado da tarefa foi ignorado)?

Existe um método para este caso - CSharpCommandLineParser.ParseConditionalCompilationSymbols da biblioteca Microsoft.CodeAnalysis.CSharp . A análise também é realizada pelo método String.Split com vários separadores:

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

Observe a diferença com o conjunto de delimitadores do método Csc.GetDefineConstantsSwitch ? Nesse caso, o espaço em branco não é um separador. Portanto, se os caracteres de compilação condicional foram escritos com um espaço, esse método os analisará incorretamente.

Essa situação surgiu em projetos problemáticos - os caracteres de compilação condicional foram gravados neles com um espaço e foram analisados ​​com êxito usando GetDefineConstantsSwitch , mas não ParseConditionalCompilationSymbols .

Outro problema que se revelou após a atualização das bibliotecas foi a deterioração do comportamento em vários casos, principalmente em projetos que não foram coletados. Os problemas surgiram nas bibliotecas Microsoft.CodeAnalysis e nos retornaram na forma de várias exceções - ArgumentNullException (algum log interno não foi inicializado), NullReferenceException e outros.

Quero falar sobre um desses problemas abaixo - me pareceu bastante interessante.

Encontramos esse problema ao verificar a versão mais recente do projeto Roslyn - uma NullReferenceException foi lançada do código de uma das bibliotecas. Devido a informações detalhadas suficientes sobre a localização do problema, encontramos rapidamente o código do problema e, por uma questão de interesse, decidimos tentar ver se o problema se repete ao trabalhar no Visual Studio.

Bem - foi possível reproduzi-lo no Visual Studio (o experimento foi realizado no Visual Studio 16.0.3). Para fazer isso, precisamos de uma definição de classe do seguinte formato:

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

Também precisaremos do Syntax Visualizer (parte do .NET Compiler Platform SDK). É necessário solicitar TypeSymbol (item de menu “View TypeSymbol (se houver)”) no nó da árvore de sintaxe do tipo ConstantPatternSyntax ( null ). Depois disso, o Visual Studio será reiniciado e, no Visualizador de Eventos, você poderá ver informações sobre o problema, em particular, encontrar o rastreamento de pilha:

 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 você pode ver, a causa do problema é a desreferenciação da referência nula.

Como mencionei anteriormente, encontramos o mesmo problema durante o teste do analisador. Se você usar as bibliotecas de depuração Microsoft.CodeAnalysis para criar o analisador, poderá chegar ao local exato depurando solicitando TypeSymbol do nó desejado na árvore de sintaxe.

Como resultado, chegamos ao método ClassifyImplicitBuiltInConversionSlow mencionado no rastreamento de pilha acima :

 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; } 

O problema é que o parâmetro de destino é nulo neste caso. Assim, ao chamar destination.SpecialType, uma NullReferenceException é lançada .Sim, Debug.Assert é mais alto que o desreferenciamento , mas isso não é suficiente, pois na verdade não protege contra nada - apenas ajuda a identificar o problema nas versões de depuração das bibliotecas. Ou não ajuda.

Alterações na criação de um modelo de projetos C ++


Nada de particularmente interessante aconteceu aqui - os algoritmos antigos não exigiam modificações significativas, o que seria interessante de se falar. Talvez houvesse dois pontos nos quais faz sentido insistir.

Primeiro, tivemos que modificar os algoritmos que dependem do valor de ToolsVersion para serem escritos em formato numérico. Sem entrar em detalhes - há vários casos em que você precisa comparar conjuntos de ferramentas e escolher, por exemplo, uma nova versão mais atual. Esta versão, respectivamente, teve um valor numérico mais alto. Houve um cálculo de que ToolsVersion, correspondente à nova versão do MSBuild / Visual Studio, será igual a 16.0. Seja qual for o caso ... Por uma questão de interesse, cito uma tabela sobre como os valores de várias propriedades foram alteradas em diferentes versões do Visual Studio:
Nome do produto Visual Studio
Número da versão do Visual Studio
Versão das ferramentas
Versão PlatformToolset
Visual studio 2010
10.0
4.0
100
Visual studio 2012
11,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
Atual
142

A piada, é claro, está desatualizada, mas você não pode deixar de lembrar sobre a alteração de versões do Windows e do Xbox para entender que prever valores futuros (independentemente do nome, das versões), no caso da Microsoft, é uma coisa instável. :)

A solução foi bastante simples - introduzindo a priorização de conjuntos de ferramentas (alocação de uma entidade prioritária separada).

O segundo ponto são problemas ao trabalhar no Visual Studio 2017 ou em um ambiente adjacente (por exemplo, a presença da variável de ambiente VisualStudioVersion ). O fato é que calcular os parâmetros necessários para criar um modelo de um projeto C ++ é muito mais complicado do que criar um modelo de um projeto .NET. No caso do .NET, usamos nosso próprio conjunto de ferramentas e o valor correspondente de ToolsVersion. No caso do C ++, podemos criar conjuntos de ferramentas próprios e existentes no sistema. A partir das Ferramentas de compilação no Visual Studio 2017, os conjuntos de ferramentas são registrados no arquivo MSBuild.exe.config, não no registro. Portanto, não podemos obtê-los da lista geral de conjuntos de ferramentas (por exemplo, através do Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.Toolsets ), diferente dos conjuntos de ferramentas registrados no registro (correspondente a <= Visual Studio 2015) .

Como conseqüência do exposto, não funcionará para criar um modelo de projeto usando o ToolsVersion 15.0 , pois o sistema não verá o conjunto de ferramentas necessário. Conjunto de ferramentas mais atual - Atual- estará disponível ao mesmo tempo, já que este é nosso próprio conjunto de ferramentas; portanto, não existe esse problema para o Visual Studio 2019. A solução acabou sendo simples e permitiu resolver o problema sem alterar os algoritmos existentes para a construção do modelo de projeto - adicionando à lista de seus próprios conjuntos de ferramentas, além do Current, outro - 15.0 .

Alterações na criação de um modelo de projetos C # .NET Core


No âmbito desta tarefa, 2 problemas foram resolvidos de uma só vez, pois estavam relacionados:

  • depois de adicionar o conjunto de ferramentas 'Atual', a análise dos projetos do .NET Core para o Visual Studio 2017 parou de funcionar;
  • A análise de projetos do .NET Core em um sistema em que pelo menos uma versão do Visual Studio não foi instalada não funcionou.

O problema nos dois casos era o mesmo - alguns dos arquivos .targets / .props básicos foram pesquisados ​​de maneira incorreta. Isso levou ao fato de que não era possível construir um modelo de projeto usando nosso conjunto de ferramentas.

Na ausência do Visual Studio, você pode ver esse erro (com a versão anterior do toolset'a - 15.0 ):

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

Ao criar o modelo C # .NET Core do projeto no Visual Studio 2017, você pode ver o seguinte problema (com a versão atual do conjunto de ferramentas, Atual ):

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

Como os problemas são semelhantes (mas parece assim), você pode tentar matar dois coelhos com uma cajadada só.

Abaixo, descrevo como esse problema foi resolvido sem entrar em detalhes técnicos. Esses detalhes (sobre a construção de modelos de projetos C # .NET Core, bem como a alteração da construção de modelos em nosso conjunto de ferramentas) estão aguardando em um de nossos futuros artigos. A propósito, se você ler atentamente o texto acima, poderá notar que esta é a segunda referência a artigos futuros. :)

Então, como resolvemos esse problema? A solução foi expandir nosso próprio conjunto de ferramentas às custas dos principais arquivos .targets / .props do SDK do .NET Core ( Sdk.props , Sdk.targets ). Isso nos permitiu ter mais controle sobre a situação, mais flexibilidade no gerenciamento de importações e na construção de um modelo de projetos do .NET Core em geral. Sim, nosso conjunto de ferramentas voltou a crescer um pouco e também tivemos que adicionar alguma lógica para configurar os projetos de ambiente necessários para a criação do modelo .NET Core, mas parece que valeu a pena.

Anteriormente, o princípio do trabalho ao criar um modelo de projetos do .NET Core era o seguinte: simplesmente solicitávamos essa construção e, em seguida, tudo funcionava às custas do MSBuild.

Agora, quando assumimos mais controle em nossas próprias mãos, parece um pouco diferente:

  • preparação do ambiente necessário para a construção de um modelo de projetos .NET Core;
  • construção de modelos:
    • início da construção usando arquivos .targets / .props do nosso conjunto de ferramentas;
    • construção continuada usando arquivos externos.

A partir das etapas descritas acima, é óbvio que a definição do ambiente necessário tem dois objetivos principais:
  • inicie a construção do modelo usando arquivos .targets / .props do seu próprio conjunto de ferramentas;
  • redirecione outras operações para arquivos .targets / .props externos.

Para procurar arquivos .targets / .props necessários para criar um modelo de projetos do .NET Core, é usada uma biblioteca especial - Microsoft.DotNet.MSBuildSdkResolver. A iniciação da criação usando arquivos do nosso conjunto de ferramentas foi resolvida usando uma variável de ambiente especial usada por esta biblioteca - sugerimos onde importar os arquivos necessários (do nosso conjunto de ferramentas). Como a biblioteca faz parte da nossa distribuição, não há receios de que a lógica mude repentinamente e pare de funcionar.

Agora, os arquivos Sdk são importados primeiro do nosso conjunto de ferramentas e, como podemos alterá-los facilmente, o controle da lógica adicional de construção do modelo passa para nossas mãos. Portanto, podemos determinar por nós mesmos quais arquivos precisam ser importados e de onde. Isso também se aplica aos Microsoft.Common.props mencionados acima. Importamos este e outros arquivos básicos de nosso próprio conjunto de ferramentas com confiança em sua disponibilidade e conteúdo.

Depois disso, após concluir as importações necessárias e definir várias propriedades, transferimos o controle adicional da criação do modelo para o .NET Core SDK real, onde ocorrem as demais ações necessárias.

Conclusão


Em geral, o suporte ao Visual Studio 2019 foi mais fácil que o suporte ao Visual Studio 2017, que, a meu ver, é devido a vários fatores. Primeiro, a Microsoft não mudou tantas coisas quanto entre o Visual Studio 2015 e o Visual Studio 2017. Sim, alteramos o conjunto de ferramentas principal e começamos a orientar os plug-ins do Visual Studio em assincronia, mas mesmo assim. A segunda - já tínhamos uma solução pronta com nosso próprio conjunto de ferramentas e modelos de projeto de construção - não havia necessidade de inventar tudo de novo, bastava expandir a solução existente. O suporte relativamente simples para analisar projetos do .NET Core para novas condições (bem como para casos de análise em uma máquina em que não há instâncias do Visual Studio instaladas) devido à expansão do nosso sistema de criação de modelos de projeto também dá esperança de que fizemos a escolha certa.Tendo decidido assumir o controle sobre si mesmo.

Ainda assim, gostaria de repetir um pensamento que estava no artigo anterior novamente - às vezes, usar soluções prontas não é tão simples quanto parece à primeira vista.



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Sergey Vasiliev. Suporte do Visual Studio 2019 no PVS-Studio

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


All Articles