Comment l'analyseur PVS-Studio a commencé à trouver encore plus d'erreurs dans les projets Unity

image1.png


Lors du développement de l'analyseur statique PVS-Studio, nous essayons de le développer dans différentes directions. Ainsi, notre équipe travaille sur des plugins pour IDE (Visual Studio, Rider), l'amélioration de l'intégration avec CI, etc. Augmenter l'efficacité de l'analyse de projet pour Unity est également l'un de nos objectifs prioritaires. Nous pensons que l'analyse statique permettra aux programmeurs utilisant ce moteur de jeu d'améliorer la qualité de leur code source et de simplifier le travail sur n'importe quel projet. Par conséquent, je voudrais augmenter la popularité de PVS-Studio parmi les entreprises développant pour Unity. L'une des premières étapes de la mise en œuvre de cette idée a été d'écrire des annotations pour les méthodes définies dans le moteur. Cela vous permet de contrôler l'exactitude du code associé aux appels aux méthodes annotées.



introduction



Les annotations sont l'un des mécanismes d'analyse les plus importants. Ils fournissent diverses informations sur les arguments, les valeurs de retour et les fonctionnalités internes des méthodes qui ne peuvent pas être déterminées automatiquement. En même temps, le programmeur d'annotations peut suggérer une structure interne approximative de la méthode et des caractéristiques de son travail, basée sur la documentation et le bon sens.



Par exemple, l'appel de la méthode GetComponent semble un peu étrange si la valeur qu'elle renvoie n'est pas utilisée. Erreur absurde? Pas du tout. Bien sûr, cela peut simplement être un défi supplémentaire, oublié et abandonné par tout le monde. Ou il se peut qu'il manque une tâche importante. Les annotations peuvent aider l'analyseur à trouver des erreurs similaires et de nombreuses autres.



Bien sûr, nous avons déjà écrit de nombreuses annotations pour l'analyseur. Par exemple, les méthodes de classe annotées de l'espace de noms System . De plus, il existe également un mécanisme d'annotation automatique de certaines méthodes. Vous pouvez en savoir plus à ce sujet ici . Je note que cet article en dit plus sur la partie de PVS-Studio, qui est chargée d'analyser les projets C ++. Cependant, il n'y a aucune différence tangible dans le fonctionnement des annotations pour C # et C ++.



Écriture d'annotations pour les méthodes Unity



Nous nous efforçons d'améliorer la qualité de la révision du code des projets utilisant Unity, et il a donc été décidé d'annoter les méthodes de ce moteur.



L'idée originale était de couvrir toutes les méthodes Unity avec des annotations en général, cependant, il y en avait beaucoup. Pour cette raison, nous avons décidé de commencer à annoter des méthodes à partir des classes les plus couramment utilisées.



Collecte d'informations



Tout d'abord, il était nécessaire de savoir exactement quelles classes sont utilisées plus souvent que d'autres. De plus, un aspect important est la possibilité de collecter les résultats d'annotation - de nouvelles erreurs que l'analyseur trouvera dans des projets réels grâce aux annotations écrites. Par conséquent, la première étape a été de trouver des projets open source pertinents. Cependant, cela ne s'est pas avéré si facile.



Le problème est que la plupart des projets trouvés étaient assez petits en termes de code source. Dans ce cas, s'il y a des erreurs, alors il n'y en a pas beaucoup, et il est encore moins probable d'y trouver des points positifs liés spécifiquement aux méthodes d'Unity. Parfois, il y avait des projets qui n'utilisaient pratiquement pas (ou n'utilisaient pas du tout) des classes spécifiques à Unity, bien que selon la description, ils étaient en quelque sorte liés au moteur. De telles découvertes n'étaient absolument pas adaptées à la tâche à accomplir.



Bien sûr, parfois chanceux. Par exemple, le joyau de cette collection est MixedRealityToolkit . Le code qu'il contient est déjà décent, ce qui signifie que les statistiques collectées sur l'utilisation des méthodes Unity dans un tel projet seront plus complètes.



Ainsi, 20 projets ont été recrutés en utilisant les capacités du moteur. Afin de trouver les classes les plus couramment utilisées, un utilitaire basé sur Roslyn a été écrit pour compter les appels de méthode à partir de Unity. À propos, un tel programme peut également être appelé un analyseur statique. Après tout, si vous y réfléchissez, elle analyse vraiment le code source, sans recourir au lancement du projet lui-même.



