PVS-Studio para Java

PVS-Studio para Java

En la séptima versión del analizador estático PVS-Studio, agregamos soporte para el lenguaje Java. Es hora de hablar un poco sobre cómo comenzamos a brindar soporte para el lenguaje Java, qué hicimos y qué planes futuros. Y, por supuesto, el artículo mostrará las primeras pruebas del analizador en proyectos abiertos.

PVS-Studio


Para los desarrolladores de Java que no han oído hablar de la herramienta PVS-Studio antes, les daré una breve descripción.

PVS-Studio es una herramienta para detectar errores y vulnerabilidades potenciales en el código fuente de programas escritos en C, C ++, C # y Java. Se ejecuta en Windows, Linux y macOS.

PVS-Studio realiza un análisis de código estático y genera un informe que ayuda al programador a encontrar y corregir defectos. Para aquellos que estén interesados ​​en cómo exactamente PVS-Studio está buscando errores, les sugiero que lean el artículo " Tecnologías utilizadas en el analizador de código PVS-Studio para encontrar errores y vulnerabilidades potenciales ".

Inicio


Podría presentar una historia inteligente, ya que hemos estado pensando durante dos años sobre qué próximo idioma admitir en PVS-Studio. El hecho de que Java es una opción razonable basada en la gran popularidad de este lenguaje, etc.

Sin embargo, como sucede en la vida, todo se decidió no mediante un análisis en profundidad, sino mediante un experimento :). Sí, estábamos pensando en qué dirección debería desarrollarse más el analizador PVS-Studio. Se consideraron lenguajes de programación como: Java, PHP, Python, JavaScript, IBM RPG. Y estábamos inclinados al lenguaje Java, pero la elección final aún no se ha hecho. Aquellos cuyos ojos están atrapados en un RPG de IBM desconocido, me refiero a esta nota aquí , de la que todo se aclarará.

A finales de 2017, el colega Egor Bredikhin analizó qué bibliotecas preparadas para analizar el código (en otras palabras, analizadores) están disponibles para nuevas direcciones que nos interesan. Y me encontré con varios proyectos para analizar el código Java. Basado en Spoon , rápidamente logró hacer un prototipo de analizador con un par de diagnósticos. Además, quedó claro que podemos usar algunos mecanismos del analizador C ++ con la ayuda de SWIG en el analizador Java. Observamos lo que sucedió y nos dimos cuenta de que nuestro próximo analizador sería para Java.

Gracias a Egor por su trabajo emprendedor y activo realizado por él en el analizador de Java. El desarrollo se describe en el artículo " Desarrollo de un nuevo analizador estático: PVS-Studio Java ".

Competidores?


Existen muchos analizadores de código estático gratuitos y comerciales para Java en el mundo. No tiene sentido enumerarlos a todos en el artículo, y solo dejo un enlace a " Lista de herramientas para el análisis de código estático " (consulte la sección Java y Multi-idioma).

Sin embargo, sé que, en primer lugar, se nos preguntará sobre IntelliJ IDEA, FindBugs y SonarQube (SonarJava).

IDEA IntelliJ

IntelliJ IDEA tiene incorporado un analizador de código estático muy potente. Además, el analizador se está desarrollando y sus autores están monitoreando de cerca nuestras actividades. Con IntelliJ IDEA seremos los más difíciles. No podremos superar IntelliJ IDEA en capacidades de diagnóstico, al menos por ahora. Por lo tanto, intentaremos concentrarnos en nuestras otras ventajas.

El análisis estático en IntelliJ IDEA es, en primer lugar, uno de los chips del entorno de desarrollo, que le impone ciertas restricciones. Somos libres en lo que podemos hacer con nuestro analizador. Por ejemplo, podemos adaptar rápidamente el analizador a las necesidades específicas del cliente. El soporte rápido y profundo es nuestra ventaja competitiva. Nuestros clientes se comunican directamente con los programadores que desarrollan una parte particular de PVS-Studio.

PVS-Studio tiene muchas posibilidades para integrarlo en el ciclo de desarrollo de grandes proyectos antiguos. Esta es la integración con SonarQube . Esta es una supresión masiva de los mensajes del analizador, que le permite comenzar a usar inmediatamente el analizador en un proyecto grande para rastrear errores solo en código nuevo o modificado. PVS-Studio está integrado en el proceso de integración continua. Creo que estas y otras características ayudarán a nuestro analizador a encontrar un lugar bajo el sol en el mundo de Java.

