Cómo el analizador PVS-Studio comenzó a encontrar aún más errores en proyectos de Unity

image1.png


Mientras desarrollamos el analizador estático PVS-Studio, intentamos desarrollarlo en varias direcciones. Por lo tanto, nuestro equipo está trabajando en complementos para IDE (Visual Studio, Rider), mejorando la integración con CI, etc. El aumento de la eficiencia del análisis de proyectos para Unity es también uno de nuestros objetivos prioritarios. Creemos que el análisis estático permitirá a los programadores que usan este motor de juego mejorar la calidad de su código fuente y simplificar el trabajo en cualquier proyecto. Por lo tanto, me gustaría aumentar la popularidad de PVS-Studio entre las compañías que desarrollan para Unity. Uno de los primeros pasos en la implementación de esta idea fue escribir anotaciones para los métodos definidos en el motor. Esto le permite controlar la corrección del código asociado con las llamadas a métodos anotados.



Introducción



Las anotaciones son uno de los mecanismos analizadores más importantes. Proporcionan información variada sobre argumentos, valores de retorno y características internas de métodos que no se pueden resolver automáticamente. Al mismo tiempo, el programador de anotaciones puede asumir la estructura interna aproximada del método y los detalles de su funcionamiento, basándose en la documentación y el sentido común.



Por ejemplo, llamar al método GetComponent parece algo extraño si no se usa el valor devuelto por él. ¿Error sin sentido? De ningún modo. Por supuesto, esto puede ser simplemente un desafío adicional, olvidado y abandonado por todos. O puede ser que falte alguna tarea importante. Las anotaciones pueden ayudar al analizador a encontrar errores similares y muchos otros.



Por supuesto, ya hemos escrito muchas anotaciones para el analizador. Por ejemplo, los métodos de clases del espacio de nombres del sistema están anotados . Además, también hay un mecanismo para la anotación automática de algunos métodos. Puedes leer más sobre esto aquí . Tenga en cuenta que este artículo brinda más información sobre la parte de PVS-Studio que se encarga de analizar proyectos en C ++. Sin embargo, no hay una diferencia tangible en la forma en que funcionan las anotaciones para C # y C ++.



Escribir anotaciones para métodos de Unity



Nos esforzamos por mejorar la calidad de la revisión de código de proyectos que utilizan Unity y, por lo tanto, se decidió anotar los métodos de este motor.



La idea original era cubrir todos los métodos de Unity con anotaciones en general, sin embargo, había muchos de ellos. Como resultado, decidimos comenzar anotando métodos de las clases más comúnmente utilizadas.



Colección de información



Primero, era necesario averiguar qué clases se usan con más frecuencia que otras. Además, un aspecto importante es proporcionar la capacidad de recopilar resultados de anotaciones, nuevos errores que el analizador encuentra en proyectos reales debido a anotaciones escritas. Por lo tanto, el primer paso fue buscar proyectos apropiados de código abierto. Sin embargo, esto resultó no ser tan fácil.



El problema es que muchos de los proyectos encontrados fueron bastante pequeños en términos de código fuente. En tal caso, si hay errores, entonces no hay muchos de ellos, y es aún menos probable que encuentre allí aspectos positivos relacionados específicamente con los métodos de Unity. A veces había proyectos que prácticamente no usaban (o no usaban en absoluto) clases específicas de Unity, aunque de acuerdo con la descripción, de alguna manera estaban conectadas con el motor. Tales hallazgos eran completamente inadecuados para la tarea en cuestión.



Por supuesto, a veces con suerte. Por ejemplo, la gema en esta colección es el MixedRealityToolkit . Ya existe un código decente, lo que significa que las estadísticas recopiladas sobre el uso de los métodos de Unity en dicho proyecto serán más completas.



Por lo tanto, se reclutaron 20 proyectos utilizando las capacidades del motor. Para encontrar las clases más utilizadas, se escribió una utilidad basada en Roslyn para contar las llamadas a métodos de Unity. Por cierto, dicho programa también se puede llamar analizador estático. Después de todo, si lo piensa, ella realmente analiza el código fuente, sin recurrir al lanzamiento del proyecto en sí.



El "analizador" escrito hizo posible encontrar clases cuya frecuencia promedio de uso en proyectos encontrados es más alta:



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


Por supuesto, esto no significa en absoluto que estas clases sean realmente utilizadas tan a menudo por los desarrolladores; después de todo, las estadísticas basadas en una muestra tan pequeña de proyectos no son muy confiables. Sin embargo, para comenzar esta información fue suficiente para asegurarse de que los métodos de las clases que se utilizan al menos en algún lugar están anotados.



Anotación



Después de obtener la información que necesita, es hora de comenzar con la anotación real. La documentación y el editor de Unity, donde se creó el proyecto de prueba , fueron ayudantes fieles en este asunto . Esto fue necesario para verificar algunos puntos no especificados en la documentación. Por ejemplo, estaba lejos de estar siempre claro si pasar nulo en cualquier argumento conduciría a un error, o si el programa continuaría ejecutándose sin problemas. Por supuesto, pasar nulo generalmente no es muy bueno, pero en este caso consideramos errores solo que interrumpieron el flujo de ejecución, o el editor de Unity lo registró como un error.