L '"analyseur" écrit a permis de trouver les classes dont la fréquence moyenne d'utilisation dans les projets trouvés est la plus élevée:



  • UnityEngine.Vector3
  • UnityEngine.Mathf
  • UnityEngine.Debug
  • UnityEngine.GameObject
  • UnityEngine.Material
  • UnityEditor.EditorGUILayout
  • UnityEngine.Component
  • UnityEngine.Object
  • UnityEngine.GUILayout
  • UnityEngine.Quaternion
  • Etc.


Bien sûr, cela ne signifie pas du tout que ces classes sont vraiment utilisées par les développeurs si souvent - après tout, les statistiques basées sur un si petit échantillon de projets ne sont pas particulièrement fiables. Cependant, pour démarrer cette information était suffisant pour être sûr que les méthodes des classes qui sont utilisées au moins quelque part sont annotées.



Annotation



Après avoir obtenu les informations dont vous avez besoin, il est temps de commencer avec l'annotation réelle. La documentation et l'éditeur d'Unity, où le projet de test a été créé , ont été de fidèles assistants dans ce domaine . Cela était nécessaire pour vérifier certains points non spécifiés dans la documentation. Par exemple, il était loin d'être toujours clair si la transmission de null dans un argument entraînerait une erreur ou si le programme se poursuivrait sans problème. Bien sûr, passer null , en règle générale, n'est pas entièrement bon, mais dans ce cas, nous n'avons considéré que les erreurs qui ont interrompu le thread d'exécution, ou il a été enregistré par l'éditeur Unity comme une erreur.



Au cours de ces vérifications, des caractéristiques intéressantes du travail de certaines méthodes ont été trouvées. Par exemple, exécuter le code



MeshRenderer renderer = cube.GetComponent<MeshRenderer>();
Material m = renderer.material;
List<int> outNames = null;
m.GetTexturePropertyNameIDs(outNames);


conduit au fait que l'éditeur Unity lui-même plante, bien que généralement dans de tels cas, l'exécution du script en cours soit interrompue et l'erreur correspondante est enregistrée. Bien sûr, il est peu probable que les développeurs écrivent souvent ceci, mais le fait que l'éditeur Unity puisse être mis en panne en exécutant des scripts ordinaires n'est pas très bon. La même chose se produit dans au moins un autre cas:



MeshRenderer renderer = cube.GetComponent<MeshRenderer>();
Material m = renderer.material;
string keyWord = null;
bool isEnabled = m.IsKeywordEnabled(keyWord);


Ces problèmes concernent Unity Editor 2019.3.10f1.



Collecte des résultats



Une fois l'annotation terminée, vous devez vérifier comment cela affectera les avertissements émis. Avant d'ajouter des annotations, un journal des erreurs est généré pour chacun des projets sélectionnés, que nous appelons la référence. De nouvelles annotations sont ensuite intégrées à l'analyseur et les projets sont revérifiés. Les listes d'alertes générées, grâce aux annotations, seront différentes des références.



La procédure de test d'annotation est effectuée automatiquement à l'aide du programme CSharpAnalyserTester spécialement écrit pour ces besoins. Il démarre l'analyse des projets, après quoi il compare les journaux résultants avec les références et génère des fichiers contenant des informations sur les différences.



L'approche décrite est également utilisée pour découvrir les modifications apportées aux journaux lors de l'ajout d'un nouveau diagnostic ou de la modification d'un diagnostic existant.



Comme indiqué précédemment, il y a eu des difficultés à trouver de grands projets ouverts sous Unity. C'est désagréable, car pour eux l'analyseur pourrait produire des réponses plus intéressantes. Dans le même temps, il y aurait beaucoup plus de différences entre les journaux de référence et les journaux générés après l'annotation.



Néanmoins, les annotations écrites ont permis d'identifier plusieurs moments suspects dans les projets à l'étude, ce qui est également un résultat agréable du travail.



Par exemple, un appel quelque peu étrange à GetComponent a été trouvé :



void OnEnable()
{
  GameObject uiManager = GameObject.Find("UIRoot");

  if (uiManager)
  {
    uiManager.GetComponent<UIManager>();
  }
}


Avertissement de l'analyseur : V3010 La valeur de retour de la fonction «GetComponent» doit être utilisée. - SUPPLÉMENTAIRE DANS UIEditorWindow.cs ACTUEL 22



D'après la documentation , il est logique de conclure que la valeur renvoyée par cette méthode doit être utilisée d'une manière ou d'une autre. Par conséquent, une fois annoté, il était marqué en conséquence. Immédiatement, le résultat de l'appel n'est attribué à rien, ce qui semble un peu étrange.



