diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml index 441748328c43..ab8740992e8f 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml @@ -1125,6 +1125,10 @@ key="unnecessary.unicode.escape.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.internationalization.issues" enabledByDefault="false" level="WARNING" implementationClass="com.siyeh.ig.internationalization.UnnecessaryUnicodeEscapeInspection"/> + #ref to ''System.{0}.{1}()'' call suppress.for.tests.scope.quickfix=Suppress for 'Tests' scope +implicit.default.charset.usage.display.name=Implicit usage of platform's default charset +implicit.default.charset.usage.problem.descriptor=Call to #ref() uses the platform's default charset +implicit.default.charset.usage.constructor.problem.descriptor=new #ref() call uses the platform's default charset diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/internationalization/ImplicitDefaultCharsetUsageInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/internationalization/ImplicitDefaultCharsetUsageInspection.java new file mode 100644 index 000000000000..a2a3b12bd8de --- /dev/null +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/internationalization/ImplicitDefaultCharsetUsageInspection.java @@ -0,0 +1,151 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.siyeh.ig.internationalization; + +import com.intellij.psi.*; +import com.siyeh.InspectionGadgetsBundle; +import com.siyeh.ig.BaseInspection; +import com.siyeh.ig.BaseInspectionVisitor; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +/** + * @author Bas Leijdekkers + */ +public class ImplicitDefaultCharsetUsageInspection extends BaseInspection { + + @Nls + @NotNull + @Override + public String getDisplayName() { + return InspectionGadgetsBundle.message("implicit.default.charset.usage.display.name"); + } + + @NotNull + @Override + protected String buildErrorString(Object... infos) { + if (infos[0] instanceof PsiNewExpression) { + return InspectionGadgetsBundle.message("implicit.default.charset.usage.constructor.problem.descriptor"); + } + else { + return InspectionGadgetsBundle.message("implicit.default.charset.usage.problem.descriptor"); + } + } + + @Override + public BaseInspectionVisitor buildVisitor() { + return new ImplicitDefaultCharsetUsageVisitor(); + } + + private static class ImplicitDefaultCharsetUsageVisitor extends BaseInspectionVisitor { + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + super.visitMethodCallExpression(expression); + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + @NonNls final String name = methodExpression.getReferenceName(); + if (!"getBytes".equals(name)) { + return; + } + final PsiMethod method = expression.resolveMethod(); + if (method == null) { + return; + } + final PsiParameterList parameterList = method.getParameterList(); + if (parameterList.getParametersCount() == 1) { + return; + } + final PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return; + } + final String qName = aClass.getQualifiedName(); + if (!CommonClassNames.JAVA_LANG_STRING.equals(qName)) { + return; + } + registerMethodCallError(expression, expression); + } + + @Override + public void visitNewExpression(PsiNewExpression expression) { + super.visitNewExpression(expression); + final PsiMethod constructor = expression.resolveConstructor(); + if (constructor == null) { + return; + } + final PsiClass aClass = constructor.getContainingClass(); + if (aClass == null) { + return; + } + final PsiParameterList parameterList = constructor.getParameterList(); + final int count = parameterList.getParametersCount(); + if (count == 0) { + return; + } + final PsiParameter[] parameters = parameterList.getParameters(); + final String qName = aClass.getQualifiedName(); + if (CommonClassNames.JAVA_LANG_STRING.equals(qName)) { + if (!parameters[0].getType().equalsToText("byte[]") || hasCharsetType(parameters[count - 1])) { + return; + } + } + else if ("java.io.InputStreamReader".equals(qName) || + "java.io.OutputStreamWriter".equals(qName) || + "java.io.PrintStream".equals(qName)) { + if (hasCharsetType(parameters[count - 1])) { + return; + } + } + else if ("java.io.PrintWriter".equals(qName)) { + if (count > 1 && hasCharsetType(parameters[count - 1]) || parameters[0].getType().equalsToText("java.io.Writer")) { + return; + } + } + else if ("java.util.Formatter".equals(qName)) { + if (count > 1 && hasCharsetType(parameters[1])) { + return; + } + final PsiType firstType = parameters[0].getType(); + if (!firstType.equalsToText(CommonClassNames.JAVA_LANG_STRING) && !firstType.equalsToText("java.io.File") && + !firstType.equalsToText("java.io.OutputStream")) { + return; + } + } + else if ("java.util.Scanner".equals(qName)) { + if (count > 1 && hasCharsetType(parameters[1])) { + return; + } + final PsiType firstType = parameters[0].getType(); + if (!firstType.equalsToText("java.io.InputStream") && !firstType.equalsToText("java.io.File") && + !firstType.equalsToText("java.nio.file.Path") && !firstType.equalsToText("java.nio.channels.ReadableByteChannel")) { + return; + } + } + else if (!"java.io.FileReader".equals(qName) && !"java.io.FileWriter".equals(qName)) { + return; + } + registerNewExpressionError(expression, expression); + + } + + private static boolean hasCharsetType(PsiVariable variable) { + final PsiType lastType = variable.getType(); + return lastType.equalsToText(CommonClassNames.JAVA_LANG_STRING) || + lastType.equalsToText("java.nio.charset.Charset"); + } + } +} diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/ImplicitDefaultCharsetUsage.html b/plugins/InspectionGadgets/src/inspectionDescriptions/ImplicitDefaultCharsetUsage.html new file mode 100644 index 000000000000..a70bc25680eb --- /dev/null +++ b/plugins/InspectionGadgets/src/inspectionDescriptions/ImplicitDefaultCharsetUsage.html @@ -0,0 +1,10 @@ + + +Reports method and constructor calls which implicitly use the platform's default charset. +These can produce different results on (e.g. foreign language) systems that use a different default charset, +resulting in unexpected behaviour. + +