Durante dichos controles, se encontraron características interesantes del trabajo de algunos métodos. Por ejemplo, ejecutando el código



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


conduce al hecho de que el editor de Unity se bloquea, aunque generalmente en tales casos se interrumpe la ejecución del script actual y se registra el error correspondiente. Por supuesto, es poco probable que los desarrolladores a menudo escriban algo así, pero el hecho de que el editor de Unity pueda caerse al ejecutar scripts normales no es muy bueno. Lo mismo sucede en al menos un caso más:



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


Los problemas indicados son relevantes para el editor Unity 2019.3.10f1.



Recolectando resultados



Después de realizar la anotación, debe verificar cómo afectará esto a las advertencias emitidas. Antes de agregar anotaciones, se genera un registro con errores para cada uno de los proyectos seleccionados, que llamamos referencia. Luego, se incorporan nuevas anotaciones en el analizador y se vuelven a verificar los proyectos. Las listas de advertencia generadas, debido a anotaciones, diferirán de las de referencia.



El procedimiento de prueba de anotación se realiza automáticamente utilizando el programa CSharpAnalyserTester especialmente escrito para estas necesidades. Lanza el análisis de proyectos, luego compara los registros resultantes con los de referencia y genera archivos que contienen información sobre las diferencias.



El enfoque descrito también se utiliza para averiguar qué cambios aparecen en los registros al agregar un diagnóstico nuevo o cambiar uno existente.



Como se señaló anteriormente, fue difícil encontrar grandes proyectos de código abierto para Unity. Esto es desagradable, porque el analizador podría haber producido desencadenantes más interesantes para ellos. Al mismo tiempo, habría muchas más diferencias entre los registros de referencia y los registros generados después de la anotación.



Sin embargo, las anotaciones escritas ayudaron a identificar varios puntos sospechosos en los proyectos en consideración, lo que también es un resultado agradable del trabajo.



Por ejemplo, se encontró una llamada algo extraña a GetComponent :



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

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


Advertencia del analizador : V3010 Se requiere utilizar el valor de retorno de la función 'GetComponent'. - ADICIONAL EN ACTUAL UIEditorWindow.cs 22



Según la documentación , es lógico concluir que el valor devuelto por este método debe usarse de alguna manera. Por lo tanto, al anotar, se marcó en consecuencia. Inmediatamente, el resultado de la llamada no se asigna a nada, lo que parece un poco extraño.



Y aquí hay otro ejemplo de disparadores de analizador adicionales:



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);
  }
}


Advertencias del analizador :



  • V3095 El objeto 'this.CurrentRoom' se usó antes de que se verificara como nulo. Líneas de verificación: 1709, 1712. - ADICIONAL EN CORRIENTE LoadBalancingClient.cs 1709
  • V3125 El objeto 'this.LocalPlayer' se usó después de que se verificó contra nulo. Líneas de verificación: 1715, 1707. - ADICIONAL EN CORRIENTE LoadBalancingClient.cs 1715


Tenga en cuenta que PVS-Studio no presta atención a pasar LocalPlayer a string.Format , ya que esto no conducirá a un error. Y el código parece que fue escrito a propósito.



En este caso, el efecto de las anotaciones no es tan obvio. Sin embargo, fueron ellos quienes causaron la aparición de estos aspectos positivos. Surge la pregunta: ¿por qué estas advertencias no fueron antes?



El hecho es que se realizan varias llamadas en el método DebugReturn , lo que, en teoría, podría afectar el valor de la propiedad 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
}


El analizador no conoce las características de los métodos llamados y, por lo tanto, no se sabe cómo afectarán la situación. Entonces, PVS-Studio asume que el valor de esto. CorrentRoom podría cambiar durante la operación del método DebugReturn , por lo tanto, se realiza una verificación adicional.



Las anotaciones proporcionaron información de que los métodos llamados dentro de DebugReturn no afectarán los valores de otras variables. Por lo tanto, usar una variable antes de verificar nulo puede considerarse sospechoso.



Conclusión



En resumen, vale la pena decir que anotar métodos específicos de Unity, sin lugar a dudas, permitirá encontrar más errores diversos en proyectos que usan este motor. Sin embargo, cubrir anotaciones de todos los métodos disponibles requerirá mucho tiempo. Será más eficiente anotar primero los más utilizados. Sin embargo, para comprender qué clases particulares se usan con más frecuencia, necesita proyectos adecuados con una gran base de código. Además, los proyectos grandes permiten un control mucho mejor sobre la efectividad de las anotaciones. Continuaremos lidiando con todo esto en el futuro cercano.



El analizador se desarrolla y refina constantemente. Agregar anotaciones a los métodos de Unity es solo un ejemplo de extender sus capacidades. Por lo tanto, con el tiempo, la eficiencia de PVS-Studio está creciendo. Por lo tanto, si aún no ha probado PVS-Studio, es hora de solucionarlo descargándolo de la página correspondiente . Allí puede obtener una clave de prueba para que el analizador se familiarice con sus capacidades al verificar varios proyectos.





Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Nikita Lipilin. Cómo el analizador PVS-Studio comenzó a encontrar aún más errores en los proyectos de Unity .



All Articles