44 votes

Bogue survenant uniquement lorsque l'optimisation de la compilation est activée

Je suis tombé sur un bug dans un code qui ne se reproduit que lorsque le code est construit avec les optimisations activées. J'ai créé une application console qui reproduit la logique à des fins de test (code ci-dessous). Vous verrez que lorsque l'optimisation est activée, 'value' devient nul après l'exécution de cette logique invalide :

if ((value == null || value == new string[0]) == false)

La correction est simple et est commentée sous le code incriminé. Mais... Je suis plus préoccupé par le fait que j'ai peut-être rencontré un bogue dans l'assembleur ou peut-être que quelqu'un d'autre a une explication de la raison pour laquelle la valeur devient nulle.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace memory_testing
{
    class Program
    {
        sta tic void Main(string[] args)
        {
            while(true)
            {
                Console.Write("Press any key to start...");
                Console.ReadKey();
                Console.WriteLine();
                PrintManagerUser c = new PrintManagerUser();
                c.MyProperty = new string[1];
            }
        }
    }

    public class PrintManager
    {
        public void Print(string key, object value)
        {
            Console.WriteLine("Key is: " + key);
            Console.WriteLine("Value is: " + value);
        }
    }

    public class PrintManagerUser
    {
        public string[] MyProperty
        {
            get { return new string[100]; }
            set
            {
                Console.WriteLine("Pre-check Value is: " + value);
                if ((value == null || value == new string[0]) == false)
                {
                    Console.WriteLine("Post-check Value is: " + value);
                    new PrintManager().Print("blah", value);
                }
                //if (value != null && value.Length > 0)
                //{
                //    new PrintManager().Print("blah", value);
                //}
            }
        }
    }
}

La sortie normale devrait être :

Pre-check Value is: System.String[]
Post-check Value is: System.String[]
Key is: blah
Value is: System.String[]

La sortie boguée est :

Pre-check Value is: System.String[]
Post-check Value is:
Key is: blah
Value is:   

Mon Env est une VM exécutant Windows Server 2003 R2 avec .NET 3.5 SP1. J'utilise le système d'équipe VS2008.

Merci,

Brian

45voto

Hans Passant Points 475940

Oui, votre expression confond fatalement l'optimiseur JIT. Le code machine généré ressemble à ceci :

                if ((value == null || value == new string[0]) == false)
00000027  test        esi,esi               ; value == null?
00000029  je          00000075 
0000002b  xor         edx,edx               ; new string[0]
0000002d  mov         ecx,6D913BD2h 
00000032  call        FFD20BC8 
00000037  cmp         eax,esi               ; (value == new string[0]) == false?
00000039  je          00000075 
                {
                    Console.WriteLine("Post-check Value is: " + value);
0000003b  mov         ecx,dword ptr ds:[03532090h]  ; "Post-check value is: "
00000041  xor         edx,edx               ; BUGBUG not null!
00000043  call        6D70B7E8              ; String.Concat()
00000048  mov         esi,eax               ; 
0000004a  call        6D72BE08              ; get Console.Out
0000004f  mov         ecx,eax 
00000051  mov         edx,esi 
00000053  mov         eax,dword ptr [ecx] 
00000055  call        dword ptr [eax+000000D8h]     ; Console.WriteLine()

Le bogue se produit à l'adresse 41, l'optimiseur a conclu que la valeur sera toujours nulle et il transmet directement un null à String.Concat().

À titre de comparaison, voici le code qui est généré lorsque l'optimisation JIT est désactivée :

                    Console.WriteLine("Post-check Value is: " + value);
00000056  mov         ecx,dword ptr ds:[03342090h] 
0000005c  mov         edx,dword ptr [ebp-8] 
0000005f  call        6D77B790 

Le code a été déplacé, mais notez qu'à l'adresse 5c il utilise maintenant la variable locale (valeur) au lieu de null.

Vous pouvez signaler ce bogue à l'adresse connect.microsoft.com. La solution de contournement est simple :

  if (value != null)
  {
    Console.WriteLine("Post-check Value is: " + value);
    new PrintManager().Print("blah", value);
  }

3voto

ladenedge Points 4986

Ce bogue semble avoir été corrigé dans .NET 4 (bêta 2). Voici le désassemblage x86 optimisé pour le bit que nobugz a mis en évidence, ci-dessus :

                    Console.WriteLine("Post-check Value is: " + value);
00000056  mov         ecx,dword ptr ds:[033C2090h] 
0000005c  mov         edx,dword ptr [ebp-8] 
0000005f  call        65D8FE10

Le programme affiche également le résultat attendu en mode optimisé et non optimisé.

2voto

Maurits Rijk Points 4873
value == new string[0]

Ce qui précède me semble être une déclaration bizarre. Vous comparez deux tableaux de chaînes avec une instruction equals. Le résultat ne sera vrai que s'ils pointent tous deux vers le même tableau, ce qui est assez improbable. Cela n'explique pas encore pourquoi ce code se comporte différemment dans une version optimisée.

1voto

Tim Coker Points 3943

Je suis sur x64 et je n'ai pas pu reproduire le problème au début. Puis j'ai spécifié la cible en x86, et ça m'est arrivé. Je suis revenu à x64, et le problème a disparu. Je ne suis pas sûr de ce que cela signifie exactement, mais j'ai fait des allers-retours plusieurs fois maintenant.

0voto

rsp Points 14367

Cela ressemble certainement à un bogue, se reproduit-il lorsque vous échangez les opérandes de l'opérateur comme ceci ?

if (false == (null == value || new string[0] == value))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X