+New in 13.1 + + \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/internationalization/implicit_default_charset_usage/ImplicitDefaultCharsetUsage.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/internationalization/implicit_default_charset_usage/ImplicitDefaultCharsetUsage.java new file mode 100644 index 000000000000..17dd59eadf7a --- /dev/null +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/internationalization/implicit_default_charset_usage/ImplicitDefaultCharsetUsage.java @@ -0,0 +1,40 @@ +package com.siyeh.igtest.internationalization.implicit_default_charset_usage; + +import java.io.*; +import java.util.ArrayList; +import java.util.Formatter; +import java.util.Locale; +import java.util.Scanner; + +class ImplicitDefaultCharsetUsage { + + void f() throws IOException { + final byte[] bytes = "asdf".getBytes(); + "asdf".getBytes(""); + new String(); + new String("asdfas"); + new String(new byte[10], "asdf"); + new String(new byte[10]); + new String(new byte[10], 1, 9); + new InputStreamReader(null); + new InputStreamReader(null, "utf-8"); + new OutputStreamWriter(null); + new OutputStreamWriter(null, "utf-8"); + new FileReader("asdf"); + new FileWriter((String)null); + new PrintStream((OutputStream)null); + new PrintStream("filename", "utf-8"); + new PrintStream("filename"); + new PrintWriter((Writer)null); + new PrintWriter("filename", "utf-8"); + new PrintWriter("filename"); + new Formatter(new FileOutputStream("null")); + new Formatter(new FileOutputStream("null"), "utf-8"); + new Formatter(new FileOutputStream("null"), "utf-8", Locale.getDefault()); + new Formatter(System.out); + new Scanner(new FileInputStream("null")); + new Scanner(new FileInputStream("null"), "utf-8"); + new Scanner("string input"); + new ArrayList(10); + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/internationalization/ImplicitDefaultCharsetUsageInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/internationalization/ImplicitDefaultCharsetUsageInspectionTest.java new file mode 100644 index 000000000000..78822492b4bc --- /dev/null +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/internationalization/ImplicitDefaultCharsetUsageInspectionTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.siyeh.ig.internationalization; + +import com.intellij.codeInspection.InspectionProfileEntry; +import com.siyeh.ig.LightInspectionTestCase; + + +/** + * @author Bas Leijdekkers + */ +public class ImplicitDefaultCharsetUsageInspectionTest extends LightInspectionTestCase { + @Override + protected InspectionProfileEntry getInspection() { + return new ImplicitDefaultCharsetUsageInspection(); + } + + @Override + protected String[] getEnvironmentClasses() { + return new String[] { + "package java.io;" + + "import java.io.File;" + + "import java.io.Writer;" + + "public class PrintWriter {" + + " public PrintWriter(String fileName, String csn) {}" + + " public PrintWriter(String fileName) {}" + + " public PrintWriter(Writer writer) {}" + + "}", + "package java.util;" + + "import java.io.*;" + + "public class Formatter {" + + " public Formatter(OutputStream os) {}" + + " public Formatter(OutputStream os, String csn) {}" + + " public Formatter(OutputStream os, String csn, Locale l) {}" + + " public Formatter(PrintStream ps) {}" + + "}", + "package java.util;" + + "import java.io.*;" + + "public class Scanner {" + + " public Scanner(InputStream source) {}" + + " public Scanner(InputStream source, String charsetName) {}" + + " public Scanner(String source) {}" + + "}" + }; + + } + + public void testImplicitDefaultCharsetUsage() { + doTest(); + } +}