Je sais comment faire pour l'empêcher de se produire
Je ne sais pas pourquoi il la fait (encore). Et il semblerait que vous avez trouvé un bug, soit dans la .Net BCL ou, plus probablement, dans le JIT.
J'ai juste commenté de toutes les lignes dans l' MyButton_Click_Aux
méthode, puis a commencé à les ramener en, un par un.
Le décollage, l' volatile
de la static int et vous n'aurez plus un StackOverflowException
.
Maintenant à la recherche pourquoi... Clairement quelque chose à faire avec les Barrières de la Mémoire est à l'origine d'un problème - peut-être forcer en quelque sorte l' MyButton_Click_Aux
méthode à appeler lui-même...
Mise à JOUR
Ok, donc, d'autres personnes trouvent qu' .Net 3.5 n'est pas un problème.
Je suis en utilisant .Nt 4 ainsi donc, ces observations ont trait à:
Comme je l'ai dit, prendre le volatile et il fonctionne.
De même, si vous mettez de la volatilité de retour sur et retirez le try/finally, il fonctionne aussi:
private static void MyButton_Click_Aux()
{
//try { /*remove because stack overflows without*/ }
//finally
//{
var myLogData = new ArrayList();
myLogData.Add(reportCount);
//myLogData.Add("method MyButtonClickAux");
//Log(myLogData);
//}
}
Je me demandais aussi si c'était quelque chose à voir avec la non initialisée reportCount
lorsque le try/finally est dans. Mais il ne fait aucune différence si vous l'initialiser à zéro.
Je suis à la recherche à la IL maintenant - même si cela peut nécessiter quelqu'un avec une certaine ASM chaps s'engager...
Dernière Mise À Jour
Comme je l'ai dit, c'est vraiment va nécessiter une analyse de l'équipe de sortie pour vraiment comprendre ce qui se passe et si je le trouve agréable à analyser assembleur - je pense que c'est probablement un travail pour quelqu'un dans Microsoft donc ce bug peut effectivement être confirmé et fixe! Que dit - il semble être un joli petit ensemble de circonstances.
Je l'ai déplacé sur une version release de se débarrasser de tous les IL du bruit (opr, etc) pour l'analyse.
Il a, cependant, eu une complication de l'impact sur le diagnostic. Je pensais que je l'avais, mais n'a pas - mais maintenant, je sais ce que c'est.
J'ai essayé ce code:
private static void MyButton_Click_Aux()
{
try { }
finally
{
var myLogData = new ArrayList();
Console.WriteLine(reportCount);
//myLogData.Add("method MyButtonClickAux");
//Log(myLogData);
}
}
Avec l'int comme volatile. Il s'exécute sans faute. Voici le IL:
.maxstack 1
L_0000: leave.s L_0015
L_0002: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()
L_0007: pop
L_0008: volatile.
L_000a: ldsfld int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) WindowsFormsApplication1.Form1::reportCount
L_000f: call void [mscorlib]System.Console::WriteLine(int32)
L_0014: endfinally
L_0015: ret
.try L_0000 to L_0002 finally handler L_0002 to L_0015
Ensuite, nous examinons le code minimal nécessaire pour obtenir une nouvelle erreur:
private static void MyButton_Click_Aux()
{
try { }
finally
{
var myLogData = new ArrayList();
myLogData.Add(reportCount);
}
}
Et c'est IL:
.maxstack 2
.locals init (
[0] class [mscorlib]System.Collections.ArrayList myLogData)
L_0000: leave.s L_001c
L_0002: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()
L_0007: stloc.0
L_0008: ldloc.0
L_0009: volatile.
L_000b: ldsfld int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) WindowsFormsApplication1.Form1::reportCount
L_0010: box int32
L_0015: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
L_001a: pop
L_001b: endfinally
L_001c: ret
.try L_0000 to L_0002 finally handler L_0002 to L_001c
La différence? Il y a bien deux que j'ai repéré de la boxe de la volatilité de l'int, et un appel virtuel. J'ai donc l'installation de ces deux classes:
public class DoesNothingBase
{
public void NonVirtualFooBox(object arg) { }
public void NonVirtualFooNonBox(int arg) { }
public virtual void FooBox(object arg) { }
public virtual void FooNonBox(int arg) { }
}
public class DoesNothing : DoesNothingBase
{
public override void FooBox(object arg) { }
public override void FooNonBox(int arg) { }
}
Et puis essayé chacun de ces quatre versions de la délinquance de la méthode:
try { }
finally
{
var doesNothing = new DoesNothing();
doesNothing.FooNonBox(reportCount);
}
Qui fonctionne.
try { }
finally
{
var doesNothing = new DoesNothing();
doesNothing.NonVirtualFooNonBox(reportCount);
}
Qui travaille aussi.
try { }
finally
{
var doesNothing = new DoesNothing();
doesNothing.FooBox(reportCount);
}
Oups - StackOverflowException
.
Et:
try { }
finally
{
var doesNothing = new DoesNothing();
doesNothing.NonVirtualFooBox(reportCount);
}
Oups encore une fois! StackOverflowException
!
Nous pourrions aller plus loin avec cela - mais la question est, je pense, clairement causés par la boxe de la volatilité de l'int tandis qu'à l'intérieur du bloc finally d'un try/catch... j'ai mis le code à l'intérieur de l'essayer, et pas de problème. J'ai ajouté une clause catch (et mettre le code en y), aussi pas de problème.
Il pourrait également s'appliquer à la boxe d'autres types de valeur, je suppose.
Donc, pour résumer - en .Net 4.0 - en debug et release - la boxe d'un volatile int dans un bloc finally semble causer le JIT pour générer du code qui se termine le remplissage de la pile. Le fait que la trace de la pile de montre, tout simplement, 'externe' appuie également cette proposition.
Il y a même une possibilité qu'il ne peut pas toujours être reproduit et pourrait même dépendent de la disposition et la taille du code généré par le try/finally. C'est clairement quelque chose à faire avec un errant jmp
ou quelque chose de similaire généré à l'emplacement incorrect qui finalement se répète une ou plusieurs pousser les commandes de la pile. L'idée que c'est effectivement causé par une zone de fonctionnement est, franchement, c'est fascinant!
Final Dernière Mise À Jour
Si vous regardez la connexion MS bug @Hâtives G trouvé (réponse plus bas) vous voyez là que le bug se manifeste d'une manière similaire, mais avec une volatilité bool dans un catch déclaration.
Aussi - MS en attente d'un correctif pour cette après en avoir pour repro - mais pas de correctif disponible encore au bout de 7 mois. Je suis allé sur enregistrer avant comme étant à l'appui de connexion MS, donc je vais dire non plus - je ne pense pas que j'en ai besoin!
Finale Finale Finale Mise À Jour (23/02/2011)
Il est fixe, mais n'est pas encore sorti. Citation de la MS de l'Équipe sur la connexion MS bug:
Oui, c'est corrigé. Nous sommes dans le processus de trouver comment mieux pour expédier un correctif. C'est déjà corrigé dans la version 4.5, mais nous aimerions vraiment fixer un lot de génération de code de bugs avant la 4.5-release.