2010-04-20 3 views
7

J'ai un argument avec le CodeContracts static analysis tool.CodeContracts: appel possible d'une méthode sur une référence nulle

Mon code:

Screenshot http://i40.tinypic.com/r91zq9.png

(ASCII version)

L'outil me dit que instance.bar peut être une référence nulle. Je crois le contraire.

Qui a raison? Comment puis-je le prouver?

+2

hors sujet, mais pourriez-vous me dire ce thème visual studio que vous utilisez? – Justin

+1

@Justin: juste le thème par défaut vs2010 avec mon propre schéma de couleur de texte personnalisé (voir la publication éditée). Pourquoi demandes-tu? – dtb

+0

Je viens de chercher à changer mon propre schéma de couleurs de studio visuel récemment et vraiment comme le vôtre. Je me demandais si vous l'aviez téléchargé quelque part. – Justin

Répondre

2

Mise à jour: Il semble que le problème est que invariants are not supported for static fields.

2ème mise à jour: La méthode décrite ci-dessous est currently the recommended solution.

Une solution possible est de créer une propriété pour instance que Ensure s les invariants que vous souhaitez conserver. (Bien sûr, vous avez besoin de Assume pour que le Ensure soit prouvé.) Une fois que vous avez fait cela, vous pouvez simplement utiliser la propriété et tous les invariants doivent être prouvés correctement.

Voici votre exemple en utilisant cette méthode:

class Foo 
{ 
    private static readonly Foo instance = new Foo(); 
    private readonly string bar; 

    public static Foo Instance 
    // workaround for not being able to put invariants on static fields 
    { 
     get 
     { 
      Contract.Ensures(Contract.Result<Foo>() != null); 
      Contract.Ensures(Contract.Result<Foo>().bar != null); 

      Contract.Assume(instance.bar != null); 
      return instance; 
     } 
    } 

    public Foo() 
    { 
     Contract.Ensures(bar != null); 
     bar = "Hello world!"; 
    } 

    public static int BarLength() 
    { 
     Contract.Assert(Instance != null); 
     Contract.Assert(Instance.bar != null); 
     // both of these are proven ok 

     return Instance.bar.Length; 
    } 
} 
+0

Merci d'avoir enquêté sur ce sujet. N'hésitez pas à le publier sur le forum MSDN si cela vous intéresse. J'ai arrêté d'utiliser le vérificateur statique pour le moment parce qu'une révision de code produit actuellement de meilleurs résultats pour moi. – dtb

+0

J'ai trouvé un message sur le forum qui indique que les invariants ne sont pas supportés par les champs statiques. J'ai mis à jour mon post avec une solution de contournement. – porges

+0

Belle trouvaille. Mais 'bar' est un champ d'instance, pas statique!? Quoi qu'il en soit, j'ai déplacé la coche à cette réponse parce que c'est la meilleure explication jusqu'à présent. – dtb

2

CodeContracts a raison. Rien ne vous empêche de définir instance.bar = null avant d'appeler la méthode BarLength().

+2

Mais je ne mets pas 'instance.bar = null' n'importe où et' instance.bar' est privé, donc il ne peut pas être nul, non? – dtb

+0

Oui, donc je suppose que vous avez raison si c'est tout le code. Son analyse statique n'est probablement pas assez intelligente pour déterminer si vous avez déjà défini la valeur sur null ou non, donc cela suppose que vous le pouvez. Je ne sais pas quelle longueur il faut pour vérifier cela - s'il y en a. – EMP

+0

Comment puis-je convaincre que 'instance.bar' n'est jamais' null'? Rendre 'bar' en lecture seule et ajouter' Contract.Ensures (bar! = Null); 'au constructeur ne fait pas l'affaire. 'Contract.Assume (instance.bar! = Null);' dans 'BarLength()' fonctionne mais semble moche. – dtb

-1

Je suis d'accord avec vous. instance et bar sont tous les deux privés, donc CodeContracts devrait être capable de savoir que instance.bar n'est jamais défini sur null.

0

Si vous marquez le champ 'bar' comme étant en lecture seule, cela devrait satisfaire l'analyseur statique que le champ ne sera jamais défini après le ctor.

+0

Marquer 'bar' comme étant en lecture seule n'a aucun effet. Pour une raison quelconque, l'analyseur ne semble pas réaliser que j'affecte 'bar' à une valeur non nulle dans le constructeur. – dtb

2

Votre code comprend une instance initialisée statique privé:

private static Foo instance = new Foo(); 

Vous présumez que cela signifie que la instance de constructeur aura toujours courir avant l'accès à toute méthode statique, assurant ainsi bar a été initialisé?

Dans le cas d'un seul filetage, je pense que vous avez raison.

La séquence des événements serait:

  1. Appel à Foo.BarLength()
  2. initialisation statique de classe Foo (si pas déjà terminé)
  3. d'initialisation statique de membre statique privé instance avec instance de Foo
  4. Entrée à Foo.BarLength()

Toutefois, l'initialisation statique d'une classe est uniquement déclenchée une fois par App Domain - et IIRC il n'y a aucun blocage pour s'assurer que est complété avant d'appeler d'autres méthodes statiques.

Donc, vous pourriez avoir ce scénario:

  1. Discussion Alpha: Appel à Foo.BarLength()
  2. Discussion Alpha: L'initialisation statique de classe Foo (sinon déjà terminé) commence
  3. Context Switch
  4. Thread Beta: Appelez à Foo.BarLength()
  5. Thread Beta: Aucun appel à l'initialisation statique de classe Foo parce que c'est déjà en cours
  6. Discussion Beta: Entrée Foo.BarLength()
  7. Discussion Beta: L'accès à null membre statique instance

Il n'y a aucun moyen de l'analyseur de contrats peut savoir que vous ne lanceriez jamais le code d'une manière multithread, donc il doit pécher par excès de prudence.

+0

À droite, mais comme vous le dites (étape 7), 'instance' serait null, pas' instance.bar'. 'instance.bar' est seulement null avant d'être assigné dans le constructeur de l'instance de Foo, mais l'instance n'est stockée dans le champ que lorsque le constructeur a été complété. – dtb

+0

De toute façon, c'est évidemment une limitation de l'analyseur statique. Maintenant, je cherche la meilleure façon de le convaincre que j'ai raison. Détruire mon code avec des instructions 'Assume' ou avec des vérifications qui ne peuvent jamais se produire est un peu sale. – dtb

+0

Oui, le CLR vérifie que le constructeur statique est terminé avant toute référence à cette classe, sauf si vous avez deux classes avec des constructeurs statiques qui se référencent mutuellement: http://msdn.microsoft.com/fr-fr/library/ aa645612.aspx – EMP

Questions connexes