Skip to content

Recipe NoValueOfOnStringType incorrectly removes String.valueOf on null-initialized constants, introducing runtime NPE #1009

Description

@jbessels

What version of OpenRewrite are you using?

rewrite-recipe-bom 3.37.0
rrewrite-maven-plugin v6.46.1

What is the smallest, simplest way to reproduce the problem?

 private static final String INCIDENT_OPEN_TASK = null;

 private String replaceIncidentGenericDummyValuesWithRealValues(String templateBody) {
        return templateBody
                .replace("$INCIDENT_OPEN_TASK", String.valueOf(INCIDENT_OPEN_TASK))
                // changes above code into the following
                // .replace("$INCIDENT_OPEN_TASK", INCIDENT_OPEN_TASK)
             
    }

Its part of our e2e/integration test repo and setting INCIDENT_OPEN_TASK to null is part of the test. The recipe does not check if the constant is null. If so do not remove the String.valueOf(). IntelliJ also gives a warning on the changed code: Passing 'null' argument to parameter annotated as non-null.

Boils down to

  • INCIDENT_OPEN_TASK: Throws NullPointerException if null.
  • String.valueOf(INCIDENT_OPEN_TASK): Converts null to the literal string "null", replacing the placeholder without crashing.

Test code generated by Ai and not tested.

  package org.openrewrite.staticanalysis;

 import org.junit.jupiter.api.Test;
 import org.openrewrite.test.RecipeSpec;
 import org.openrewrite.test.RewriteTest;

 import static org.openrewrite.java.Assertions.java;

 class SimplifyStringValueOfTest implements RewriteTest {

     @Override
     public void defaults(final RecipeSpec spec) {
         // Enforce the recipe under test
         spec.recipe(new SimplifyStringValueOf());
     }

     @Test
     void simplifyStringValueOf_whenConstantIsNull_doesNotChange() {
         rewriteRun(
             java(
                 """
                 class Test {
                     private static final String INCIDENT_OPEN_TASK = null;

                     private String replaceIncidentGenericDummyValuesWithRealValues(final String templateBody) {
                         return templateBody
                             .replace("$INCIDENT_OPEN_TASK", String.valueOf(INCIDENT_OPEN_TASK));
                     }
                 }
                 """
             )
         );
     }
 }

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    Status
    In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions