O código de exemplo abaixo ocorreu naturalmente. De repente, meu código provocou uma FatalExecutionEngineError
exceção muito desagradável . Passei bons 30 minutos tentando isolar e minimizar a amostra culpada. Compile isso usando o Visual Studio 2012 como um aplicativo de console:
class A<T>
{
static A() { }
public A() { string.Format("{0}", string.Empty); }
}
class B
{
static void Main() { new A<object>(); }
}
Deve produzir esse erro no .NET framework 4 e 4.5:
Esse erro é conhecido, qual é a causa e o que posso fazer para atenuá-lo? Meu trabalho atual é não usar string.Empty
, mas estou latindo na árvore errada? Alterar qualquer coisa sobre esse código faz com que funcione como você esperaria - por exemplo, removendo o construtor estático vazio de A
ou alterando o parâmetro de tipo de object
para int
.
Eu tentei esse código no meu laptop e ele não se queixou. No entanto, eu tentei o meu aplicativo principal e ele travou no laptop também. Eu devo ter destruído alguma coisa ao reduzir o problema, vou ver se consigo descobrir o que era isso.
Meu laptop travou com o mesmo código acima, com o framework 4.0, mas o principal travou mesmo com o 4.5. Ambos os sistemas estão usando o VS'12 com as atualizações mais recentes (julho?).
Mais informações :
- Código IL (compilação de depuração / qualquer CPU / 4.0 / VS2010 (não que o IDE importe?)): Http://codepad.org/boZDd98E
- Não visto VS 2010 com 4.0. Não travar com / sem otimizações, CPU de destino diferente, depurador conectado / não conectado, etc. - Tim Medora
- Falha em 2010, se eu usar o AnyCPU, está bem em x86. Falha no Visual Studio 2010 SP1, usando o Target Platform = AnyCPU, mas é bom com o Target Platform = x86. Esta máquina também possui o VS2012RC instalado e, portanto, o 4.5 está fazendo uma substituição no local. Use AnyCPU e TargetPlatform = 3.5 para não travar, por isso parece uma regressão no Framework. - colinsmith
- Não é possível reproduzir em x86, x64 ou AnyCPU no VS2010 com 4.0. - Fuji
- Só acontece para x64, (2012rc, Fx4.5) - Henk Holterman
- VS2012 RC no Win8 RP. Inicialmente Não está vendo este MDA ao direcionar o .NET 4.5. Quando passou para o .NET 4.0, o MDA apareceu. Depois de voltar ao .NET 4.5, o MDA permanece. - Wayne
Respostas:
Esta também não é uma resposta completa, mas tenho algumas idéias.Acredito que encontrei uma explicação tão boa quanto a encontrada sem que alguém da equipe do .NET JIT responda.
ATUALIZAR
Eu olhei um pouco mais fundo e acredito que encontrei a fonte do problema. Parece ser causado por uma combinação de um erro na lógica de inicialização do tipo JIT e por uma alteração no compilador C # que se baseia na suposição de que o JIT funcione conforme o esperado. Acho que o bug do JIT existia no .NET 4.0, mas foi descoberto pela alteração no compilador do .NET 4.5.
Eu não acho que esse
beforefieldinit
seja o único problema aqui. Eu acho que é mais simples que isso.O tipo
System.String
no mscorlib.dll do .NET 4.0 contém um construtor estático:Na versão .NET 4.5 do mscorlib.dll,
String.cctor
(o construtor estático) está conspicuamente ausente:Nas duas versões, o
String
tipo é adornadobeforefieldinit
:Tentei criar um tipo que seria compilado para IL da mesma forma (para que ele tenha campos estáticos, mas nenhum construtor estático
.cctor
), mas não consegui. Todos esses tipos têm um.cctor
método em IL:Meu palpite é que duas coisas mudaram entre o .NET 4.0 e o 4.5:
Primeiro: o EE foi alterado para inicializar automaticamente a
String.Empty
partir de código não gerenciado. Essa alteração foi provavelmente feita no .NET 4.0.Segundo: O compilador foi alterado para não emitir um construtor estático para a string, sabendo que isso
String.Empty
seria atribuído do lado não gerenciado. Essa alteração parece ter sido feita para o .NET 4.5.Parece que o EE não atribui o
String.Empty
suficiente ao longo de alguns caminhos de otimização. A alteração feita no compilador (ou o que foi alterado para fazerString.cctor
desaparecer) esperava que o EE fizesse essa atribuição antes que qualquer código de usuário fosse executado, mas parece que o EE não fez essa atribuição antesString.Empty
é usado em métodos de classes genéricas reificadas do tipo referência.Por fim, acredito que o bug é indicativo de um problema mais profundo na lógica de inicialização do tipo JIT. Parece que a mudança no compilador é um caso especial
System.String
, mas duvido que o JIT tenha feito um caso especial aquiSystem.String
.Original
Primeiro de tudo, WOW O pessoal da BCL ficou muito criativo com algumas otimizações de desempenho. Muitos dos
String
métodos agora são executados usando umStringBuilder
objeto em cache estático do Thread .Eu segui esse lead por um tempo, mas
StringBuilder
não é usado noTrim
caminho do código, então decidi que não poderia ser um problema estático do Thread.Acho que encontrei uma manifestação estranha do mesmo bug.
Este código falha com uma violação de acesso:
No entanto, se você descomente
//new A<int>(out s);
emMain
seguida, o código funciona muito bem. De fato, seA
for reificado com qualquer tipo de referência, o programa falhará, mas seA
for reificado com qualquer tipo de valor, o código não falhará. Além disso, se você comentar oA
construtor estático, o código nunca falha. Depois de pesquisarTrim
eFormat
, fica claro que o problema estáLength
sendo incorporado e que nessas amostras acima oString
tipo não foi inicializado. Em particular, dentro do corpo doA
construtor de,string.Empty
não está atribuído corretamente, embora dentro do corpo deMain
,string.Empty
esteja atribuído corretamente.É incrível para mim que a inicialização do tipo de
String
alguma forma dependa se é ou nãoA
reificada com um tipo de valor. Minha única teoria é que há algum caminho de código JIT otimizado para inicialização de tipo genérica que é compartilhado entre todos os tipos e que esse caminho faz suposições sobre os tipos de referência BCL ("tipos especiais?") E seu estado. Uma rápida olhada em outras classes BCL compublic static
campos mostra que basicamente todas elas implementam um construtor estático (mesmo aquelas com construtores vazios e sem dados, comoSystem.DBNull
eSystem.Empty
. Os tipos de valor BCL compublic static
campos não parecem implementar um construtor estático (System.IntPtr
por exemplo) Isso parece indicar que o JIT faz algumas suposições sobre a inicialização do tipo de referência BCL.FYI Aqui está o código JITed para as duas versões:
A<object>.ctor(out string)
:A<int32>.ctor(out string)
:O restante do código (
Main
) é idêntico entre as duas versões.EDITAR
Além disso, o IL das duas versões é idêntico, exceto pela chamada para
A.ctor
inB.Main()
, onde o IL da primeira versão contém:versus
no segundo.
Outra coisa a ser observada é que o código JITed para
A<int>.ctor(out string)
: é o mesmo da versão não genérica.fonte
string.Empty
ou""
... :)typeof(string).GetField("Empty").SetValue(null, "Hello world!"); Console.WriteLine(string.Empty);
dá resultados diferentes no .NET 4.0 versus .NET 4.5. Essa alteração está relacionada à alteração descrita acima? Como o .NET 4.5 tecnicamente me ignora alterando um valor de campo? Talvez eu deva fazer uma nova pergunta sobre isso?Eu suspeito fortemente que isso seja causado por essa otimização (relacionada a
BeforeFieldInit
) no .NET 4.0.Se eu me lembro bem:
Quando você declara explicitamente um construtor estático,
beforefieldinit
é emitido, informando ao tempo de execução que o construtor estático deve ser executado antes de qualquer membro estático acessar .Meu palpite:
Eu acho que eles de alguma forma estragaram esse fato no x64 JITer, de modo que quando um membro estático de um tipo diferente é acessado de uma classe cujo próprio construtor estático já foi executado, ele de alguma forma ignora a execução (ou executa na ordem errada) o construtor estático - e, portanto, causa uma falha. (Você não recebe uma exceção de ponteiro nulo, provavelmente porque não foi inicializado com nulo.)
Como não executei seu código, esta parte pode estar errada - mas se eu precisasse adivinhar, diria que pode ser que algo
string.Format
(ouConsole.WriteLine
semelhante) precise acessar internamente e esteja causando o travamento, como talvez uma classe relacionada ao código do idioma que precise de construção estática explícita.Novamente, não testei, mas é o meu melhor palpite sobre os dados.
Sinta-se à vontade para testar minha hipótese e me informar como ela é.
fonte
B
não possui um construtor estático e não ocorre quandoA
é reificado com um tipo de valor. Eu acho que é um pouco mais complicado.B
ter um construtor estático não importa muito. ComoA
possui um controlador estático, o tempo de execução altera a ordem em que é executado quando comparado a alguma classe relacionada ao código do idioma em outro espaço de nome. Portanto, esse campo ainda não foi inicializado. No entanto, se você instanciarA
com um tipo de valor, pode ser a segunda passagem do tempo de execução pela instanciaçãoA
(o CLR provavelmente já o pré-instanciou com um tipo de referência, como uma otimização), para que o pedido funcione quando for executado pela segunda vez .beforefieldinit
otimização fornecida é a causa raiz. Pode ser que algumas das explicações reais sejam diferentes das mencionadas, mas a causa raiz provavelmente é a mesma coisa.A<object>.ctor()
.Uma observação, mas o DotPeek mostra a string descompilada.
Se eu declarar o meu
Empty
da mesma maneira, exceto sem o atributo, não receberei mais o MDA:fonte
""
resolve isso.