diff options
author | Tor Norbye <tnorbye@google.com> | 2012-12-03 17:54:49 -0800 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2012-12-03 18:18:35 -0800 |
commit | cf7a066f41b4eb1d63f176e0958a7f2c64d740cb (patch) | |
tree | e49dbe6690cc32c6a46c6753d1dddc3250d876d9 /lint/libs | |
parent | b227943031143adf72fb9d4fca8483403ebe61e4 (diff) | |
download | sdk-cf7a066f41b4eb1d63f176e0958a7f2c64d740cb.zip sdk-cf7a066f41b4eb1d63f176e0958a7f2c64d740cb.tar.gz sdk-cf7a066f41b4eb1d63f176e0958a7f2c64d740cb.tar.bz2 |
40876: Lint should flag "extension" pattern specifiers in SimpleDateFormat
Change-Id: I427e498a314aeaeb2a302811bfde1dc719e435d5
Diffstat (limited to 'lint/libs')
5 files changed, 109 insertions, 22 deletions
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java index 46e330e..323ec37 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java @@ -514,11 +514,16 @@ public class ApiDetector extends ResourceXmlDetector implements Detector.ClassSc // For virtual dispatch, walk up the inheritance chain checking // each inherited method if (owner.startsWith("android/") //$NON-NLS-1$ - || owner.startsWith("java/") //$NON-NLS-1$ || owner.startsWith("javax/")) { //$NON-NLS-1$ // The API map has already inlined all inherited methods // so no need to keep checking up the chain owner = null; + } else if (owner.startsWith("java/")) { //$NON-NLS-1$ + if (owner.equals(LocaleDetector.DATE_FORMAT_OWNER)) { + checkSimpleDateFormat(context, method, node, minSdk); + } + // Already inlined; see comment above + owner = null; } else if (node.getOpcode() == Opcodes.INVOKEVIRTUAL) { owner = context.getDriver().getSuperClass(owner); } else if (node.getOpcode() == Opcodes.INVOKESTATIC && api == -1) { @@ -570,6 +575,42 @@ public class ApiDetector extends ResourceXmlDetector implements Detector.ClassSc } } + private void checkSimpleDateFormat(ClassContext context, MethodNode method, + MethodInsnNode node, int minSdk) { + if (minSdk >= 9) { + // Already OK + return; + } + if (node.name.equals(CONSTRUCTOR_NAME) && !node.desc.equals("()V")) { //$NON-NLS-1$ + // Check first argument + AbstractInsnNode prev = LintUtils.getPrevInstruction(node); + if (prev != null && !node.desc.equals("(Ljava/lang/String;)V")) { //$NON-NLS-1$ + prev = LintUtils.getPrevInstruction(prev); + } + if (prev != null && prev.getOpcode() == Opcodes.LDC) { + LdcInsnNode ldc = (LdcInsnNode) prev; + Object cst = ldc.cst; + if (cst instanceof String) { + String pattern = (String) cst; + boolean isEscaped = false; + for (int i = 0; i < pattern.length(); i++) { + char c = pattern.charAt(i); + if (c == '\'') { + isEscaped = !isEscaped; + } else if (!isEscaped && (c == 'L' || c == 'c')) { + String message = String.format( + "The pattern character '%1$c' requires API level 9 (current " + + "min is %2$d) : \"%3$s\"", c, minSdk, pattern); + report(context, message, node, method, pattern, null, + SearchHints.create(FORWARD)); + return; + } + } + } + } + } + } + @SuppressWarnings("rawtypes") // ASM API private boolean methodDefinedLocally(ClassNode classNode, String name, String desc) { List methodList = classNode.methods; diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/LocaleDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/LocaleDetector.java index 6a6ed9a..1b34494 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/LocaleDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/LocaleDetector.java @@ -99,7 +99,7 @@ public class LocaleDetector extends Detector implements ClassScanner { Scope.CLASS_FILE_SCOPE).setMoreInfo( "http://developer.android.com/reference/java/text/SimpleDateFormat.html"); //$NON-NLS-1$ - private static final String DATE_FORMAT_OWNER = "java/text/SimpleDateFormat"; //$NON-NLS-1$ + static final String DATE_FORMAT_OWNER = "java/text/SimpleDateFormat"; //$NON-NLS-1$ private static final String STRING_OWNER = "java/lang/String"; //$NON-NLS-1$ /** Constructs a new {@link LocaleDetector} */ diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java index aa62f58..e42280f 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java @@ -463,26 +463,6 @@ public class ApiDetectorTest extends AbstractCheckTest { )); } - public void testSkipAndroidSupportInAospHalf() throws Exception { - String expected; - if (System.getenv("ANDROID_BUILD_TOP") != null) { - expected = "No warnings."; - } else { - expected = "bin/classes/android/support/foo/Foo.class: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMError [NewApi]\n" + - "1 errors, 0 warnings\n"; - } - - assertEquals( - expected, - - lintProject( - "apicheck/classpath=>.classpath", - "apicheck/minsdk1.xml=>AndroidManifest.xml", - "apicheck/ApiCallTest2.java.txt=>src/src/android/support/foo/Foo.java", - "apicheck/ApiCallTest2.class.data=>bin/classes/android/support/foo/Foo.class" - )); - } - public void testSuper() throws Exception { // See http://code.google.com/p/android/issues/detail?id=36384 assertEquals( @@ -700,4 +680,40 @@ public class ApiDetectorTest extends AbstractCheckTest { "apicheck/ApiCallTest11$MyActivity.class.data=>bin/classes/test/pkg/ApiCallTest11$MyActivity.class" )); } + + public void testDateFormat() throws Exception { + // See http://code.google.com/p/android/issues/detail?id=40876 + assertEquals( + "src/test/pkg/ApiCallTest12.java:18: Error: Call requires API level 9 (current min is 4): java.text.DateFormatSymbols#getInstance [NewApi]\n" + + " new SimpleDateFormat(\"yyyy-MM-dd\", DateFormatSymbols.getInstance());\n" + + " ~~~~~~~~~~~\n" + + "src/test/pkg/ApiCallTest12.java:23: Error: The pattern character 'L' requires API level 9 (current min is 4) : \"yyyy-MM-dd LL\" [NewApi]\n" + + " new SimpleDateFormat(\"yyyy-MM-dd LL\", Locale.US);\n" + + " ^\n" + + "src/test/pkg/ApiCallTest12.java:25: Error: The pattern character 'c' requires API level 9 (current min is 4) : \"cc yyyy-MM-dd\" [NewApi]\n" + + " SimpleDateFormat format = new SimpleDateFormat(\"cc yyyy-MM-dd\");\n" + + " ^\n" + + "3 errors, 0 warnings\n", + + lintProject( + "apicheck/classpath=>.classpath", + "apicheck/minsdk4.xml=>AndroidManifest.xml", + "project.properties1=>project.properties", + "apicheck/ApiCallTest12.java.txt=>src/test/pkg/ApiCallTest12.java", + "apicheck/ApiCallTest12.class.data=>bin/classes/test/pkg/ApiCallTest12.class" + )); + } + + public void testDateFormatOk() throws Exception { + assertEquals( + "No warnings.", + + lintProject( + "apicheck/classpath=>.classpath", + "apicheck/minsdk10.xml=>AndroidManifest.xml", + "project.properties1=>project.properties", + "apicheck/ApiCallTest12.java.txt=>src/test/pkg/ApiCallTest12.java", + "apicheck/ApiCallTest12.class.data=>bin/classes/test/pkg/ApiCallTest12.class" + )); + } } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.class.data Binary files differnew file mode 100644 index 0000000..4056133 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.java.txt new file mode 100644 index 0000000..6ac71db --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest12.java.txt @@ -0,0 +1,30 @@ +package test.pkg; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.os.Build; + +import java.text.DateFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.Locale; + +@SuppressWarnings({ "unused", "javadoc" }) +@SuppressLint("SimpleDateFormat") +public class ApiCallTest12 { + public void test() { + // Normal SimpleDateFormat calls + new SimpleDateFormat(); + new SimpleDateFormat("yyyy-MM-dd"); + new SimpleDateFormat("yyyy-MM-dd", DateFormatSymbols.getInstance()); + new SimpleDateFormat("yyyy-MM-dd", Locale.US); + new SimpleDateFormat("MMMM", Locale.US); + + // Flag format strings requiring API 9 + new SimpleDateFormat("yyyy-MM-dd LL", Locale.US); + + SimpleDateFormat format = new SimpleDateFormat("cc yyyy-MM-dd"); + + // Escaped text + new SimpleDateFormat("MM-dd 'My Location'", Locale.US); + } +} |