Findbugs

El proyecto FindBugs está abandonado . Pero debe recordarse por la razón de que este es quizás el analizador estático gratuito más conocido del código Java.

El sucesor de FindBugs es el proyecto SpotBugs . Sin embargo, es menos popular, y lo que le sucederá aún no está del todo claro.

En general, creemos que aunque FindBugs fue y sigue siendo extremadamente popular, y también un analizador gratuito, no debemos pensar en ello. Este proyecto es silenciosamente una cosa del pasado.

PD: Por cierto, ahora PVS-Studio también se puede usar de forma gratuita cuando se trabaja con proyectos abiertos.

SonarQube (SonarJava)

Creemos que no competimos con SonarQube, sino que lo complementamos. PVS-Studio se integra con SonarQube, que permite a los desarrolladores encontrar más errores y vulnerabilidades potenciales en sus proyectos. Cómo integrar la herramienta PVS-Studio y otros analizadores en SonarQube, hablamos regularmente en clases magistrales que realizamos en varias conferencias ( ejemplo ).

Cómo iniciar PVS-Studio para Java


Hemos puesto a disposición de los usuarios las formas más populares de integrar el analizador en el sistema de ensamblaje:

  • Plugin para Maven;
  • Plugin para Gradle;
  • Plugin para IntelliJ IDEA

En la etapa de prueba, conocimos a muchos usuarios que tienen sistemas de ensamblaje autoescritos, especialmente en el desarrollo móvil. Les gustó la capacidad de ejecutar el analizador directamente, enumerando las fuentes y classpath.

Puede encontrar información detallada sobre todos los métodos para iniciar el analizador en la página de documentación " Cómo iniciar PVS-Studio Java ".

No podíamos ignorar la plataforma de control de calidad del código SonarQube , tan popular entre los desarrolladores de Java, por lo que agregamos compatibilidad con el lenguaje Java a nuestro complemento SonarQube .

Planes adicionales


Tenemos muchas ideas que necesitan más estudio, pero algunos planes específicos para cualquiera de nuestros analizadores se ven así:

  • Creación de nuevos diagnósticos y refinamiento de los existentes;
  • Desarrollo de análisis de flujo de datos;
  • Mejora de la fiabilidad y la usabilidad.

Podemos encontrar tiempo para adaptar el complemento IntelliJ IDEA para CLion. Hola C ++ a los desarrolladores que leen sobre el analizador de Java :-)

Ejemplos de errores encontrados en proyectos de código abierto


No seré yo si no muestro ningún error encontrado usando el nuevo analizador en el artículo. Podríamos tomar un gran proyecto Java de código abierto y escribir un artículo clásico con análisis de errores, como solemos hacer .

Sin embargo, inmediatamente preveo las preguntas de si podemos encontrar algo en proyectos como IntelliJ IDEA, FindBugs, etc. Por lo tanto, simplemente no tengo una salida, y comenzaré precisamente con estos proyectos. Entonces, decidí revisar y escribir rápidamente algunos ejemplos interesantes de errores de los siguientes proyectos:

  • IntelliJ IDEA Community Edition . Creo que no hay necesidad de explicar por qué se eligió este proyecto :).
  • SpotBugs Como escribí anteriormente, el proyecto FindBugs no se está desarrollando. Así que eche un vistazo al proyecto SpotBugs, que es el sucesor de FindBugs. SpotBugs es un analizador de código Java estático clásico.
  • Algunos de los proyectos de SonarSource, que desarrolla software para el control continuo de la calidad del código. Echa un vistazo a los proyectos SonarQube y SonarJava .

Escribir sobre errores en estos proyectos es una tarea difícil. El hecho es que estos proyectos son de muy alta calidad. En realidad, esto no es sorprendente. Como muestran nuestras observaciones, el código de los analizadores estáticos siempre se prueba y verifica con otras herramientas.

A pesar de todo esto, tengo que comenzar con estos mismos proyectos. No tendré una segunda oportunidad de escribir algo sobre ellos. Estoy seguro de que después del lanzamiento de PVS-Studio para Java, los desarrolladores de estos proyectos pondrán en servicio PVS-Studio y comenzarán a usarlo para verificaciones regulares o al menos periódicas de su código. Por ejemplo, sé que Tagir Valeyev ( lany ), uno de los desarrolladores de JetBrains, que se dedica al analizador de código estático IntelliJ IDEA, ya está jugando con la versión Beta de PVS-Studio en el momento en que escribo el artículo. Ya nos ha escrito unas 15 cartas con informes de errores y recomendaciones. Gracias Tagir!

Afortunadamente, no necesito encontrar tantos errores como sea posible en un proyecto en particular. Ahora mi tarea es mostrar que el analizador PVS-Studio para Java no apareció en vano y podrá reponer la línea de otras herramientas diseñadas para mejorar la calidad del código. Eché un vistazo a los informes del analizador y escribí algunos errores que me parecieron interesantes. Siempre que fue posible, traté de escribir errores de varios tipos. Veamos que pasó.

IntelliJ IDEA Integer Division


private static boolean checkSentenceCapitalization(@NotNull String value) { List<String> words = StringUtil.split(value, " "); .... int capitalized = 1; .... return capitalized / words.size() < 0.2; // allow reasonable amount of // capitalized words } 

Advertencia de PVS-Studio: V6011 [CWE-682] El literal '0.2' del tipo 'doble' se compara con un valor del tipo 'int'. TitleCapitalizationInspection.java 169

Según lo previsto, una función debería ser verdadera si menos del 20% de las palabras comienzan con mayúscula. De hecho, la comprobación no funciona, ya que se produce la división de enteros. Como resultado de la división, solo se pueden obtener dos valores: 0 o 1.

La función devolverá un valor falso solo si todas las palabras comienzan con una letra mayúscula. En todos los demás casos, la división producirá 0 y la función devolverá el valor verdadero.

IntelliJ IDEA Ciclo sospechoso


 public int findPreviousIndex(int current) { int count = myPainter.getErrorStripeCount(); int foundIndex = -1; int foundLayer = 0; if (0 <= current && current < count) { current--; for (int index = count - 1; index >= 0; index++) { // <= int layer = getLayer(index); if (layer > foundLayer) { foundIndex = index; foundLayer = layer; } } .... } 

Advertencia de PVS-Studio: V6007 [CWE-571] La expresión 'index> = 0' siempre es verdadera. Updater.java 184

Primero, observe la condición (0 <= actual && actual <cuenta) . Solo se ejecuta si el valor de la variable de conteo es mayor que 0.

Ahora mira el bucle:

 for (int index = count - 1; index >= 0; index++) 

El índice variable se inicializa mediante la expresión count - 1 . Como la variable de conteo es mayor que 0, el valor inicial de la variable de índice siempre es mayor o igual que 0. Resulta que el ciclo se ejecutará hasta que la variable de índice se desborde.

Lo más probable es que esto sea solo un error tipográfico y el incremento no debe ejecutarse, sino la disminución de la variable:

 for (int index = count - 1; index >= 0; index--) 

IDEA IntelliJ, copiar y pegar


 @NonNls public static final String BEFORE_STR_OLD = "before:"; @NonNls public static final String AFTER_STR_OLD = "after:"; private static boolean isBeforeOrAfterKeyword(String str, boolean trimKeyword) { return (trimKeyword ? LoadingOrder.BEFORE_STR.trim() : LoadingOrder.BEFORE_STR).equalsIgnoreCase(str) || (trimKeyword ? LoadingOrder.AFTER_STR.trim() : LoadingOrder.AFTER_STR).equalsIgnoreCase(str) || LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase(str) || // <= LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase(str); // <= } 

Advertencia de PVS-Studio: V6001 [CWE-570] Hay subexpresiones idénticas 'LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase (str)' a la izquierda y a la derecha de '||' operador Líneas de verificación: 127, 128. ExtensionOrderConverter.java 127

Buen efecto antiguo de la última línea . El programador se apresuró y, al multiplicar una línea de código, olvidó arreglarlo. Como resultado, el doble de la cadena str se compara con BEFORE_STR_OLD . Lo más probable es que una de las comparaciones sea con AFTER_STR_OLD .

IntelliJ IDEA typo


 public synchronized boolean isIdentifier(@NotNull String name, final Project project) { if (!StringUtil.startsWithChar(name,'\'') && !StringUtil.startsWithChar(name,'\"')) { name = "\"" + name; } if (!StringUtil.endsWithChar(name,'"') && !StringUtil.endsWithChar(name,'\"')) { name += "\""; } .... } 

Advertencia de PVS-Studio: V6001 [CWE-571] Hay subexpresiones idénticas '! StringUtil.endsWithChar (nombre,' "')' a la izquierda y a la derecha del operador '&&'. JsonNamesValidator.java 27

Este fragmento de código verifica que el nombre esté entre comillas simples o dobles. Si este no es el caso, las comillas dobles se agregan automáticamente.

Debido a un error tipográfico, el final del nombre solo se verifica para las comillas dobles. Como resultado, el nombre tomado entre comillas simples no se procesará correctamente.

Nombre

 'Abcd' 

debido a la adición de comillas dobles adicionales se convertirá en:

 'Abcd'" 

IntelliJ IDEA, protección de desbordamiento de matriz incorrecta


 static Context parse(....) { .... for (int i = offset; i < endOffset; i++) { char c = text.charAt(i); if (c == '<' && i < endOffset && text.charAt(i + 1) == '/' && startTag != null && CharArrayUtil.regionMatches(text, i + 2, endOffset, startTag)) { endTagStartOffset = i; break; } } .... } 

Advertencia de PVS-Studio: V6007 [CWE-571] La expresión 'i <endOffset' siempre es verdadera. EnterAfterJavadocTagHandler.java 183

La subexpresión i <endOffset en la condición de la instrucción if no tiene sentido. La variable i siempre es menor que endOffset , como se deduce de la condición para ejecutar el bucle.

Lo más probable es que el programador quisiera protegerse para no salirse de la línea al llamar a funciones:

  • text.charAt (i + 1)
  • CharArrayUtil.regionMatches (texto, i + 2, endOffset, startTag)

En este caso, la subexpresión para verificar el índice debería ser así: i <endOffset - 2 .

IntelliJ IDEA Repeat Check


 public static String generateWarningMessage(....) { .... if (buffer.length() > 0) { if (buffer.length() > 0) { buffer.append(" ").append( IdeBundle.message("prompt.delete.and")).append(" "); } } .... } 

Advertencia de PVS-Studio: V6007 [CWE-571] La expresión 'buffer.length ()> 0' siempre es verdadera. DeleteUtil.java 62

Esto puede ser un código redundante inofensivo o un error grave.

Si una verificación duplicada apareció por casualidad, por ejemplo, durante la refactorización, entonces no hay nada de malo en eso. La segunda verificación simplemente se puede eliminar.

Pero otro escenario es posible. La segunda verificación debe ser completamente diferente y el código no se comporta como se esperaba. Entonces esto es un verdadero error.

Nota Por cierto, hay muchas comprobaciones redundantes diferentes. Además, a menudo se ve que esto no es un error. Sin embargo, los mensajes del analizador tampoco se pueden llamar falsos positivos. Para aclarar, aquí hay un ejemplo, también tomado de IntelliJ IDEA:

 private static boolean isMultiline(PsiElement element) { String text = element.getText(); return text.contains("\n") || text.contains("\r") || text.contains("\r\n"); } 

El analizador dice que la función text.contains ("\ r \ n") siempre devuelve falso. De hecho, si no se encuentran el símbolo "\ n" y "\ r", entonces no tiene sentido buscar "\ r \ n". Esto no es un error, y el código es malo solo porque funciona un poco más lento, realizando una búsqueda sin sentido de una subcadena.

Cómo lidiar con dicho código, en cada caso, corresponde a los programadores decidir. Al escribir artículos, por regla general, simplemente no presto atención a dicho código.

IDEA IntelliJ, algo está mal


 public boolean satisfiedBy(@NotNull PsiElement element) { .... @NonNls final String text = expression.getText().replaceAll("_", ""); if (text == null || text.length() < 2) { return false; } if ("0".equals(text) || "0L".equals(text) || "0l".equals(text)) { return false; } return text.charAt(0) == '0'; } 

Advertencia de PVS-Studio: V6007 [CWE-570] La expresión '"0" .equals (texto)' siempre es falsa. ConvertIntegerToDecimalPredicate.java 46

Este código definitivamente contiene un error lógico. Pero me resulta difícil decir qué quería comprobar el programador y cómo corregir el defecto. Por lo tanto, aquí solo señalaré una verificación sin sentido.

Al principio, se verifica que la cadena debe contener al menos dos caracteres. Si no es así, la función devuelve falso .

La siguiente es una comprobación de "0" .equals (texto) . No tiene sentido, ya que una cadena no puede contener solo un carácter.

En general, algo está mal aquí y el código debe repararse.

SpotBugs (sucesor de FindBugs), error de límite de iteración


 public static String getXMLType(@WillNotClose InputStream in) throws IOException { .... String s; int count = 0; while (count < 4) { s = r.readLine(); if (s == null) { break; } Matcher m = tag.matcher(s); if (m.find()) { return m.group(1); } } throw new IOException("Didn't find xml tag"); .... } 

Advertencia de PVS-Studio: V6007 [CWE-571] La expresión 'cuenta <4' siempre es verdadera. Util.java 394

Según lo planeado, la búsqueda de la etiqueta xml debe llevarse a cabo solo en las primeras cuatro líneas del archivo. Pero debido al hecho de que olvidaron aumentar el recuento variable, se leerá todo el archivo.

En primer lugar, esto puede resultar una operación muy lenta y, en segundo lugar, en el medio del archivo, se puede encontrar algo que se interpretará como una etiqueta xml, pero no será así.

SpotBugs (sucesor de FindBugs), sobrescribiendo valores


 private void reportBug() { int priority = LOW_PRIORITY; String pattern = "NS_NON_SHORT_CIRCUIT"; if (sawDangerOld) { if (sawNullTestVeryOld) { priority = HIGH_PRIORITY; // <= } if (sawMethodCallOld || sawNumericTestVeryOld && sawArrayDangerOld) { priority = HIGH_PRIORITY; // <= pattern = "NS_DANGEROUS_NON_SHORT_CIRCUIT"; } else { priority = NORMAL_PRIORITY; // <= } } bugAccumulator.accumulateBug( new BugInstance(this, pattern, priority).addClassAndMethod(this), this); } 

Advertencia de PVS-Studio: V6021 [CWE-563] El valor se asigna a la variable 'prioridad' pero no se utiliza. FindNonShortCircuit.java 197

El valor de la variable de prioridad se establece dependiendo del valor de la variable sawNullTestVeryOld . Sin embargo, esto no juega ningún papel. Además, a la variable de prioridad se le asignará un valor diferente en cualquier caso. Un error obvio en la lógica de la función.

SonarQube, Copy-Paste


 public class RuleDto { .... private final RuleDefinitionDto definition; private final RuleMetadataDto metadata; .... private void setUpdatedAtFromDefinition(@Nullable Long updatedAt) { if (updatedAt != null && updatedAt > definition.getUpdatedAt()) { setUpdatedAt(updatedAt); } } private void setUpdatedAtFromMetadata(@Nullable Long updatedAt) { if (updatedAt != null && updatedAt > definition.getUpdatedAt()) { setUpdatedAt(updatedAt); } } .... } 

PVS-Studio: V6032 Es extraño que el cuerpo del método 'setUpdatedAtFromDefinition' sea completamente equivalente al cuerpo de otro método 'setUpdatedAtFromMetadata'. Líneas de verificación: 396, 405. RuleDto.java 396

El método setUpdatedAtFromMetadata usa el campo de definición . Lo más probable es que se use el campo de metadatos . Esto es muy similar a las consecuencias del fallido Copy-Paste.

SonarJava, duplicados en inicialización de mapa


 private final Map<JavaPunctuator, Tree.Kind> assignmentOperators = Maps.newEnumMap(JavaPunctuator.class); public KindMaps() { .... assignmentOperators.put(JavaPunctuator.PLUSEQU, Tree.Kind.PLUS_ASSIGNMENT); .... assignmentOperators.put(JavaPunctuator.PLUSEQU, Tree.Kind.PLUS_ASSIGNMENT); .... } 

Advertencia de PVS-Studio: V6033 [CWE-462] Ya se ha agregado un elemento con la misma clave 'JavaPunctuator.PLUSEQU'. Líneas de verificación: 104, 100. KindMaps.java 104

El mismo par clave-valor se coloca dos veces en la tarjeta. Lo más probable es que esto haya sido desatento, y de hecho no hay ningún error real. Sin embargo, en cualquier caso, es necesario verificar este código, ya que es posible que haya olvidado agregar algún otro par.

Conclusión


¿Pero qué conclusión puede haber? ¡Invito a todos, sin demora, a descargar PVS-Studio e intentar probar sus proyectos de trabajo en Java! Descargar PVS-Studio .

Gracias a todos por su atención. Espero que pronto deleitemos a los lectores con una serie de artículos dedicados a verificar varios proyectos Java abiertos.



Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Andrey Karpov. PVS-Studio para Java .

Source: https://habr.com/ru/post/es436434/


All Articles