L'exemple de code ci-dessous s'est produit naturellement. Soudain, mon code a fait une très mauvaise FatalExecutionEngineError
exception. J'ai passé 30 bonnes minutes à essayer d'isoler et de minimiser l'échantillon coupable. Compilez ceci à l'aide de Visual Studio 2012 en tant qu'application console:
class A<T>
{
static A() { }
public A() { string.Format("{0}", string.Empty); }
}
class B
{
static void Main() { new A<object>(); }
}
Devrait produire cette erreur sur .NET Framework 4 et 4.5:
Est-ce un bug connu, quelle en est la cause et que puis-je faire pour l'atténuer? Mon travail actuel consiste à ne pas utiliser string.Empty
, mais est-ce que j'aboie le mauvais arbre? Changer quoi que ce soit à propos de ce code le fait fonctionner comme vous vous en doutez - par exemple en supprimant le constructeur statique vide de A
ou en changeant le paramètre de type de object
à int
.
J'ai essayé ce code sur mon ordinateur portable et il ne s'est pas plaint. Cependant, j'ai essayé mon application principale et elle s'est également plantée sur l'ordinateur portable. J'ai dû démanteler quelque chose en réduisant le problème, je vais voir si je peux comprendre ce que c'était.
Mon ordinateur portable s'est écrasé avec le même code que ci-dessus, avec le framework 4.0, mais le principal plante même avec 4.5. Les deux systèmes utilisent VS'12 avec les dernières mises à jour (juillet?).
Plus d'informations :
- Code IL (Débogage compilé / Tout CPU / 4.0 / VS2010 (pas que l'IDE ne devrait avoir d'importance?)): Http://codepad.org/boZDd98E
- Pas vu VS 2010 avec 4.0. Pas écraser avec / sans optimisations, différentes CPU cible, débogueur attaché / non attaché, etc. - Tim Medora
- Crashes en 2010 si j'utilise AnyCPU, ça va dans x86. Crashes dans Visual Studio 2010 SP1, en utilisant Platform Target = AnyCPU, mais bien avec Platform Target = x86. VS2012RC est également installé sur cette machine, donc la version 4.5 peut éventuellement effectuer un remplacement sur place. Utilisez AnyCPU et TargetPlatform = 3.5, cela ne plante pas et ressemble donc à une régression dans le Framework.- Colinsmith
- Impossible de reproduire sur x86, x64 ou AnyCPU dans VS2010 avec 4.0. - Fuji
- Ne se produit que pour x64, (2012rc, Fx4.5) - Henk Holterman
- VS2012 RC sur Win8 RP. Initialement, ne pas voir ce MDA lors du ciblage de .NET 4.5. Lors du passage au ciblage .NET 4.0, le MDA est apparu. Ensuite, après le retour à .NET 4.5, le MDA reste. - Wayne
Réponses:
Ce n’est pas non plus une réponse complète, mais j’ai quelques idées.Je crois avoir trouvé une explication aussi bonne que celle que nous trouverons sans que quelqu'un de l'équipe .NET JIT ne réponde.
METTRE À JOUR
J'ai regardé un peu plus profondément et je pense avoir trouvé la source du problème. Cela semble être causé par une combinaison d'un bogue dans la logique d'initialisation de type JIT et d'un changement dans le compilateur C # qui repose sur l'hypothèse que le JIT fonctionne comme prévu. Je pense que le bogue JIT existait dans .NET 4.0, mais a été découvert par la modification du compilateur pour .NET 4.5.
Je ne pense pas que ce
beforefieldinit
soit le seul problème ici. Je pense que c'est plus simple que ça.Le type
System.String
dans mscorlib.dll de .NET 4.0 contient un constructeur statique:Dans la version .NET 4.5 de mscorlib.dll,
String.cctor
(le constructeur statique) est manifestement absent:Dans les deux versions, le
String
type est orné debeforefieldinit
:J'ai essayé de créer un type qui se compilerait en IL de la même manière (afin qu'il ait des champs statiques mais pas de constructeur statique
.cctor
), mais je ne pouvais pas le faire. Tous ces types ont une.cctor
méthode en IL:Je suppose que deux choses ont changé entre .NET 4.0 et 4.5:
Premièrement: l'EE a été modifié pour qu'il s'initialise automatiquement à
String.Empty
partir du code non géré. Cette modification a probablement été effectuée pour .NET 4.0.Deuxièmement: le compilateur a changé de sorte qu'il n'émette pas de constructeur statique pour string, sachant que
String.Empty
serait affecté du côté non managé. Cette modification semble avoir été effectuée pour .NET 4.5.Il semble que l'EE n'attribue assez tôt le long des chemins d'optimisation. La modification apportée au compilateur (ou tout ce qui a changé pour faire disparaître) attendait que l'EE fasse cette affectation avant l'exécution de tout code utilisateur, mais il semble que l'EE n'effectue pas cette affectation avant
String.Empty
String.cctor
String.Empty
soit utilisée dans des méthodes de type référence classes génériques réifiées.Enfin, je pense que le bogue est le signe d'un problème plus profond dans la logique d'initialisation de type JIT. Il semble que le changement dans le compilateur soit un cas particulier pour
System.String
, mais je doute que le JIT ait fait un cas particulier ici pourSystem.String
.Original
Tout d'abord, WOW Les gens de la BCL sont devenus très créatifs avec quelques optimisations de performances. Un grand nombre des
String
méthodes sont maintenant effectuées à l' aide d' un cache statique de discussionStringBuilder
objet.J'ai suivi cette piste pendant un certain temps, mais je ne suis
StringBuilder
pas utilisé sur leTrim
chemin du code, j'ai donc décidé qu'il ne pouvait pas s'agir d'un problème statique de Thread.Je pense que j'ai trouvé une étrange manifestation du même bug.
Ce code échoue avec une violation d'accès:
Toutefois, si vous décommentez
//new A<int>(out s);
dansMain
alors le code fonctionne très bien. En fait, siA
est réifié avec n'importe quel type de référence, le programme échoue, mais s'ilA
est réifié avec n'importe quel type de valeur, le code n'échoue pas. De plus, si vous mettez en commentaireA
le constructeur statique de, le code n'échoue jamais. Après avoir fouillé dansTrim
etFormat
, il est clair que le problème est qu'ilLength
est en ligne et que dans ces échantillons ci-dessus, leString
type n'a pas été initialisé. En particulier, à l'intérieur du corps duA
constructeur de,string.Empty
n'est pas correctement assigné, bien qu'à l'intérieur du corps deMain
,string.Empty
est attribué correctement.Il est étonnant pour moi que l'initialisation de type
String
dépend en quelque sorte de la réification ou nonA
avec un type valeur. Ma seule théorie est qu'il existe un chemin de code JIT optimisant pour l'initialisation de type générique qui est partagé entre tous les types, et que ce chemin fait des hypothèses sur les types de référence BCL ("types spéciaux?") Et leur état. Un rapide coup d'œil sur les autres classes BCL avec despublic static
champs montre que pratiquement toutes implémentent un constructeur statique (même celles avec des constructeurs vides et aucune donnée, commeSystem.DBNull
etSystem.Empty
. Les types de valeur BCL avec despublic static
champs ne semblent pas implémenter un constructeur statique (System.IntPtr
par exemple) Cela semble indiquer que le JIT émet des hypothèses sur l'initialisation du type de référence BCL.FYI Voici le code JITed pour les deux versions:
A<object>.ctor(out string)
:A<int32>.ctor(out string)
:Le reste du code (
Main
) est identique entre les deux versions.ÉDITER
De plus, l'IL des deux versions est identique à l'exception de l'appel à
A.ctor
inB.Main()
, où l'IL de la première version contient:contre
dans la seconde.
Une autre chose à noter est que le code JITed pour
A<int>.ctor(out string)
: est le même que dans la version non générique.la source
string.Empty
ou""
... :)typeof(string).GetField("Empty").SetValue(null, "Hello world!"); Console.WriteLine(string.Empty);
donne des résultats différents sur .NET 4.0 par rapport à .NET 4.5. Ce changement est-il lié au changement décrit ci-dessus? Comment .NET 4.5 peut-il techniquement ignorer la modification d'une valeur de champ? Peut-être que je devrais poser une nouvelle question à ce sujet?Je soupçonne fortement cela est causé par cette optimisation (lié à
BeforeFieldInit
) dans .NET 4.0.Si je me souviens bien:
Lorsque vous déclarez explicitement un constructeur statique,
beforefieldinit
est émis, indiquant au runtime que le constructeur statique doit être exécuté avant tout accès à un membre statique .Ma conjecture:
Je suppose qu'ils ont en quelque sorte foiré ce fait sur le JITer x64, de sorte que lorsqu'un membre statique d' un type différent est accédé à partir d'une classe dont le propre constructeur statique a déjà exécuté, il saute d'une manière ou d'une autre l' exécution (ou exécute dans le mauvais ordre) le constructeur statique - et provoque donc un crash. (Vous n'obtenez pas d'exception de pointeur nul, probablement parce qu'il n'est pas initialisé par null.)
Je n'ai pas exécuté votre code, donc cette partie peut être erronée - mais si je devais faire une autre estimation, je dirais que c'est peut-être quelque chose
string.Format
(ouConsole.WriteLine
, ce qui est similaire), qui doit accéder en interne à l'origine du crash, comme peut-être une classe liée à la locale qui a besoin d'une construction statique explicite.Encore une fois, je ne l'ai pas testé, mais c'est ma meilleure estimation des données.
N'hésitez pas à tester mon hypothèse et à me dire comment ça se passe.
la source
B
n'a pas de constructeur statique, et il ne se produit pas lorsqueA
est réifié avec un type valeur. Je pense que c'est un peu plus compliqué.B
avoir un constructeur statique n'a pas beaucoup d'importance. Puisqu'ilA
a un ctor statique, le runtime perturbe l'ordre dans lequel il est exécuté par rapport à une classe liée aux paramètres régionaux dans un autre espace de noms. Donc, ce champ n'est pas encore initialisé. Cependant, si vous instanciezA
avec un type valeur, il peut s'agir du deuxième passage du runtime via l'instanciationA
(le CLR l'a probablement déjà pré-instancié avec un type de référence, en tant qu'optimisation) afin que l'ordre fonctionne lorsqu'il est exécuté une deuxième fois .beforefieldinit
optimisation donnée est la cause première. Il se peut qu'une partie de l'explication réelle soit différente de ce que j'ai mentionné, mais la cause profonde est probablement la même chose.A<object>.ctor()
.Une observation, mais DotPeek montre la chaîne décompilée. Videz ainsi:
Si je déclare le mien de
Empty
la même manière sauf sans l'attribut, je n'obtiens plus le MDA:la source
""
résout.