IDEA-201515 IDEA suggests to use Java 11 writeString which is buggy for UTF-8 charset (until 11.0.2)

This commit is contained in:
Tagir Valeev
2018-11-01 18:20:32 +07:00
parent c508396a01
commit 8b8b7d30ed
7 changed files with 164 additions and 12 deletions
@@ -2,6 +2,7 @@
package com.intellij.codeInspection;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
@@ -22,11 +23,14 @@ public class ReadWriteStringCanBeUsedInspection extends AbstractBaseJavaLocalIns
.parameterTypes("java.nio.file.Path");
private static final CallMatcher STRING_GET_BYTES = CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_STRING, "getBytes")
.parameterTypes("java.nio.charset.Charset");
private static final CallMatcher CHARSET_FOR_NAME = CallMatcher.staticCall("java.nio.charset.Charset", "forName")
.parameterTypes(CommonClassNames.JAVA_LANG_STRING);
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
if (!PsiUtil.isLanguageLevel11OrHigher(holder.getFile())) {
LanguageLevel level = PsiUtil.getLanguageLevel(holder.getFile());
if (level.isLessThan(LanguageLevel.JDK_11)) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JavaElementVisitor() {
@@ -35,8 +39,15 @@ public class ReadWriteStringCanBeUsedInspection extends AbstractBaseJavaLocalIns
if (FILES_WRITE.test(call)) {
PsiMethodCallExpression bytesExpression = tryCast(ExpressionUtils.resolveExpression(call.getArgumentList().getExpressions()[1]), PsiMethodCallExpression.class);
if (STRING_GET_BYTES.test(bytesExpression) && bytesExpression.getMethodExpression().getQualifierExpression() != null) {
holder.registerProblem(call, "Can be replaced with 'Files.writeString()'",
new ReplaceWithWriteStringFix());
// Do not suggest for UTF-8 before Java 12 due to JDK-8209576
ProblemHighlightType highlight;
String message = "Can be replaced with 'Files.writeString()'";
if (level.isAtLeast(LanguageLevel.JDK_12) || isNonUtf8Charset(bytesExpression.getArgumentList().getExpressions()[0])) {
highlight = ProblemHighlightType.WARNING;
} else {
highlight = ProblemHighlightType.INFORMATION;
}
holder.registerProblem(call, message, highlight, new ReplaceWithWriteStringFix(highlight == ProblemHighlightType.INFORMATION));
}
} else if (FILES_READ_ALL_BYTES.test(call)) {
PsiExpressionList expressionList = tryCast(PsiUtil.skipParenthesizedExprUp(call.getParent()), PsiExpressionList.class);
@@ -58,6 +69,7 @@ public class ReadWriteStringCanBeUsedInspection extends AbstractBaseJavaLocalIns
}
private static class ReplaceWithReadStringFix implements LocalQuickFix {
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
@@ -87,6 +99,19 @@ public class ReadWriteStringCanBeUsedInspection extends AbstractBaseJavaLocalIns
}
private static class ReplaceWithWriteStringFix implements LocalQuickFix {
private final boolean myMayNotWork;
private ReplaceWithWriteStringFix(boolean mayNotWork) {
myMayNotWork = mayNotWork;
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getName() {
return myMayNotWork ? getFamilyName() + " (may not work before JDK 11.0.2)" : getFamilyName();
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
@@ -122,16 +147,50 @@ public class ReadWriteStringCanBeUsedInspection extends AbstractBaseJavaLocalIns
}
}
}
static boolean isUtf8Charset(PsiExpression expression) {
PsiReferenceExpression ref = tryCast(PsiUtil.skipParenthesizedExprDown(expression), PsiReferenceExpression.class);
if (ref == null) return false;
if (!"UTF_8".equals(ref.getReferenceName())) {
return false;
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (expression instanceof PsiReferenceExpression) {
PsiReferenceExpression ref = (PsiReferenceExpression)expression;
if (!"UTF_8".equals(ref.getReferenceName())) {
return false;
}
PsiField target = tryCast(ref.resolve(), PsiField.class);
return target != null &&
target.getContainingClass() != null &&
"java.nio.charset.StandardCharsets".equals(target.getContainingClass().getQualifiedName());
}
PsiField target = tryCast(ref.resolve(), PsiField.class);
return target != null &&
target.getContainingClass() != null &&
"java.nio.charset.StandardCharsets".equals(target.getContainingClass().getQualifiedName());
if (expression instanceof PsiMethodCallExpression && CHARSET_FOR_NAME.test((PsiMethodCallExpression)expression)) {
PsiExpression arg = ((PsiMethodCallExpression)expression).getArgumentList().getExpressions()[0];
Object value = ExpressionUtils.computeConstantExpression(arg);
return value instanceof String && ((String)value).equalsIgnoreCase("utf-8");
}
return false;
}
static boolean isNonUtf8Charset(PsiExpression expression) {
for (int i = 0; i < 3; i++) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (expression instanceof PsiReferenceExpression) {
PsiField target = tryCast(((PsiReferenceExpression)expression).resolve(), PsiField.class);
if (target != null) {
if ("UTF_8".equals(target.getName())) return false;
if (target.getContainingClass() != null &&
"java.nio.charset.StandardCharsets".equals(target.getContainingClass().getQualifiedName())) {
return true;
}
if (target.hasModifierProperty(PsiModifier.FINAL)) {
expression = target.getInitializer();
continue;
}
}
}
if (expression instanceof PsiMethodCallExpression && CHARSET_FOR_NAME.test((PsiMethodCallExpression)expression)) {
PsiExpression arg = ((PsiMethodCallExpression)expression).getArgumentList().getExpressions()[0];
Object value = ExpressionUtils.computeConstantExpression(arg);
return value instanceof String && !((String)value).equalsIgnoreCase("utf-8");
}
break;
}
return false;
}
}
@@ -0,0 +1,15 @@
// "Replace with 'Files.writeString()'" "WARNING"
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Example {
private static final Charset CHARSET = Charset.forName("UTF-16");
void test1(String str) throws IOException {
Files.writeString(Paths.get("/etc/passwd"), str, CHARSET, StandardOpenOption.CREATE);
}
}
@@ -0,0 +1,15 @@
// "Replace with 'Files.writeString()'" "WARNING"
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Example {
private static final Charset CHARSET = StandardCharsets.UTF_16;
void test1(String str) throws IOException {
Files.writeString(Paths.get("/etc/passwd"), str, CHARSET, StandardOpenOption.CREATE);
}
}
@@ -0,0 +1,15 @@
// "Replace with 'Files.writeString()' (may not work before JDK 11.0.2)" "INFORMATION"
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Example {
private static final Charset CHARSET = StandardCharsets.UTF_8;
void test1(String str) throws IOException {
Files.writeString(Paths.get("/etc/passwd"), str, CHARSET, StandardOpenOption.CREATE);
}
}
@@ -0,0 +1,16 @@
// "Replace with 'Files.writeString()'" "WARNING"
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Example {
private static final Charset CHARSET = Charset.forName("UTF-16");
void test1(String str) throws IOException {
byte[] bytes = str.getBytes(CHARSET);
Files.write<caret>(Paths.get("/etc/passwd"), bytes, StandardOpenOption.CREATE);
}
}
@@ -0,0 +1,16 @@
// "Replace with 'Files.writeString()'" "WARNING"
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Example {
private static final Charset CHARSET = StandardCharsets.UTF_16;
void test1(String str) throws IOException {
byte[] bytes = str.getBytes(CHARSET);
Files.write<caret>(Paths.get("/etc/passwd"), bytes, StandardOpenOption.CREATE);
}
}
@@ -0,0 +1,16 @@
// "Replace with 'Files.writeString()' (may not work before JDK 11.0.2)" "INFORMATION"
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Example {
private static final Charset CHARSET = StandardCharsets.UTF_8;
void test1(String str) throws IOException {
byte[] bytes = str.getBytes(CHARSET);
Files.write<caret>(Paths.get("/etc/passwd"), bytes, StandardOpenOption.CREATE);
}
}