Preserve Base64 imports and receiver evaluation - #1196
Draft
martinfrancois wants to merge 1 commit into
Draft
Conversation
…eceiver addImportWhenNoCoderVariablePresent pins that no import java.util.Base64 is added when no coder variable exists for ChangeType to retype, so the output does not compile. retainMethodCallReceiver pins that a non-identifier receiver such as encoder() is silently dropped from the rewritten call. Both are marked @ExpectedToFail; both defects are disclosed in openrewrite#1195.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Suggested review order: 40 of 52 (Score: 2)
Review first: openrewrite/rewrite-static-analysis#995
What's changed?
Adds 2 known-failing tests to
UseJavaUtilBase64Testthat reproduce two defects in theUseJavaUtilBase64recipe:addImportWhenNoCoderVariablePresent(the migrated file is missing thejava.util.Base64import and does not compile) andretainMethodCallReceiver(a method call receiver is silently dropped). No recipe code changes. The tests are marked@ExpectedToFailso the suite stays green; removing the annotation shows the failure.What's your motivation?
Recipe:
org.openrewrite.java.migrate.UseJavaUtilBase64.Case 1: add the
Base64importBefore
Take a file that uses
BASE64Encoderinline, with no coder variable anywhere:Actual after the recipe
Using current main. the call becomes
return Base64.getEncoder().encodeToString(bBytes);but noimport java.util.Base64;is added. Compiling that exact output with javac fails withcannot find symbol: variable Base64. The recipe'sJavaTemplatedeclares.imports("java.util.Base64")but never callsmaybeAddImport; the import only appears as a side effect ofChangeTyperetyping aBASE64EncoderorBASE64Decodervariable, and this file has none. After the recipe runs, the code should keep the same rewrite and also gain the import.Expected after the recipe
The rewritten call MUST be retained and
import java.util.Base64;MUST be added.Case 2: preserve a non-identifier receiver
Before
Take a call whose receiver is itself a method call:
Actual after the recipe
Using current main:
return Base64.getEncoder().encodeToString(bBytes);.Expected after the recipe
return encoder().encodeToString(bBytes);Theencoder()receiver is gone, so its evaluation and any side effects are lost. In the same output,ChangeTypedoes retype the helper toBase64.Encoder encoder() { return Base64.getEncoder(); }, so the expected result is simplyreturn encoder().encodeToString(bBytes);. The cause is thatvisitMethodInvocationrestores the original receiver only whenmethod.getSelect() instanceof J.Identifier; any other receiver (method call, field access, new-class) is discarded.Found while preparing #1195, which adds a guard to this recipe. Its description discloses both cases, but its guard fixes neither: both inputs above still pass the guard and are migrated incorrectly. These two tests fail on current main whether or not #1195 is merged.
Affected code in real projects
shuzheng/zhengAESUtil.java: AES utility that encodes cipher output with an inlinenew BASE64Encoder().encode(byteAES)and decodes with an inlinenew BASE64Decoder().decodeBuffer(content)(line 105); the file declares no coder variable, so the recipe from main rewrites both calls tojava.util.Base64factory calls without ever addingimport java.util.Base64;, and the class no longer compiles.paascloud/paascloud-masterHttpAesUtil.java: AES helper with the same inline shape,new BASE64Encoder().encode(bytes)andnew BASE64Decoder().decodeBuffer(contentParam)(line 86) and no coder variable; the recipe from main rewrites both calls but adds nojava.util.Base64import, so the migrated file does not compile.DTStack/TaierDtStringUtil.java: compresses a string and encodes it with an inlinenew sun.misc.BASE64Encoder().encodeBuffer(compressed); the file has no coder variable and nojava.util.Base64import, so the rewrite from main toBase64.getEncoder().encodeToString(compressed)leaves theBase64reference unresolved and the file does not compile.Anything in particular you'd like reviewers to focus on?
I think both are genuine bugs: the first produces code that does not compile, and the second silently loses a method call and its side effects. If you agree this should change, I would gladly prepare the fix. If this behavior is intended, feel free to close this and I know it is settled.
Any additional context
Pre-existing tests changed: None.
Related open PR of mine touching the same recipe: #1195. It does not fix these two cases. Before this change the class had 3 tests; with the additions it has 5. A fresh run reports tests=5, failures=0, errors=0, skipped=2; the 2 skipped are the new
@ExpectedToFailtests, which fail when the annotation is removed.This reproduction was prepared with AI assistance (Claude Code). I reviewed the tests and this description.
The added reproduction tests and the existing suite together cover changed and unchanged behavior. The known-failing tests remain disabled until implementation. The formatter run was calibrated per file; untouched lines were not reformatted.
Checklist
./gradlew buildlocally, and committed any resulting changes torecipes.csv