Error Prone Documentation

repository·master·Indexed 27 days ago

https://github.com/google/error-prone

Error Prone is a static analysis tool for Java that catches common programming mistakes at compile-time by hooking into the Java compiler. It supports integration with Bazel, Maven, Ant, and Gradle. The tool provides a variety of bug patterns to identify logic errors, security vulnerabilities (such as Fragment Injection in PreferenceActivity), and Android-specific issues like unsafe Bundle deserialization casts and Binder identity management.

Tokens
107.6K
Snippets
261
Records
601
Agent score
90%

What's inside Error Prone

  1. Overview of Error Prone

    master
    Error Prone is a static analysis tool for Java designed to catch common programming mistakes at compile-time. It integrates into the compilation process to identify bugs that standard compilers might miss, providing descriptive error messages that point to the specific cause of the issue.
  2. Understand the MoreThanOneInjectableConstructor bug pattern

    master

    The MoreThanOneInjectableConstructor bug pattern identifies classes that have multiple constructors annotated with @Inject.

    Injection frameworks use the @Inject annotation to determine which constructor to use when constructing an object. If a class contains more than one constructor with this annotation, the framework cannot reliably choose between them, which can lead to unpredictable behavior or runtime errors.

  3. Identify insecure cryptographic algorithm usage with InsecureCryptoUsage

    master

    The InsecureCryptoUsage checker flags three specific classes of cryptographic vulnerabilities in Java code:

    1. Insecure Cipher Configurations:

      • Creating javax.crypto.Cipher instances using default settings or the insecure ECB mode.
      • Note that Cipher.getInstance(AES) defaults to ECB mode in Java.
      • Dynamically constructed transformation strings are flagged to prevent concealing ECB mode.
      • IES-based cipher algorithms are flagged because their implementations use ECB mode.
    2. Insecure Diffie-Hellman (DH) Implementations:

      • Flags any operation involving the Diffie-Hellman protocol on prime fields, as these are often exploitable.
      • Recommendation: Use Elliptic Curve Diffie-Hellman (ECDH) instead, as it is considered secure.
    3. Insecure DSA Signatures:

      • Flags all cryptographic operations involving DSA (Digital Signature Algorithm) due to vulnerabilities in certain library configurations that accept invalid signatures.
  4. Understand the MissingRefasterAnnotation bug pattern

    master

    The MissingRefasterAnnotation bug pattern identifies Refaster templates that contain methods missing required annotations. A valid Refaster template typically consists of multiple methods where each method is decorated with an annotation (such as @BeforeTemplate or @AfterTemplate). If a method within a template class lacks an annotation, it is flagged as an oversight.

    static final class MethodLacksBeforeTemplateAnnotation {
      @BeforeTemplate
      boolean before1(String string) {
        return string.equals("");
      }
    
      // @BeforeTemplate is missing
      boolean before2(String string) {
        return string.length() == 0;
      }
    
      @AfterTemplate
      @AlsoNegation
      boolean after(String string) {
        return string.isEmpty();
      }
    }
  5. Understand the BanJNDI bug pattern

    master

    The BanJNDI bug pattern identifies and bans usage of Java JDK APIs that can result in the deserialization of unsafe objects via the Java Naming and Directory Interface (JNDI).

    JNDI allows Java objects to be serialized and deserialized over the wire. If an application performs a JNDI lookup over a transport protocol, a remote server can execute arbitrary attacker-defined code.

    This checker works in two ways:

    1. Direct Ban: It bans specific high-risk APIs.
    2. Indirect Ban: It bans specific methods (lookup(), bind(), rebind(), getAttributes(), modifyAttributes(), createSubcontext(), getSchema(), getSchemaClassDefinition(), and search()) on any subclass or implementer of javax.naming.Context. This prevents attackers from bypassing the check by casting a vulnerable implementation to the base Context type.
  6. Identify SizeGreaterThanOrEqualsZero bug pattern

    master
    The SizeGreaterThanOrEqualsZero bug pattern detects logic errors where a developer checks if the size of an array or collection is greater than or equal to 0. Since the size of a collection is always at least 0, this check is always true and typically indicates a mistake where the developer intended to check if the collection was non-empty (i.e., size > 0).
  7. Understand the RedundantNullCheck bug pattern

    master

    The RedundantNullCheck pattern flags null checks (e.g., x == null or x != null) performed on expressions that are statically determined to be non-null. This occurs when:

    1. Language semantics guarantee the value is non-null.
    2. The code is within a @NullMarked scope and the type is not explicitly annotated with @Nullable.

    This helps clean up code and reinforces the contracts provided by nullness annotations.

    import org.jspecify.annotations.NullMarked;
    import org.jspecify.annotations.Nullable;
    
    @NullMarked
    class MyClass {
      void process(String definitelyNonNull) {
        // BUG: RedundantNullCheck
        if (definitelyNonNull == null) {
          System.out.println("This will never happen");
        }
      }
    
      String getString() {
        return "hello";
      }
    
      @Nullable String getNullableString() {
        return null;
      }
    
      void anotherMethod() {
        String s = getString();
        // BUG: RedundantNullCheck (s is known to be non-null)
        if (s == null) {
          System.out.println("Redundant check");
        }
    
        String nullableStr = getNullableString();
        if (nullableStr == null) { // This check is NOT redundant
          System.out.println("Nullable string might be null");
        }
      }
    }
  8. Refactor null checks to use requireNonNull

    master

    To improve code readability and potentially gain minor performance benefits, replace manual null checks that throw a NullPointerException with java.util.Objects.requireNonNull(value).

    Manual pattern to avoid:

    if (value == null) {
      throw new NullPointerException(...);
    }

    Recommended pattern:

    java.util.Objects.requireNonNull(value);

    Benefits of requireNonNull:

    • Readability: It is more concise and expressive.
    • Performance: It is annotated with @ForceInline and receives special JVM treatment to ensure it is inlined into equivalent if/throw code. It also produces slightly smaller bytecode, which can assist JIT inlining decisions.
  9. Use Pattern Matching for instanceof

    master

    Error Prone identifies code patterns where instanceof is used followed by an explicit cast to the same type. You can refactor these patterns to use Java's pattern matching for instanceof to make the code more concise and readable. This eliminates the need for a separate casting step.

    // Refactor this:
    void handle(Object o) {
      if (o instanceof Point) {
        Point point = (Point) o;
        handlePoint(point.x(), point.y());
      } else if (o instanceof String) {
        String s = (String) o;
        handleString(s);
      }
    }
    
    // To this:
    void handle(Object o) {
      if (o instanceof Point(int x, int y)) {
        handlePoint(x, y);
      } else if (o instanceof String s) {
        handleString(s);
      }
    }
  10. Avoid unbounded work queues in ThreadPoolExecutor

    master

    The ErroneousThreadPoolConstructorChecker bug pattern identifies cases where a ThreadPoolExecutor is constructed with an unbounded workQueue. When the queue is unbounded, the pool size will never exceed the corePoolSize, rendering the maximumPoolSize parameter ineffective.

    To fix this, you should either:

    1. Set corePoolSize equal to maximumPoolSize if you intend to use an unbounded queue.
    2. Use a bounded queue (e.g., new LinkedBlockingQueue<>(capacity)) to allow the pool to scale up to the maximumPoolSize when the queue fills up.
    // Bad: maximumPoolSize is ignored because the queue is unbounded
    new ThreadPoolExecutor(
        /* corePoolSize= */ 1,
        /* maximumPoolSize= */ 10,
        /* keepAliveTime= */ 60,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>());
    
    // Good: corePoolSize and maximumPoolSize are equal
    new ThreadPoolExecutor(
        /* corePoolSize= */ 10,
        /* maximumPoolSize= */ 10,
        /* keepAliveTime= */ 60,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>());
    
    // Good: Using a bounded queue allows scaling to maximumPoolSize
    new ThreadPoolExecutor(
        /* corePoolSize= */ 1,
        /* maximumPoolSize= */ 10,
        /* keepAliveTime= */ 60,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(QUEUE_CAPACITY));