diff --git a/check_api/src/main/java/com/google/errorprone/util/SourceVersion.java b/check_api/src/main/java/com/google/errorprone/util/SourceVersion.java index 7709b10692d..86ec6024660 100644 --- a/check_api/src/main/java/com/google/errorprone/util/SourceVersion.java +++ b/check_api/src/main/java/com/google/errorprone/util/SourceVersion.java @@ -56,7 +56,8 @@ public static boolean supportsInstanceMainMethods(Context context) { return sourceIsAtLeast(context, 25); } - private static boolean sourceIsAtLeast(Context context, int version) { + /** Returns true if the compiler source version level is at least the given version. */ + public static boolean sourceIsAtLeast(Context context, int version) { Source lowerBound = Source.lookup(Integer.toString(version)); return lowerBound != null && Source.instance(context).compareTo(lowerBound) >= 0; } diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/JdkObsolete.java b/core/src/main/java/com/google/errorprone/bugpatterns/JdkObsolete.java index a722fd69a6a..a2c17df08f2 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/JdkObsolete.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/JdkObsolete.java @@ -46,6 +46,7 @@ import com.google.errorprone.matchers.Description; import com.google.errorprone.matchers.Matcher; import com.google.errorprone.util.ASTHelpers; +import com.google.errorprone.util.SourceVersion; import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; @@ -157,9 +158,9 @@ Optional fix(Tree tree, VisitorState state) { .collect(toImmutableMap(Obsolete::qualifiedName, x -> x)); private record ObsoleteApi( - Matcher matcher, String message, int androidMinSdkVersion) { + Matcher matcher, String message, int androidMinSdkVersion, int jdkVersion) { ObsoleteApi(Matcher matcher, String message) { - this(matcher, message, 1); + this(matcher, message, 1, 1); } } @@ -189,7 +190,8 @@ private record ObsoleteApi( .named("toString") .withParameters("java.lang.String"), "Use ByteArrayOutputStream.toString(Charset) instead.", - 33), + 33, + 10), new ObsoleteApi( instanceMethod() .onExactClass("java.lang.String") @@ -202,35 +204,40 @@ private record ObsoleteApi( .named("decode") .withParameters("java.lang.String", "java.lang.String"), "Use URLDecoder.decode(String, Charset) instead.", - 33), + 33, + 10), new ObsoleteApi( staticMethod() .onClass("java.net.URLEncoder") .named("encode") .withParameters("java.lang.String", "java.lang.String"), "Use URLEncoder.encode(String, Charset) instead.", - 33), + 33, + 10), new ObsoleteApi( staticMethod() .onClass("java.nio.channels.Channels") .named("newReader") .withParameters("java.nio.channels.ReadableByteChannel", "java.lang.String"), "Use Channels.newReader(ReadableByteChannel, Charset) instead.", - 33), + 33, + 10), new ObsoleteApi( staticMethod() .onClass("java.nio.channels.Channels") .named("newWriter") .withParameters("java.nio.channels.WritableByteChannel", "java.lang.String"), "Use Channels.newWriter(WritableByteChannel, Charset) instead.", - 33), + 33, + 10), new ObsoleteApi( instanceMethod() .onExactClass("java.util.Properties") .named("storeToXML") .withParameters("java.io.OutputStream", "java.lang.String", "java.lang.String"), "Use Properties.storeToXML(OutputStream, String, Charset) instead.", - 35), + 35, + 10), new ObsoleteApi( staticMethod() .onClass(IO_UTILS) @@ -343,25 +350,29 @@ private record ObsoleteApi( .forClass("java.util.Scanner") .withParameters("java.io.InputStream", "java.lang.String"), "Use new Scanner(InputStream, Charset) instead.", - 34), + 34, + 10), new ObsoleteApi( constructor() .forClass("java.util.Scanner") .withParameters("java.io.File", "java.lang.String"), "Use new Scanner(File, Charset) instead.", - 34), + 34, + 10), new ObsoleteApi( constructor() .forClass("java.util.Scanner") .withParameters("java.nio.file.Path", "java.lang.String"), "Use new Scanner(Path, Charset) instead.", - 34), + 34, + 10), new ObsoleteApi( constructor() .forClass("java.util.Scanner") .withParameters("java.nio.channels.ReadableByteChannel", "java.lang.String"), "Use new Scanner(ReadableByteChannel, Charset) instead.", - 34), + 34, + 10), new ObsoleteApi( constructor() .forClass("java.lang.String") @@ -386,63 +397,79 @@ private record ObsoleteApi( constructor() .forClass("java.io.PrintStream") .withParameters("java.io.OutputStream", "boolean", "java.lang.String"), - "Use new PrintStream(OutputStream, boolean, Charset) instead."), + "Use new PrintStream(OutputStream, boolean, Charset) instead.", + 1, + 10), new ObsoleteApi( constructor() .forClass("java.io.PrintStream") .withParameters("java.lang.String", "java.lang.String"), - "Use new PrintStream(String, Charset) instead."), + "Use new PrintStream(String, Charset) instead.", + 1, + 10), new ObsoleteApi( constructor() .forClass("java.io.PrintStream") .withParameters("java.io.File", "java.lang.String"), - "Use new PrintStream(File, Charset) instead."), + "Use new PrintStream(File, Charset) instead.", + 1, + 10), new ObsoleteApi( constructor() .forClass("java.io.PrintWriter") .withParameters("java.lang.String", "java.lang.String"), - "Use new PrintWriter(String, Charset) instead."), + "Use new PrintWriter(String, Charset) instead.", + 1, + 10), new ObsoleteApi( constructor() .forClass("java.io.PrintWriter") .withParameters("java.io.File", "java.lang.String"), - "Use new PrintWriter(File, Charset) instead."), + "Use new PrintWriter(File, Charset) instead.", + 1, + 10), new ObsoleteApi( constructor() .forClass("java.util.Formatter") .withParameters("java.lang.String", "java.lang.String"), "Use new Formatter(String, Charset) instead.", - 34), + 34, + 10), new ObsoleteApi( constructor() .forClass("java.util.Formatter") .withParameters("java.lang.String", "java.lang.String", "java.util.Locale"), "Use new Formatter(String, Charset, Locale) instead.", - 34), + 34, + 10), new ObsoleteApi( constructor() .forClass("java.util.Formatter") .withParameters("java.io.File", "java.lang.String"), "Use new Formatter(File, Charset) instead.", - 34), + 34, + 10), new ObsoleteApi( constructor() .forClass("java.util.Formatter") .withParameters("java.io.File", "java.lang.String", "java.util.Locale"), "Use new Formatter(File, Charset, Locale) instead.", - 34), + 34, + 10), new ObsoleteApi( constructor() .forClass("java.util.Formatter") .withParameters("java.io.OutputStream", "java.lang.String"), "Use new Formatter(OutputStream, Charset) instead.", - 34), + 34, + 10), new ObsoleteApi( constructor() .forClass("java.util.Formatter") .withParameters("java.io.OutputStream", "java.lang.String", "java.util.Locale"), "Use new Formatter(OutputStream, Charset, Locale) instead.", - 34)); + 34, + 10)); static final Matcher MATCHER_STRINGBUFFER = anyOf( @@ -748,6 +775,8 @@ private Description matchObsoleteApi( api -> (androidMinSdkVersion.isEmpty() || androidMinSdkVersion.get() >= api.androidMinSdkVersion()) + && (api.jdkVersion() <= 1 + || SourceVersion.sourceIsAtLeast(state.context, api.jdkVersion())) && api.matcher().matches(tree, state)) .map(api -> buildDescription(tree).setMessage(api.message()).build()) .findFirst() diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/JdkObsoleteTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/JdkObsoleteTest.java index f30324e8f41..39204aafb3d 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/JdkObsoleteTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/JdkObsoleteTest.java @@ -537,6 +537,70 @@ void scanner(InputStream is, String fileName, File file, Path path, ReadableByte .doTest(); } + @Test + public void preferCharsetAcceptingApis_jdk9() { + testHelper + .addSourceLines( + "Test.java", + """ + import static java.nio.charset.StandardCharsets.UTF_8; + + import java.io.*; + import java.net.*; + import java.nio.channels.*; + import java.util.*; + + class Test { + private static final String UTF8_NAME = UTF_8.name(); + + void string(byte[] bytes) throws Exception { + // BUG: Diagnostic contains: String.getBytes(Charset) + "foo".getBytes(UTF8_NAME); + // BUG: Diagnostic contains: new String(byte[], Charset) + new String(bytes, UTF8_NAME); + // BUG: Diagnostic contains: new String(byte[], int, int, Charset) + new String(bytes, 0, 1, UTF8_NAME); + } + + void inputStreamReader(InputStream is) throws Exception { + // BUG: Diagnostic contains: new InputStreamReader(InputStream, Charset) + new InputStreamReader(is, UTF8_NAME); + } + + void outputStreamWriter(OutputStream os) throws Exception { + // BUG: Diagnostic contains: new OutputStreamWriter(OutputStream, Charset) + new OutputStreamWriter(os, UTF8_NAME); + } + + void urlEncoder(String UTF8_NAME) throws Exception { + URLEncoder.encode("foo", UTF8_NAME); + } + + void urlDecoder(String UTF8_NAME) throws Exception { + URLDecoder.decode("foo", UTF8_NAME); + } + + void newReader(ReadableByteChannel rbc) throws Exception { + Channels.newReader(rbc, UTF8_NAME); + } + + void printStream(String fileName) throws Exception { + new PrintStream(fileName, UTF8_NAME); + } + + void printWriter(String fileName) throws Exception { + new PrintWriter(fileName, UTF8_NAME); + } + + void scanner(InputStream is) throws Exception { + new Scanner(is, UTF8_NAME); + } + } + """) + .setArgs("--release", "9") + .doTest(); + } + @Test public void preferCharsetAcceptingApis_androidMinSdk32() { testHelper