Et voici un autre exemple d'opérations d'analyseur supplémentaires:



public void ChangeLocalID(int newID)
{
  if (this.LocalPlayer == null)                          // <=
  {
    this.DebugReturn(
      DebugLevel.WARNING, 
      string.Format(
        ...., 
        this.LocalPlayer, 
        this.CurrentRoom.Players == null,                // <=
        newID  
      )
    );
  }

  if (this.CurrentRoom == null)                          // <=
  {
    this.LocalPlayer.ChangeLocalID(newID);               // <=
    this.LocalPlayer.RoomReference = null;
  }
  else
  {
    // remove old actorId from actor list
    this.CurrentRoom.RemovePlayer(this.LocalPlayer);

    // change to new actor/player ID
    this.LocalPlayer.ChangeLocalID(newID);

    // update the room's list with the new reference
    this.CurrentRoom.StorePlayer(this.LocalPlayer);
  }
}


Avertissements de l'analyseur :



  • V3095 L'objet 'this.CurrentRoom' a été utilisé avant d'être vérifié par rapport à null. Vérifiez les lignes: 1709, 1712. - ADDITIONAL IN CURRENT LoadBalancingClient.cs 1709
  • V3125 L'objet 'this.LocalPlayer' a été utilisé après avoir été vérifié par rapport à null. Vérifiez les lignes: 1715, 1707. - ADDITIONAL IN CURRENT LoadBalancingClient.cs 1715


Notez que PVS-Studio ne fait pas attention au passage de LocalPlayer à string.Format , car cela n'entraînera pas d'erreur. Et le code semble avoir été écrit intentionnellement.



Dans ce cas, l'effet des annotations n'est pas si évident. Cependant, ce sont eux qui ont provoqué la survenue de ces positifs. La question se pose - pourquoi ces avertissements n'existaient pas auparavant?



Le fait est que plusieurs appels sont effectués dans la méthode DebugReturn , ce qui en théorie pourrait affecter la valeur de la propriété CurrentRoom :



public virtual void DebugReturn(DebugLevel level, string message)
{
  #if !SUPPORTED_UNITY
  Debug.WriteLine(message);
  #else
  if (level == DebugLevel.ERROR)
  {
    Debug.LogError(message);
  }
  else if (level == DebugLevel.WARNING)
  {
    Debug.LogWarning(message);
  }
  else if (level == DebugLevel.INFO)
  {
    Debug.Log(message);
  }
  else if (level == DebugLevel.ALL)
  {
    Debug.Log(message);
  }
  #endif
}


L'analyseur ne connaît pas les particularités des méthodes appelées, ce qui signifie qu'il ne sait pas comment elles affecteront la situation. Ainsi, PVS-Studio suppose que la valeur de this.CurrentRoom peut changer pendant le fonctionnement de la méthode DebugReturn , par conséquent, une vérification supplémentaire est effectuée.



Les annotations ont fourni des informations selon lesquelles les méthodes appelées dans DebugReturn n'affecteront pas les valeurs des autres variables. Par conséquent, utiliser une variable avant de la vérifier pour null est suspect.



Conclusion



En résumé, il vaut la peine de dire que l'annotation des méthodes spécifiques à Unity permettra sans aucun doute de trouver des erreurs plus diverses dans les projets utilisant ce moteur. Néanmoins, couvrir les annotations de toutes les méthodes disponibles nécessitera beaucoup de temps. Il sera plus efficace d'annoter d'abord les plus fréquemment utilisés. Cependant, comprendre quelles classes sont utilisées le plus souvent nécessite des projets appropriés avec une grande base de code. De plus, les grands projets permettent un bien meilleur contrôle de l'efficacité des annotations. Nous continuerons de traiter de tout cela dans un proche avenir.



L'analyseur évolue et se perfectionne constamment. L'ajout d'annotations aux méthodes Unity n'est qu'un exemple d'extension de ses capacités. Ainsi, au fil du temps, l'efficacité de PVS-Studio augmente. Par conséquent, si vous n'avez pas essayé PVS-Studio, il est temps de résoudre ce problème en le téléchargeant à partir de la page correspondante . Là, vous pouvez également obtenir une clé d'essai pour que l'analyseur se familiarise avec ses capacités en vérifiant divers projets.





Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Nikita Lipilin. Comment l'analyseur PVS-Studio a commencé à trouver encore plus d'erreurs dans les projets Unity .



All Articles