Depois de habilitar o vinculador mono para nossos binários .NETCoreApp, ele identificou vários locais onde temos código morto.
Estes se enquadram em 3 categorias.
Os relatórios foram gerados habilitando o ILLink (mono linker build da codegen team /cc @erozenfeld) e diferenciando a saída.
As diferenças são de uma compilação do Windows e restritas apenas a assemblies que fazem parte do NETCore.App.
Quando o vinculador estiver habilitado, ele produzirá uma pasta "PreTrim" na pasta de objetos de um assembly, por exemplo <corefx>\bin\obj\AnyOS.AnyCPU.Debug\Microsoft.CSharp\netstandard\PreTrim\Microsoft.CSharp.dll
.
Para habilitar uma cópia central dos assemblies aparados, você pode definir a propriedade BinPlaceILLinkTrimAssembly=true
, Isso copiará todos os assemblies pré-aparados e aparados para uma pasta em bin: <corefx>\bin\ILLinkTrimAssembly\netcoreapp-Windows_NT-Debug-x64
.
Para gerar os relatórios vinculados neste problema, você deve ter uma cópia da ferramenta AsmDiff.exe. Esta ferramenta ainda não está disponível para o núcleo, consulte (https://github.com/dotnet/buildtools/issues/1420). Se você tiver a versão desktop da ferramenta, poderá ativar os relatórios definindo AsmDiffCmd=<pathToAsmDiff.exe>
O vinculador pode ser habilitado para qualquer projeto (não apenas aqueles em NETCore.App) compilando com ILLinkTrimAssembly=true
após https://github.com/dotnet/corefx/pull/17825 ser mesclado.
Escolha uma biblioteca e anote que você vai trabalhar nela.
Abra o relatório html acima para a biblioteca. Procure por linhas em vermelho. Estes estão teoricamente mortos. Procure o código na pasta src
deste repositório e exclua-o. Continue com o relatório para a biblioteca e exclua todo o código morto assim.
MAS há alguns casos especiais:
src\<library>\src\Resources\strings.resx
. A compilação irá regenerar o SR depois que você fizer isso. Ignore qualquer outra coisa na "classe SR" - existem vários métodos que podem aparecer como mortos.#if uap
ou ter uma condição no arquivo .csproj como, por exemplo '$(TargetGroup)' == 'uap'"
Depois de remover o código morto de uma biblioteca, certifique-se de que ele seja compilado (execute “msbuild” na pasta “src” da biblioteca). Se isso não acontecer, vá para trás. Se ele compilar, em seguida, verifique os testes compilados e aprovados (execute “msbuild /t:buildandtest” na pasta “tests”). Novamente, se não o fizerem, refaça seus passos.
Se tudo estiver bem, você pode colocar um PR para a biblioteca. Quando o PR entrar, podemos verificá-lo aqui.
Aqui está uma tabela listando o diff em bytes, em ordem decrescente. XML já tem um problema
| | | |
|------------------------------------------------- ------|---------------|------------|
| Biblioteca | Bytes removidos | % de redução |
| TOTAL | 1221632 | 7,62% |
| Microsoft.XmlSerializer.Generator.dll | 392704 | 46,29% |
| System.Private.Xml.dll - dotnet/runtime#20506 | 93696 | 2,54% |
| System.Net.Http.dll | 42496 | 12,89% |
| System.Private.DataContractSerialization.dll | 39936 | 4,40% |
| System.ComponentModel.TypeConverter.dll | 36352 | 10,71% |
| System.Data.Common.dll | 36352 | 2,97% |
| System.IO.FileSystem.dll | 32768 | 26,56% |
| System.Net.HttpListener.dll | 32256 | 9,39% |
| System.Runtime.Extensions.dll | 31744 | 12,20% |
| System.Net.Mail.dll | 28672 | 10,81% |
| System.Net.Security.dll | 24064 | 9,73% |
| System.Security.Cryptography.X509Certificates.dll | 23552 | 15,13% |
| System.Net.WebSockets.Client.dll | 22016 | 27,22% |
| System.Net.Primitives.dll | 20992 | 21,58% |
| System.Net.Requests.dll | 20480 | 11,56% |
| System.IO.FileSystem.DriveInfo.dll | 18944 | 47,44% |
| System.Transactions.Local.dll | 17920 | 9,70% |
| System.Net.NetworkInformation.dll | 17408 | 15,45% |
| System.Net.NameResolution.dll | 15360 | 28,30% |
| System.Net.WebHeaderCollection.dll | 14336 | 34,57% |
| System.Net.Sockets.dll | 12288 | 5,35% |
| System.IO.FileSystem.Watcher.dll | 11776 | 28,40% |
| System.IO.Compression.dll | 11264 | 7,83% |
| System.Security.Cryptography.Algorithms.dll | 10240 | 7,04% |
| System.Threading.dll | 10240 | 15,15% |
| System.IO.Pipes.dll | 9728 | 14,18% |
| System.Diagnostics.Process.dll | 9216 | 7,83% |
| System.Security.Cryptography.Encoding.dll | 9216 | 22,22% |
| System.IO.MemoryMappedFiles.dll | 8704 | 19,77% |
| System.Security.Cryptography.Csp.dll | 8704 | 9,83% |
| System.Security.AccessControl.dll | 8192 | 7,69% |
| System.Security.Cryptography.Cng.dll | 7680 | 7,04% |
| System.Collections.dll | 7168 | 5,88% |
| System.Net.Ping.dll | 7168 | 15,05% |
| System.Console.dll | 6656 | 7,51% |
| System.Linq.Expressions.dll | 6656 | 1,03% |
| System.Security.Principal.Windows.dll | 6656 | 9,29% |
| Microsoft.Win32.Registry.dll | 6144 | 12,77% |
| System.Private.Uri.dll | 6144 | 5,41% |
| System.Net.WebClient.dll | 5632 | 8,27% |
| System.Private.Xml.Linq.dll | 5632 | 3,37% |
| Microsoft.CSharp.dll | 4096 | 0,78% |
| System.ComponentModel.Primitives.dll | 4096 | 16,00% |
| System.Reflection.Metadata.dll | 4096 | 0,81% |
| System.Threading.Tasks.Dataflow.dll | 4096 | 1,86% |
| System.Diagnostics.StackTrace.dll | 3584 | 20,59% |
| System.IO.FileSystem.AccessControl.dll | 3584 | 12,96% |
| System.Linq.Parallel.dll | 3584 | 1,41% |
| System.Text.RegularExpressions.dll | 3584 | 2,59% |
| System.IO.IsolatedStorage.dll | 3072 | 8,00% |
| System.Threading.Overlapped.dll | 3072 | 28,57% |
| System.Security.Claims.dll | 2560 | 5,32% |
| Microsoft.VisualBasic.dll | 2048 | 1,05% |
| System.Collections.Concurrent.dll | 2048 | 2,03% |
| System.Collections.NonGeneric.dll | 2048 | 4,44% |
| System.Diagnostics.TraceSource.dll | 2048 | 4,08% |
| System.Diagnostics.Tracing.dll | 2048 | 11,11% |
| System.Runtime.InteropServices.RuntimeInformation.dll | 2048 | 16,00% |
| System.Threading.Tasks.Parallel.dll | 2048 | 4,17% |
| Microsoft.Win32.Primitives.dll | 1536 | 17,65% |
| System.Collections.Specialized.dll | 1536 | 3,66% |
| System.ComponentModel.Annotations.dll | 1536 | 1,94% |
| System.Diagnostics.FileVersionInfo.dll | 1536 | 10,71% |
| System.Reflection.DispatchProxy.dll | 1536 | 5,36% |
| System.Resources.Writer.dll | 1536 | 7,69% |
| System.Runtime.Serialization.Formatters.dll | 1536 | 1,10% |
| System.Threading.Thread.dll | 1536 | 6,82% |
| System.ComponentModel.EventBasedAsync.dll | 1024 | 5,71% |
| System.Drawing.Primitives.dll | 1024 | 2,30% |
| System.IO.Compression.ZipFile.dll | 1024 | 6,90% |
| System.Linq.dll | 1024 | 0,69% |
| System.Linq.Queryable.dll | 1024 | 1,60% |
| System.Net.ServicePoint.dll | 1024 | 6,25% |
| System.ObjectModel.dll | 1024 | 2,63% |
| System.Runtime.InteropServices.dll | 1024 | 4,17% |
| System.Runtime.Numerics.dll | 1024 | 1,41% |
| System.Runtime.Serialization.Primitives.dll | 1024 | 7,69% |
| System.Security.Cryptography.Primitives.dll | 1024 | 2,41% |
| System.AppContext.dll | 512 | 8,33% |
| System.Collections.Imutable.dll | 512 | 0,26% |
| System.Diagnostics.DiagnosticSource.dll | 512 | 1,69% |
| System.Diagnostics.TextWriterTraceListener.dll | 512 | 4,35% |
| System.Globalization.Extensions.dll | 512 | 7,14% |
| System.Net.WebProxy.dll | 512 | 4,55% |
| System.Net.WebSockets.dll | 512 | 2,50% |
| System.Numerics.Vectors.dll | 512 | 0,32% |
| System.Reflection.Primitives.dll | 512 | 7,14% |
| System.Reflection.TypeExtensions.dll | 512 | 3,57% |
| System.Runtime.dll | 512 | 1,43% |
| System.Security.Cryptography.OpenSsl.dll | 512 | 6,25% |
| System.Xml.XmlSerializer.dll | 512 | 5,56% |
| System.Xml.XPath.XDocument.dll | 512 | 6,67% |
Esta é uma questão muito fácil para um novo colaborador pegar. Basta abrir um dos arquivos htm e excluir as fontes marcadas em vermelho.
Uma coisa óbvia que salta à vista é que a classe SR
gerada quase sempre tem vários membros que não são usados, e aparece em todos (ou pelo menos na maioria) dos nossos assemblies. Isso poderia nos dar algumas melhorias globais fáceis se descobrirmos como consertar isso.
Também vejo que os campos private const
aparecem em alguns casos. Eles são usados pela implementação dessa classe, mas obviamente não são visíveis ou usados fora dela. Provavelmente não vale a pena tentar "consertar" esses problemas.
Se a implementação de classe que usa os campos private const
for acessível por meio de um ponto de entrada visível fora do assembly, os campos não serão removidos.
Presumivelmente, se qualquer biblioteca tiver internos visíveis para (por exemplo, para testes de unidade), veríamos falsos positivos, certo?
Se o assembly tiver InternalsVisibleTo, todos os internos também serão considerados raízes.
OK. Eu acho que existem alguns casos em que os testes usam reflexão para obter internos. Mas eles serão descobertos rapidamente quando executarmos os testes.
@mellinoe
Uma coisa óbvia que salta à vista é que a classe SR gerada quase sempre tem vários membros que não são usados, e aparece em todos (ou pelo menos na maioria) dos nossos assemblies. Isso poderia nos dar algumas melhorias globais fáceis se descobrirmos como consertar isso.
Eu já fiz uma passagem em fevereiro excluindo centenas de strings mortas. Vão sobrar muito poucos. Eu acho que esses são casos em que o método que faz referência à string é cortado. Nesse caso, é claro, o resx precisa ser aparado.
@danmosemsft estou me referindo a esse bloco de membros que sempre é emitido no arquivo gerado:
C#
internal static string Format(string resourceFormat, object p1);
internal static string Format(string resourceFormat, object p1, object p2);
internal static string Format(string resourceFormat, object p1, object p2, object p3);
internal static string Format(string resourceFormat, params object[] args);
Muitas vezes (pelo menos do meu breve skimming 😄), apenas o primeiro é usado, e ocasionalmente o segundo. Em alguns dos que cliquei, esse foi o maior pedaço do diff.
OK. Acho que existem alguns casos em que os testes usam reflexão para obter internos. Mas eles serão descobertos rapidamente quando executarmos os testes.
Eu já cobri a maioria deles: https://github.com/dotnet/corefx/pull/17825/commits/2277db969616a5ad4fd9f4a8118a3b4030ff62d3
Já estamos limpos internamente e externamente.
O relatório do Dataflow diz que os tipos no namespace System.Threading.Tasks.Dataflow.Internal.Threading
estão inativos. Isso não é verdade, eles estão mortos na configuração padrão, mas existem outras configurações que os usam. Veja https://github.com/dotnet/corefx/pull/17912 para minha mudança proposta.
É provável que existam outros tipos assim? Há algo que deve ser feito sobre eles?
Este relatório é apenas para NETCoreApp no Windows. É possível que outra configuração ainda os use e isso seria capturado pela compilação -allConfigurations se alguém tentasse removê-los completamente. Para casos como esse você pode melhorar a configuração do NETCoreApp se-def'ing/splitting a fonte como mencionei no item 2 acima. Isso se parece com o que você está fazendo em dotnet/corefx#17912
Se você estiver interessado em identificar o diff para outras configurações em que não estamos executando o vinculador, você pode ativá-lo usando os detalhes listados na seção de plano de fundo. Isso será mais fácil quando o PR for mesclado, então você pode apenas esperar.
@erozenfeld Não queremos remover nenhum consts simplesmente porque eles são embutidos pelo compilador, porque isso perde a clareza do código. Mas alguns dos consts realmente estão mortos, e aqueles que provavelmente queremos remover.
Por exemplo, em http://tempcoverage.blob.core.windows.net/report2/System.ComponentModel.TypeConverter.diff.html todo o VSStandardCommands
está marcado como morto. Alguns são visíveis (via public class StandardCommands
), e apenas embutidos. Mas alguns estão realmente mortos, por exemplo cmdidThisWindow
Seria bom poder ver quais estão na última categoria.
Certo, não podemos remover consts embutidos pelo compilador da fonte. O ILLink opera em msil e não pode distinguir uma constante verdadeiramente morta de uma constante que se tornou morta, pois foi incorporada em todos os lugares.
Ele também sinaliza como morto qualquer construtor padrão privado, que não queremos remover geralmente, pois eles estão lá para evitar qualquer construção acidental sem parâmetros. Presumo que seja inevitável, pela mesma razão. Eles não têm nenhum propósito após o tempo de compilação, tanto quanto posso pensar.
Marcando como 'fácil' por discussão acima. Eu pretendo apontar alguns contribuidores de primeira viagem para esta questão.
Se alguém escolher um arquivo, por favor, diga sobre esta questão, para evitar a duplicação de trabalho.
@danmosemsft você deseja adicionar aqui também o seu tutorial enviado aos fornecedores? Talvez adicioná-lo ao post principal seja melhor, com todas as exceções / coisas a evitar ...
@karelz feito
Eu dei uma olhada superficial nos diffs. Há muito barulho neles - foi difícil para mim encontrar algo fácil e acionável. Podemos filtrar o ruído dos diffs (consts, construtores privados sem parâmetros, ...) para que ele tenha apenas os itens acionáveis fáceis?
Você pode abrir os CSVs e aplicar o filtro que desejar.
Seria bom mexer no CSV mestre para ver qual código em /src/common está realmente morto.
@erozenfeld Posso estar perdendo alguma coisa aqui. diz em http://tempcoverage.blob.core.windows.net/report2/System.Reflection.DispatchProxy.diff.html que EventAccessorInfo.InterfaceRaiseMethod
está morto, mas está definido no construtor, que em si não é morto.
https://github.com/dotnet/corefx/blob/master/src/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs#L919
removendo up-for-grabs e fácil, acho que não está pronto (sinta-se à vontade para mudar se achar que estou errado)
Não vejo como não está pronto, as instruções são bem claras... acho que é uma espécie de boa introdução. De qualquer forma, o pessoal do vendedor vai começar com isso. Já fiz alguns e marquei muitos que já estão limpos.
Outra coisa que marca incorretamente como morta: strings que são carregadas em tempo de execução via SR.GetResourceString("..."). Infelizmente ainda existem alguns, por exemplo return SR.GetResourceString("PropertyCategory" + value, null);
Isso tem que ser pego na revisão de código.
Ao deslizar o fio, parecia que há preocupação de ruído nos resultados. Mais uma vez, não me sinto fortemente - sinta-se à vontade para inverter ;-)
Está bem :)
Em System.Transactions.Local, existe um código que não é acessado atualmente, mas uma parte dele eventualmente será acessada assim que fizermos o trabalho de suporte a transações distribuídas. Estou reticente em simplesmente remover este código.
Eu já removi alguns dos códigos mortos identificados aqui no Microsoft.CSharp e alguns mais (a análise não detectou que, por exemplo ExpressionBinder.BindPtrToArray
está morto conforme dotnet/corefx#17948) e o resto está no meu pontos turísticos como parte de minha refatoração contínua dessa montagem.
@danmosemsft Em relação a EventAccessorInfo.InterfaceRaiseMethod: está dizendo que get_InterfaceRaiseMethod está morto e como não há set_InterfaceRaiseMethod, a propriedade está morta. O código no construtor define o campo de apoio gerado pelo compilador
@jimcarley isso faz sentido, verifiquei S.Transactions.Local acima como feito. Eu acho que se houver probabilidades e fins que realmente estão mortos, você pode querer removê-los em algum momento.
@JonHanna se você acredita que o ILLink deveria ter sido capaz de detectar que o código estava morto, eu deixaria @erozenfeld saber.
@danmosemsft Eu ficaria extremamente impressionado se pudesse, apenas quis dizer que os bits mencionados aqui já são um subconjunto de remoções que pretendo fazer, para que o item da lista seja coberto.
@danmosemsft @JonHanna ILLink não tenta encontrar código inacessível dentro de métodos. Se um método é alcançável, ele assume que todas as instruções dentro do método são alcançáveis.
Para o benefício de outros: outro caso surgiu offline, onde um campo int estava sendo atribuído por um construtor que foi sinalizado como morto. No entanto, esse campo estava sendo lido em outro lugar e, uma vez que o construtor foi removido, o compilador avisou que o campo nunca estava sendo definido. O leitor estava lendo o valor padrão. Nesse caso, o campo precisa ser inicializado explicitamente ao remover o setter morto.
@erozenfeld este código parece morto: https://github.com/dotnet/corefx/compare/master...danmosemsft :dead.xml?expand=1
mas não foi marcado como morto. Está no IL (campos em struct). o ILLink evita especificamente a remoção de campos de structs no caso, por exemplo, de serem usados em interoperabilidade?
Nesse caso específico, os campos são inicializados no construtor estático implícito para que pareçam ser usados no IL. Para detectá-los como código morto, o ILLink teria que:
Ah - claro.
Eu notei isso como eles estavam em https://github.com/dotnet/corefx/pull/18395/files .. outros achados existem várias constantes que estão mortas (o que é claro que o ILLink não pode dizer, pois mostra todas as constantes como morto) e algumas coisas como inicializações que apenas um humano poderia dizer não têm efeitos colaterais. Nada que o ILLink deveria ter encontrado.
@huanwu a ferramenta SGEN tem 400 KB de código morto.
Tudo isso é razoável para remover ou é um trabalho em andamento, você vai usar este código?
Consulte http://tempcoverage.blob.core.windows.net/report2/Microsoft.XmlSerializer.Generator.diff.html
Em System.Linq e System.Linq.Queryable, o único código sinalizado são constantes ou métodos usados em arquivos comuns onde outros métodos da mesma classe são usados nesse projeto, portanto, eles não exigem mais trabalho.
Acabei de identificar algum código morto usando uma ferramenta que escrevi - veja https://github.com/dotnet/corefx/pull/18395. Estou usando seu código como dados de entrada para validar meu código.
No passado, fiz uma exclusão massiva em Roslyn (18,5 kloc) que foi boa em identificar código que não deveria estar morto - veja os comentários em https://github.com/dotnet/roslyn/pull/17630. ou seja, usado apenas no depurador, devido a uma regressão, implementação incompleta de testes, deve ser compilado condicionalmente.
@JonHanna obrigado! Postagem principal atualizada com suas informações.
dotnet/corefx#18414 removeu a maior parte do código morto relevante em S.Linq.Expressions. Além de constantes, etc., ainda há alguns que podem se tornar relevantes se recursos atualmente não suportados forem portados (incluindo um com um problema solicitando exatamente isso), então provavelmente seria melhor não fazer mais agora.
Parece bom, está marcado na lista. A maior parte do resto só precisa de limpeza mínima. SGEN é a principal exceção ( @huanwu , veja a pergunta acima)
@zhenlan pergunta acima sobre Microsoft.XmlSerializer.Generator.dll - ele tem 400 KB de código morto (!) ... esse código está temporariamente morto ou você terminou esse trabalho e alguém pode excluí-lo? Posso pedir aos fornecedores que façam grande parte do trabalho, muito provavelmente. Apenas deixe-me saber.
@danmosemsft Estes não são códigos mortos. A maior parte do código é compartilhada com System.Private.Xml. Se você verificar o arquivo de projeto https://github.com/dotnet/corefx/blob/master/src/Microsoft.XmlSerializer.Generator/src/Microsoft.XmlSerializer.Generator.csproj , poderá encontrar a maioria dos arquivos de código localizados em $(SourceDir)SystemXmlSerialization
@huanwu , mesmo que não possamos excluir o código do repositório (porque é usado por outros), se o código não for usado nesse assembly, idealmente não o compilaríamos no assembly.
ReflectionAwareCodeGen
XmlSerializationWriterCodeGen
XmlSerializationCodeGen
todos parecem classes mortas. Idealmente, eles podem ser movidos para seus próprios arquivos de código para que sejam compilados apenas onde forem necessários.
ReflectionAwareCodeGen está realmente dentro de #if XMLSERIALIZERGENERATOR
então talvez esteja totalmente morto, se estiver morto nesta montagem.
Também a grande maioria das strings não são utilizadas neste binário.
Se este código estiver morto neste assembly, isso economizaria 400 KB, então vale a pena fazer.
Oi @danmosemsft , posso não entender completamente como o código morto é determinado, mas algo parece errado.
Por exemplo, o arquivo diff Microsoft.XmlSerializer.Generator marcou ReflectionAwareCodeGen
como vermelho, que interpretei como código morto do ponto de vista das ferramentas. No entanto, é claramente usado em Microsoft.XmlSerializer.Generator
aqui . Você pode por favor ajudar a esclarecer?
OK, vejo que a ferramenta realmente marcou toda a cadeia de *CodeGen como código morto. Por favor, ignore minha pergunta anterior e deixe-me explicar. O projeto Microsoft.XmlSerializer.Generator acaba de ter seu check-in inicial. Algum código está morto apenas temporariamente. Eles serão usados quando tivermos mais trabalho feito. Talvez possamos executar a ferramenta novamente quando nos aproximarmos da conclusão do código?
@zhenlan isso é exatamente o que eu assumi. O código completo foi na semana passada :) Talvez este seja um DCR. Há muito mais a fazer?
@danmosemsft sim, o que você assumiu está certo :)
Microsoft.XmlSerializer.Generator (Sgen) é uma ferramenta CLI que não é fornecida como parte do .NET Core. Para a parte do código que o Sgen compartilha com o S.Private.Xml, não acho que haja grandes lacunas, exceto correções de bugs, e estamos visando 5/10 para ZBB. Para a parte do código que está exclusivamente sob Sgen, há mais trabalho de recursos a fazer, mas devemos ter mais margem de manobra devido a diferentes veículos de lançamento. Espero que isso esclareça.
@erozenfeld muito do código deletável restante provavelmente são partes de arquivos compartilhados comuns que ninguém usa. Sem analisar cada um manualmente ou fazer alguma junção cansativa do CSV, não é possível limpar esse código. Existe alguma maneira direta de executar o ILLink em todo o conjunto de assemblies, agregando arquivos que são usados em vários assemblies, para que possamos obter um desses relatórios HTML mostrando o que está morto no código comum?
Também gostaríamos de mesclar os assemblies de teste à medida que alguns testes compilam no código do produto (por exemplo, PathInternal.cs). Também os Unix, porque é claro que eles usam código comum.
@danmosemsft Minha ferramenta pode fazer isso. ou seja, execute-o em toda a base de código, excluindo automaticamente as linhas, incluindo falsos positivos, compilar e reverter repetidamente e depois testar. Por fim, envie-o para minha conta do Github e crie uma solicitação de pull. Provavelmente um pouco de trabalho envolvido, portanto, não deve ser tentado, a menos que seja usado. Consulte https://github.com/dotnet/corefx/pull/18395 para obter resultados em um subconjunto
@danmosemsft
Existe alguma maneira direta de executar o ILLink em todo o conjunto de assemblies, agregando arquivos que são usados em vários assemblies, para que possamos obter um desses relatórios HTML mostrando o que está morto no código comum?
Não, não há uma maneira direta de fazer isso. O ILLink opera em assemblies, não em arquivos de origem usados para gerá-los.
@erozenfeld Eu não acho que foi isso que eu quis dizer. Eu não espero que o ILLink opere sobre fontes. Mas agora quaisquer fontes compartilhadas que mostrem como mortas no binário X não podem ser facilmente excluídas porque podem ser usadas pelo binário Y ou Z. Se teoricamente todas as nossas bibliotecas fossem compiladas em uma grande dll como ILMerge ou algo parecido, com os pdb's corrigidos adequadamente, poderíamos excluir qualquer código comum que aparecesse como morto. Minha pergunta era se o ILMerge poderia mesclar os assemblies em um e, em seguida, executar a análise de fechamento sobre esse.
@danmosemsft Não, o ILLink não pode mesclar assemblies em um como o ILMerge faz. Não sei como o ILMerge lida com nomes conflitantes em assemblies. Se você tem classe NC na montagem A e classe NC na montagem B, em geral você precisa manter ambas (e renomear uma ou ambas) ao mesclar, a menos que você possa provar que elas são idênticas em um sentido profundo.
Peguei vocês. Bem, talvez uma abordagem surja no futuro. Não é grande coisa.
Também me ocorre que isso poderia ser feito com algum processamento dos CSVs...
Todos esses dados provavelmente estão desatualizados agora. Provavelmente devemos executar novamente os relatórios se você quiser trabalhar mais nisso. Você pode encontrar instruções sobre como fazer isso na seção de plano de fundo acima.
Em System.Collections.Immutable e System.Collections.NonGeneric o único código sinalizado são constantes ou métodos usados em arquivos comuns onde outros métodos da mesma classe são usados nesse projeto, portanto, eles não requerem trabalho adicional.
Obrigado @YoupHulsebos , postagem principal atualizada.
Em System.IO.FileSystem.Watcher e System.Security.Cryptography.Primitives, o único código sinalizado são constantes ou métodos usados em arquivos comuns onde outros métodos da mesma classe são usados nesse projeto, portanto, eles não exigem trabalho adicional.
@yaelkeemink agradece as caixas de seleção atualizadas.
Olá,
Como contribuidor pela primeira vez, gostaria de abordar a biblioteca System.Net.Mail
se estiver tudo bem!
Claro, obrigado! Vá em frente. Se você encontrar algum bloqueio na estrada, envie-nos um ping aqui no arquivo de novos problemas (e me marque para roteamento).
Criei um pull request para System.Net.Mail e assinei o contrato.
Obrigado @Ermiar! Edição principal atualizada com link para o PR.
Primeira vez aqui - vou levar o seguinte:
System.IO.FileSystem.DriveInfo
System.IO.FileSystem.Watcher
🗄
Incrível, bem-vindo a bordo @garfbradaz!
Deixe-nos saber quando você enviar PR, atualizaremos a postagem principal ( @ViktorHofer , você pode ajudar hoje?)
Certo.
@garfbradaz , por favor, certifique-se depois de remover possíveis códigos mortos para executar todos os testes em ambos os projetos para Desktop e Core.
Você pode fazer isso executando primeiro:
build.cmd
e build.cmd -framework:netfx
da raiz corefx e depois disso executando os projetos de teste individuais como este:
msbuild /t:RebuildAndTest
e msbuild /t:RebuildAndTest /p:TargetGroup=netfx
.
Não hesite em contactar-me se precisar de ajuda. Obrigado pela ajuda!
Obrigado @ViktorHofer / @karelz - lentamente arando
Houve um falso positivo: dotnet/corefx#19826 é implementado com código removido pelo commit 81506698 neste problema. Embora o método esteja 'morto' por enquanto, é essencial estar lá para trazer de volta DbProviderFactories do .NET completo. Portanto, será ressuscitado se o PR do dotnet/corefx#19826 for mesclado.
Contribuinte pela primeira vez, gostaria de dar uma olhada no System.Linq.Expressions.
Grande @mccbraxton! Se precisar de ajuda, me chame.
@mccbraxton Parece que o System.Linq.Expressions já foi limpo. Você quer pegar outro pedaço?
@ViktorHofer Você está certo - deve ter lido errado. Que tal System.Security.Cryptography.Algorithms?
Parece bom! Adicionei você na tabela. Certifique-se de não excluir o código usado por qualquer destino. Obrigado pela ajuda!
dotnet/corefx#18162 cuidou do que restou no relatório para Microsoft.CSharp que estava realmente morto.
Olá novo contrib aqui, posso pegar System.ComponentModel.*
Obrigado @norek vá em frente.
Oi abaixo resumo de System.ComponentModel.
Eu estava revisando a lista e encontrei código morto em System.Diagnostics - StackTraceSymbols
está marcado como morto, mas o nome desta classe no exlorer de solução é StackTraceSymbols.CoreCLR.cs
. Encontrei em questões fechadas: dotnet/corefx#19368
e está morto ou não?
@ mikem8361 você possui SDStackTraceSymbols?
Eu o possuo e não é código morto. A reflexão é usada por System.Private.CoreLib para carregar esse assembly e fazer referência a essa classe.
Assim, o coreclr pode imprimir as informações do número de origem/linha em rastreamentos de pilha de exceção não tratadas.
@ mikem8361 obrigado pela informação. Parece que está protegido por illinktrim.xml:
<linker>
<assembly fullname="System.Diagnostics.StackTrace">
<!-- used by System.Private.CoreLib StackTrace code to load portable pdbs for the runtime diagnostic stack trace to get source/line info -->
<type fullname="System.Diagnostics.StackTraceSymbols" required="true" />
</assembly>
</linker>
@ericstj @erozenfeld podemos corrigir o CSV que o ILLink emite para que ele não mostre o código como morto quando o forçamos a reter o código?
Os CSVs que temos são apenas o diff após o corte, então eles não têm coisas explicitamente enraizadas. Os dados vinculados nesta edição não estão ativos. É de uma corrida única quando abri o problema. Suspeito que se você olhar para as informações ao vivo, elas não serão mais exibidas como um diferencial.
Olá, sou um colaborador de primeira viagem. Eu gostaria de tentar System.Net.Http e System.Net.HttpListener.
@soapdogg vá em frente..
@AlexGhiondea como mencionado
Olá. Posso levar Microsoft.VisulaBasic
?
@satano com certeza, vá em frente. Parece que há apenas alguns bits a serem removidos (todos os campos const e ctor sem parâmetros precisam permanecer, de acordo com minha descrição acima). Talvez você possa fazer CSharp também?
Existem também algumas bibliotecas que são novas desde que a análise acima foi feita...
OK, vou fazer CSharp e VisualBasic.
PR para Microsoft.VisualBasic
está pronto.
Em relação Microsoft.CSharp
- tudo vermelho no arquivo diff já foi removido. Então é só marcar na questão.
Vamos deixar esse problema para os novatos ou posso continuar?
@satano você pode encontrar a mudança que removeu o diff vermelho?
Não há problema em continuar, não o salvamos apenas para iniciantes.
Não sei se foi tudo, mas algo foi removido aqui: https://github.com/dotnet/corefx/commit/3eb339702e2fcdf924b50c2e32d7e9e02395e52f
@JonHanna até disse isso aqui https://github.com/dotnet/corefx/issues/17905#issuecomment -291924301
Vou continuar de cima. Então, em seguida, pegarei System.IO.Compression
, System.IO.FileSystem.AccessControl
e System.IO.FileSystem
.
@satano vá em frente - não há necessidade de pedir permissão, se alguém estivesse olhando, eles postariam aqui.
Um "metaproblema" interessante para alguém é se existe uma maneira de usar a mesma ferramenta de linker para encontrar código comum que ninguém está usando. Se uma classe extraída de srccommon for usada apenas parcialmente por uma biblioteca, ela aparecerá morta nesta análise, mas não poderá ser removida porque outra biblioteca pode estar puxando-a e usando-a. O que eu estava pensando é se há uma maneira de mesclar os assemblies e executar o vinculador neles. @erozenfeld poderíamos usar o linker para mesclar e encontrar o código morto no resultado?
ILLink atualmente não pode mesclar assemblies. Isso é algo que estamos considerando adicionar no futuro. Existem outras ferramentas que podem fazer isso: https://github.com/Microsoft/ILMerge e https://github.com/gluck/il-repack embora eu não tenha certeza se eles lidam com esse cenário da maneira que queremos. Eles podem ou não perceber que os tipos/métodos/campos com nomes conflitantes representam entidades idênticas.
Estou continuando com System.Net.Http
, System.Net.HttpListener
e System.Net.NetworkInformation
.
Vou levar System.Net.*
inteiros um por um.
Continuando com o resto: System.Private.*
, System.Runtime.*
e System.Security.*
.
@karelz
… você pode encontrar a mudança que removeu o diferencial vermelho?
dotnet/corefx#18162
Bem... Provavelmente é hora de rever esta questão e talvez fechá-la?
O código morto deste projeto foi removido e mesclado:
Olhei também para o resto dos projetos e não há nada a fazer neles:
Portanto, o único projeto que resta é Microsoft.XmlSerializer.Generator
. Ele tem a maior parte do código morto, mas como mencionado em alguns comentários aqui, há um trabalho em andamento lá - como eu entendi.
Esta questão está concluída? Se não, eu poderia levar System.Console
?
@Iroca88 com certeza. Observe que os dumps vinculados acima podem estar um pouco desatualizados agora.
Obrigado @danmosemsft , alguma sugestão para obter novos dumps? Caso contrário, começarei com os dumps fornecidos acima.
BTW: é @lroca88 , coloquei a primeira letra em maiúscula (L) para evitar esse tipo de confusão :)
@Lroca88 o post principal explica como acessar os conjuntos aparados e pré-aparados. Para diferenciá-los, usamos uma ferramenta que não parece ser pública. Você pode usar um descompilador como o Ilspy On e fazer uma comparação de texto nos resultados. Isso provavelmente funcionaria.
Vou executar um diff recente e compartilhá-lo. Fique ligado.
Aqui está o mais recente:
Legal, eu estava lutando com a lista antiga! 🔨
@Lroca88 certifique-se de ler as principais dicas do post sobre como fazer esse passe, economize alguma confusão ..
Ops. Perdi dois bits recém-órfãos em Microsoft.CSharp em dotnet/corefx#26491. Eu vou conseguir isso em breve.
Da nova lista, System.Runtime, System.Web.HttpUtility, System.Linq e System.Linq.Queryable têm apenas constantes que não estão realmente mortas e algum código compartilhado. System.Linq.Expressions tem esses e alguns itens usados em visualizações de depuração e recursos usados com constantes de compilador específicas e, portanto, devem ser removidos, para que esses cinco possam ser verificados.
Juntei as bibliotecas revisadas por @JonHanna e aquelas que revisei em uma lista. Continuarei atualizando a lista conforme meu tempo permitir, se alguém quiser adicionar itens a esta lista, sinta-se à vontade para me enviar um ping!
Biblioteca | Revisado por | Status
-- | -- | --
Microsoft.CSharp (csv) | @JonHanna | dotnet/corefx#27104
Microsoft.VisualBasic (csv) | @Lroca88 | Nada para remover
Microsoft.Win32.Primitives (csv) | @Lroca88 | Nada para remover
Microsoft.Win32.Registry (csv) | @Lroca88 | Nada para remover
System.Collections.Concurrent (csv) | @Lroca88 | Nada para remover
System.Collections (csv) | @Lroca88 | Nada para remover
System.Runtime (csv) | @JonHanna | Nada para remover
System.Web.HttpUtility (csv) | @JonHanna | Nada para remover
System.Linq (csv) | @JonHanna | Nada para remover
System.Linq.Queryable (csv) | @JonHanna | Nada para remover
Obrigado @Lroca88 !
Não me surpreende que não haja muito código morto. No entanto, conforme observado no início, esse processo não pode determinar se o código em srccommon está morto (pois pode aparecer morto em um assembly, mas não em outro). Se você estiver interessado em encontrar uma maneira de resolver isso, provavelmente encontrará mais código morto. Por exemplo, talvez seja possível ILMerge os assemblies e depois executar a análise de código morto neles. Ou pode ser mais fácil pós-processar os CSVs para encontrar o código comum que está morto em todos eles. @ericstj você teve ideias?
É um prazer ajudar @danmosemsft ,
Concordo com você, sobre como melhorar esse processo, das minhas análises o código mais sinalizado não está morto, é consts ou frequentemente usado em outro assembly como você disse anteriormente. Olhar para esses falsos positivos torna a caça um pouco tediosa na minha opinião.
Infelizmente não sei como realizar o ILMerge e a análise de código morto ou pós-processar os CSV's. Estou disposto a aprender/colaborar, se vocês quiserem passar algum tempo me ensinando :)
Onde estamos com isso? O OP está sendo atualizado com as aulas concluídas ou devemos sair deste comentário agora?
@MisinformedDNA -- use https://github.com/dotnet/corefx/issues/17905#issuecomment -365349091 por favor
Não sei como realizar o ILMerge e a análise de código morto ou pós-processar os CSV's. Estou disposto a aprender/colaborar, se vocês quiserem passar algum tempo me ensinando :)
Eu também não sei e teria que pensar/experimentar. No momento, estou totalmente engajado no fechamento da versão 2.1, então isso terá que esperar um pouco 😺
Eu escrevi um script para carregar todos os CSVs e depois filtrei apenas os mais acionáveis. Eu criei 3.942 questões/oportunidades.
Pretendo agora analisar cada uma das questões e tomar as medidas adequadas. Você pode acompanhar meu progresso e deixar comentários, se quiser, em https://github.com/MisinformedDNA/corefx/tree/clean-dead-code
Legal, ansioso para ver este @MisinformedDNA.
Encontrei um problema com a análise de código morto, que mostra um problema ou deficiência com a ferramenta.
O relatório diz que o seguinte pode ser removido:
Friend Enum vbErrors
ObjNotSet = 91
IllegalFor = 92
End Enum
Mas esses Enums são referenciados em outro lugar, então vamos dar uma olhada em uma possível ramificação:
' Enum cannot be accessed, so Function cannot be hit
' But the dead code analysis did not show this as removable!!
Friend Shared Function VbMakeIllegalForException() As System.Exception
Return VbMakeExceptionEx(vbErrors.IllegalFor, GetResourceString(SR.ID92))
End Function
' The chain continues:
' VbMakeIllegalForException() cannot be hit, so first "If" cannot be true, but the rest of the Function *could* still be hit
Public Shared Function ForNextCheckObj(ByVal counter As Object, ByVal loopObj As Object, ByRef counterResult As Object) As Boolean
Dim loopFor As ForLoopControl
If loopObj Is Nothing Then
Throw VbMakeIllegalForException()
End If
If counter Is Nothing Then
Throw New NullReferenceException(GetResourceString(SR.Argument_InvalidNullValue1, "Counter"))
End If
loopFor = CType(loopObj, ForLoopControl)
... cut for brevity
End Function
A lógica me levou ao lugar onde a primeira afirmação "Se" nunca é verdadeira. Mas essa declaração "If" é uma verificação de null
, então teríamos que assumir que alguém passaria null
e que falta um caso para determinar código morto. E se houver um problema (ou mais) com a determinação do código morto, um ou mais dos Enums podem não ser código morto.
tldr: Resumo
Pergunta 1: Por que VbMakeIllegalForException não está marcado para remoção, mesmo que não possa chamar o enum que foi marcado para remoção.
Pergunta 2: Por que os casos de teste na determinação do código morto não passam valores nulos para todos os parâmetros aplicáveis?
@ericstj
Os valores enum provavelmente foram embutidos em msil para que a ferramenta não os veja. O mesmo vale para as constantes. Nada deve ser feito para o código-fonte nesses casos.
Acabei de remover o código morto de System.Data.Common
com build.cmd src\System.Data.Common
completando com sucesso. Até agora, eu tinha a impressão de que só precisava trabalhar na biblioteca em que estava e se a compilação e os testes passassem, então estava pronto. Mas decidi fazer uma compilação completa de qualquer maneira, e estou feliz por ter feito isso, porque recebi uma série de erros.
Aqui estão alguns exemplos de erros:
SystemDataSqlClientSqlCommandBuilder.cs(277,17): erro CS0117: 'ADP' não contém uma definição para 'RemoveStringQuotes' [D:ReposcorefxsrcSystem.Data.SqlClientsrcSystem.Data.SqlClient.csproj]
SystemDataProviderBaseDbConnectionPool.cs(984,33): erro CS0117: 'ADP' não contém uma definição para 'SetCurrentTransaction' [D:ReposcorefxsrcSystem.Data.SqlClientsrcSystem.Data.SqlClient.csproj]
D:ReposcorefxsrcCommonsrcSystemDataProviderBaseDbMetaDataFactory.cs(409,21): erro CS0117: 'ADP' não contém uma definição para 'IsEmptyArray' [D:ReposcorefxsrcSystem.Data.SqlClientsrcSystem.Data.SqlClient.csproj]
SystemDataCommonDbConnectionStringCommon.cs(180,27): erro CS0117: 'ADP' não contém uma definição para 'InvalidConnectionOptionValue' [D:ReposcorefxsrcSystem.Data.SqlClientsrcSystem.Data.SqlClient.csproj]
Os membros ausentes foram todos claramente especificados para serem excluídos em System.Data.Common.diff.html , mas não apenas causam erros em classes filhas, mas também não há arquivos diff para essas classes filhas ou assemblies associados como System.Data.SqlClient
.
O primeiro erro mostrado acima está vinculado a um método público, SqlCommandBuilder.UnquoteIdentifier
. Então eu não vejo como ADP.RemoveStringQuotes
pode ser removido de System.Data.Common
sem uma mudança significativa em outras classes.
Como devo lidar com isso? Alguma idéia de por que eles estão sendo marcados para remoção?
@anipik você pode ajudar o @misinformeddna aqui?
@MisinformedDNA Você sempre terá que construir todo o repositório e executar os testes para todas as bibliotecas porque é possível que uma biblioteca dependa de outra biblioteca diretamente (A usando algum tipo de B) ou indiretamente. (A usando algum tipo de B que usando algum tipo de C)
Não há nada na função ADP.removeStringQuotes
, isemptyarray
, setTransaction
que seja apenas específico para ApaterUtil ou System.Data.Common para que possamos apenas movê-lo para sqlCommandBuilder.cs
como um método estático privado. Eles estão apenas manipulando os argumentos. Se você puder me indicar o ramo com essa falha, posso ajudá-lo com outras falhas.
Todos os métodos que você mencionou acima são internal static methods
que não estão mais sendo usados no assembly em que são definidos.
Os membros internos geralmente não devem ser usados fora da montagem em que são definidos. mas em alguns casos fazemos isso usando o atributo ``Internals Visible To```.
@danmosemsft está movendo essas funções internas para os assemblies onde elas são usadas de maneira ideal?
Aqui está o fork em que estou trabalhando: https://github.com/MisinformedDNA/corefx/tree/clean-dead-code
Como dito anteriormente, a análise de código morto diz que ADP.RemoveStringQuotes
para System.Data.Common
pode ser removido. No entanto, como ADP
foi marcado internal
, o analisador pode ter assumido que não estava sendo usado fora do assembly. Não tenho certeza se ele procura o atributo InternalsVisibleTo
, mas não importaria se o fizesse, porque não está sendo usado aqui. Em vez disso, ele está sendo compartilhado por meio de arquivos vinculados.
<Compile Include="$(CommonPath)\System\Data\Common\AdapterUtil.cs">
<Link>System\Data\Common\AdapterUtil.cs</Link>
</Compile>
Suponho que o analisador esteja vendo as diferentes classes ADP
como tipos separados, em vez de compartilhados, já que eles estão essencialmente sendo duplicados. Portanto, embora a remoção RemoveStringQuotes
seja a coisa correta a fazer para System.Data.Common
, é a coisa incorreta a fazer ao considerar outros projetos aos quais está vinculado.
A menos que o analisador possa determinar que todas essas classes são provenientes do mesmo arquivo, sempre pensaremos que temos código morto em alguns desses arquivos.
A menos que alguém tenha uma sugestão melhor, começarei a adicionar esses métodos de volta ao código.
@MisinformedDNA Falei sobre isso com @danmosemsft offline. Outra abordagem possível é tornar a classe ADP parcial. E então você pode mover essas funções para essas bibliotecas.
Eu arquivei meu primeiro PR, mas 2 das compilações falharam. Ambas as falhas de compilação parecem indicar um tempo limite. Que ação devo tomar para resolver isso?
Vou olhar para o System.Private.Uri
Aqui está um aplicativo(s) que escrevi para baixar todos os arquivos, combiná-los e filtrar todos aqueles que provavelmente são falsos positivos. Espero que seja útil para outras pessoas.
@ericstj faz sentido regenerar os dados para que tenhamos uma imagem mais clara de onde estamos com isso?
Iniciado nos seguintes namespaces:
Eu regenerei os dados. Aqui está como uma nova lista, eu não queria substituir o que estava lá, caso as pessoas achem relevante.
Obrigado Érico!
Verificado o seguinte:
Limpando código morto para:
System.Diagnostics.DiagnosticSource
System.Diagnostics.FileVersionInfo
Sistema.Diagnóstico.Processo
System.Diagnostics.StackTrace
System.Diagnostics.TextWriterTraceListener
Sistema.Diagnóstico.Ferramentas
System.Diagnostics.TraceSource
Sistema.Diagnóstico.Rastreamento
Sistema.Desenho.Primitivos
dotnet/corefx#33095
Eu gostaria de assumir a limpeza do System.IO.Compression como minha primeira contribuição.
No post inicial System.Console
marcado como concluído, mas nos dados regenerados não está marcado. Descobri que apenas algumas strings de SR
são necessárias para serem limpas.
Então, @ericstj , é um tipo de erro ou posso dar uma olhada em outras bibliotecas e corrigir esses pequenos códigos mortos?
Eu criei uma pequena ferramenta que remove algumas coisas inúteis desses dados .
Este é um muito comum. Qualquer coisa com “const”, por favor ignore. Estes são todos os campos embutidos pelo compilador, queremos mantê-los nas fontes. Normalmente ints e strings.
SR
que podem ser removidas. Mas os métodos SR
também não foram removidos, fique atento.Qualquer coisa que seja “string estática” na “classe SR” é especial. Em vez de editar um arquivo C#, você deve localizar a entrada correspondente no arquivo .resx e removê-la. O arquivo .resx de uma biblioteca está em src
srcResourcesstrings.resx. A compilação irá regenerar o SR depois que você fizer isso. Ignore qualquer outra coisa na "classe SR" - existem vários métodos que podem aparecer como mortos.
Ignore qualquer construtor privado sem parâmetros. Temos isso no código para evitar instanciar acidentalmente a classe, queremos mantê-los no código.
Ignore qualquer construtor público sem parâmetros se não houver outros construtores. Estes não são reais.
Você pode encontrar arquivos limpos aqui . Mencione-me depois de atualizar os dados e eu atualizarei isso.
@ericstj é seguro remover internal enum
?
Depois de usar essa ferramenta , verifiquei todos os assemblies. Alguns deles não têm nenhum código morto.
A lista atualizada se parece com a abaixo. Eu altero links para htmlpreview para arquivos limpos do repositório da ferramenta . Espero que ninguém se importe.
Então, @ericstj , é um tipo de erro ou posso dar uma olhada em outras bibliotecas e corrigir esses pequenos códigos mortos?
Ao regenerar os diffs comecei com tudo limpo novamente. O mecanismo para fazer as diferenças de código morto é muito manual. Está usando uma ferramenta interna para os diferenciais de montagem.
@ericstj é seguro remover enum interno?
Depende do que é feito com esse enum interno. Enums geralmente aparecem como mortos no binário, pois o compilador transforma sua referência de origem em literais inteiros. Você provavelmente descobrirá que a fonte ainda pode precisar dela.
Apenas alguns pensamentos sobre como podemos tornar esse código morto esfregando uma experiência melhor que é mais estável no futuro.
Hoje, o mecanismo que usamos para o código morto de trepidação de árvores é o linker que opera nos binários. Então, para mapear isso de volta para a fonte, diferenciamos sua saída com entrada e emitimos os ids. Outra coisa que discuti com o pessoal do linker no passado é emitir mais logs que poderiam mapear mais diretamente de volta às coisas com as quais nossos desenvolvedores se preocupam. Ele também está operando em PDBs, portanto, tecnicamente, possui todo o código-fonte e informações de linha.
@ericstj é tecnicamente possível rodar no corelib? Percebi um método morto no corelib e, sem dúvida, há mais. Certamente precisaria de cuidados devido a todos os retornos de chamada da VM
@danmosemsft Já está rodando em System.Private.Corelib.dll como parte da compilação coreclr. Qual é o método morto que você notou que não está sendo removido?
@ericstj Acho que podemos fechar isso?
Acho que isso está feito por enquanto. Muito obrigado a todos que contribuíram para melhorar nossa base de código!