aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java91
-rw-r--r--lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java91
2 files changed, 64 insertions, 118 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 7129b9a..e045dd4 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
@@ -74,6 +74,16 @@ import java.util.List;
* by this application (according to its minimum API requirement in the manifest).
*/
public class ApiDetector extends ResourceXmlDetector implements Detector.ClassScanner {
+ /**
+ * Whether we flag variable, field, parameter and return type declarations of a type
+ * not yet available. It appears Dalvik is very forgiving and doesn't try to preload
+ * classes until actually needed, so there is no need to flag these, and in fact,
+ * patterns used for supporting new and old versions sometimes declares these methods
+ * and only conditionally end up actually accessing methods and fields, so only check
+ * method and field accesses.
+ */
+ private static final boolean CHECK_DECLARATIONS = false;
+
private static final boolean AOSP_BUILD = System.getenv("ANDROID_BUILD_TOP") != null; //$NON-NLS-1$
/** Accessing an unsupported API */
@@ -177,6 +187,13 @@ public class ApiDetector extends ResourceXmlDetector implements Detector.ClassSc
int minSdk = getMinSdk(context);
if (api > minSdk && api > context.getFolderVersion()
&& api > getLocalMinSdk(attribute.getOwnerElement())) {
+ // Don't complain about resource references in the tools namespace,
+ // such as for example "tools:layout="@android:layout/list_content",
+ // used only for designtime previews
+ if (TOOLS_URI.equals(attribute.getNamespaceURI())) {
+ return;
+ }
+
Location location = context.getLocation(attribute);
String message = String.format(
"%1$s requires API level %2$d (current min is %3$d)",
@@ -303,51 +320,53 @@ public class ApiDetector extends ResourceXmlDetector implements Detector.ClassSc
InsnList nodes = method.instructions;
- // Check types in parameter list and types of local variables
- List localVariables = method.localVariables;
- if (localVariables != null) {
- for (Object v : localVariables) {
- LocalVariableNode var = (LocalVariableNode) v;
- String desc = var.desc;
- if (desc.charAt(0) == 'L') {
- // "Lpackage/Class;" => "package/Bar"
- String className = desc.substring(1, desc.length() - 1);
- int api = mApiDatabase.getClassVersion(className);
+ if (CHECK_DECLARATIONS) {
+ // Check types in parameter list and types of local variables
+ List localVariables = method.localVariables;
+ if (localVariables != null) {
+ for (Object v : localVariables) {
+ LocalVariableNode var = (LocalVariableNode) v;
+ String desc = var.desc;
+ if (desc.charAt(0) == 'L') {
+ // "Lpackage/Class;" => "package/Bar"
+ String className = desc.substring(1, desc.length() - 1);
+ int api = mApiDatabase.getClassVersion(className);
+ if (api > minSdk) {
+ String fqcn = ClassContext.getFqcn(className);
+ String message = String.format(
+ "Class requires API level %1$d (current min is %2$d): %3$s",
+ api, minSdk, fqcn);
+ report(context, message, var.start, method,
+ className.substring(className.lastIndexOf('/') + 1), null,
+ SearchHints.create(NEAREST).matchJavaSymbol());
+ }
+ }
+ }
+ }
+
+ // Check return type
+ // The parameter types are already handled as local variables so we can skip
+ // right to the return type.
+ // Check types in parameter list
+ String signature = method.desc;
+ if (signature != null) {
+ int args = signature.indexOf(')');
+ if (args != -1 && signature.charAt(args + 1) == 'L') {
+ String type = signature.substring(args + 2, signature.length() - 1);
+ int api = mApiDatabase.getClassVersion(type);
if (api > minSdk) {
- String fqcn = ClassContext.getFqcn(className);
+ String fqcn = ClassContext.getFqcn(type);
String message = String.format(
"Class requires API level %1$d (current min is %2$d): %3$s",
api, minSdk, fqcn);
- report(context, message, var.start, method,
- className.substring(className.lastIndexOf('/') + 1), null,
- SearchHints.create(NEAREST).matchJavaSymbol());
+ AbstractInsnNode first = nodes.size() > 0 ? nodes.get(0) : null;
+ report(context, message, first, method, method.name, null,
+ SearchHints.create(BACKWARD).matchJavaSymbol());
}
}
}
}
- // Check return type
- // The parameter types are already handled as local variables so we can skip
- // right to the return type.
- // Check types in parameter list
- String signature = method.desc;
- if (signature != null) {
- int args = signature.indexOf(')');
- if (args != -1 && signature.charAt(args + 1) == 'L') {
- String type = signature.substring(args + 2, signature.length() - 1);
- int api = mApiDatabase.getClassVersion(type);
- if (api > minSdk) {
- String fqcn = ClassContext.getFqcn(type);
- String message = String.format(
- "Class requires API level %1$d (current min is %2$d): %3$s",
- api, minSdk, fqcn);
- AbstractInsnNode first = nodes.size() > 0 ? nodes.get(0) : null;
- report(context, message, first, method, method.name, null,
- SearchHints.create(BACKWARD).matchJavaSymbol());
- }
- }
- }
-
for (int i = 0, n = nodes.size(); i < n; i++) {
AbstractInsnNode instruction = nodes.get(i);
int type = instruction.getType();
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 9823f23..b5e243b 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
@@ -124,15 +124,9 @@ public class ApiDetectorTest extends AbstractCheckTest {
public void testApi1() throws Exception {
assertEquals(
- "src/foo/bar/ApiCallTest.java:18: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMLocator [NewApi]\n" +
- " public void method(Chronometer chronometer, DOMLocator locator) {\n" +
- " ~~~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:20: Error: Call requires API level 11 (current min is 1): android.app.Activity#getActionBar [NewApi]\n" +
" getActionBar(); // API 11\n" +
" ~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:23: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMError [NewApi]\n" +
- " DOMError error = null; // API 8\n" +
- " ~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:24: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
" Class<?> clz = DOMErrorHandler.class; // API 8\n" +
" ~~~~~~~~~~~~~~~\n" +
@@ -145,22 +139,14 @@ public class ApiDetectorTest extends AbstractCheckTest {
"src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 1): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
" int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
" ~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:38: Error: Class requires API level 14 (current min is 1): android.app.ApplicationErrorReport.BatteryInfo [NewApi]\n" +
- " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
- " ~~~~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 1): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
" BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
" ~~~~~~~~~~~\n" +
+ // Note: the above error range is wrong; should be pointing to the second
"src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 1): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
" Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
" ~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:45: Error: Class requires API level 14 (current min is 1): android.widget.GridLayout [NewApi]\n" +
- " GridLayout getGridLayout() { // API 14\n" +
- " ~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:49: Error: Class requires API level 14 (current min is 1): android.app.ApplicationErrorReport [NewApi]\n" +
- " private ApplicationErrorReport getReport() {\n" +
- " ~~~~~~~~~\n" +
- "12 errors, 0 warnings\n",
+ "7 errors, 0 warnings\n",
lintProject(
"apicheck/classpath=>.classpath",
@@ -172,15 +158,9 @@ public class ApiDetectorTest extends AbstractCheckTest {
public void testApi2() throws Exception {
assertEquals(
- "src/foo/bar/ApiCallTest.java:18: Error: Class requires API level 8 (current min is 2): org.w3c.dom.DOMLocator [NewApi]\n" +
- " public void method(Chronometer chronometer, DOMLocator locator) {\n" +
- " ~~~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:20: Error: Call requires API level 11 (current min is 2): android.app.Activity#getActionBar [NewApi]\n" +
" getActionBar(); // API 11\n" +
" ~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:23: Error: Class requires API level 8 (current min is 2): org.w3c.dom.DOMError [NewApi]\n" +
- " DOMError error = null; // API 8\n" +
- " ~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:24: Error: Class requires API level 8 (current min is 2): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
" Class<?> clz = DOMErrorHandler.class; // API 8\n" +
" ~~~~~~~~~~~~~~~\n" +
@@ -193,22 +173,13 @@ public class ApiDetectorTest extends AbstractCheckTest {
"src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 2): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
" int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
" ~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:38: Error: Class requires API level 14 (current min is 2): android.app.ApplicationErrorReport.BatteryInfo [NewApi]\n" +
- " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
- " ~~~~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 2): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
" BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
" ~~~~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 2): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
" Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
" ~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:45: Error: Class requires API level 14 (current min is 2): android.widget.GridLayout [NewApi]\n" +
- " GridLayout getGridLayout() { // API 14\n" +
- " ~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:49: Error: Class requires API level 14 (current min is 2): android.app.ApplicationErrorReport [NewApi]\n" +
- " private ApplicationErrorReport getReport() {\n" +
- " ~~~~~~~~~\n" +
- "12 errors, 0 warnings\n",
+ "7 errors, 0 warnings\n",
lintProject(
"apicheck/classpath=>.classpath",
@@ -220,15 +191,9 @@ public class ApiDetectorTest extends AbstractCheckTest {
public void testApi4() throws Exception {
assertEquals(
- "src/foo/bar/ApiCallTest.java:18: Error: Class requires API level 8 (current min is 4): org.w3c.dom.DOMLocator [NewApi]\n" +
- " public void method(Chronometer chronometer, DOMLocator locator) {\n" +
- " ~~~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:20: Error: Call requires API level 11 (current min is 4): android.app.Activity#getActionBar [NewApi]\n" +
" getActionBar(); // API 11\n" +
" ~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:23: Error: Class requires API level 8 (current min is 4): org.w3c.dom.DOMError [NewApi]\n" +
- " DOMError error = null; // API 8\n" +
- " ~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:24: Error: Class requires API level 8 (current min is 4): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
" Class<?> clz = DOMErrorHandler.class; // API 8\n" +
" ~~~~~~~~~~~~~~~\n" +
@@ -238,22 +203,13 @@ public class ApiDetectorTest extends AbstractCheckTest {
"src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 4): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
" int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
" ~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:38: Error: Class requires API level 14 (current min is 4): android.app.ApplicationErrorReport.BatteryInfo [NewApi]\n" +
- " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
- " ~~~~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 4): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
" BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
" ~~~~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 4): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
" Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
" ~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:45: Error: Class requires API level 14 (current min is 4): android.widget.GridLayout [NewApi]\n" +
- " GridLayout getGridLayout() { // API 14\n" +
- " ~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:49: Error: Class requires API level 14 (current min is 4): android.app.ApplicationErrorReport [NewApi]\n" +
- " private ApplicationErrorReport getReport() {\n" +
- " ~~~~~~~~~\n" +
- "11 errors, 0 warnings\n",
+ "6 errors, 0 warnings\n",
lintProject(
"apicheck/classpath=>.classpath",
@@ -274,22 +230,13 @@ public class ApiDetectorTest extends AbstractCheckTest {
"src/foo/bar/ApiCallTest.java:33: Error: Field requires API level 11 (current min is 10): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
" int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
" ~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:38: Error: Class requires API level 14 (current min is 10): android.app.ApplicationErrorReport.BatteryInfo [NewApi]\n" +
- " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
- " ~~~~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:38: Error: Field requires API level 14 (current min is 10): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
" BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
" ~~~~~~~~~~~\n" +
"src/foo/bar/ApiCallTest.java:41: Error: Field requires API level 11 (current min is 10): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
" Mode mode = PorterDuff.Mode.OVERLAY; // API 11\n" +
" ~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:45: Error: Class requires API level 14 (current min is 10): android.widget.GridLayout [NewApi]\n" +
- " GridLayout getGridLayout() { // API 14\n" +
- " ~~~~~~~~~~~~~\n" +
- "src/foo/bar/ApiCallTest.java:49: Error: Class requires API level 14 (current min is 10): android.app.ApplicationErrorReport [NewApi]\n" +
- " private ApplicationErrorReport getReport() {\n" +
- " ~~~~~~~~~\n" +
- "8 errors, 0 warnings\n",
+ "5 errors, 0 warnings\n",
lintProject(
"apicheck/classpath=>.classpath",
@@ -404,15 +351,10 @@ public class ApiDetectorTest extends AbstractCheckTest {
// These errors are correctly -not- suppressed because they
// appear in method3 (line 74-98) which is annotated with a
// @SuppressLint annotation specifying only an unrelated issue id
- "src/foo/bar/SuppressTest1.java:74: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMLocator [NewApi]\n" +
- " public void method3(Chronometer chronometer, DOMLocator locator) {\n" +
- " ~~~~~~~~~~\n" +
+
"src/foo/bar/SuppressTest1.java:76: Error: Call requires API level 11 (current min is 1): android.app.Activity#getActionBar [NewApi]\n" +
" getActionBar(); // API 11\n" +
" ~~~~~~~~~~~~\n" +
- "src/foo/bar/SuppressTest1.java:79: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMError [NewApi]\n" +
- " DOMError error = null; // API 8\n" +
- " ~~~~~~~~\n" +
"src/foo/bar/SuppressTest1.java:80: Error: Class requires API level 8 (current min is 1): org.w3c.dom.DOMErrorHandler [NewApi]\n" +
" Class<?> clz = DOMErrorHandler.class; // API 8\n" +
" ~~~~~~~~~~~~~~~\n" +
@@ -425,9 +367,6 @@ public class ApiDetectorTest extends AbstractCheckTest {
"src/foo/bar/SuppressTest1.java:89: Error: Field requires API level 11 (current min is 1): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE [NewApi]\n" +
" int field = OpcodeInfo.MAXIMUM_VALUE; // API 11\n" +
" ~~~~~~~~~~~~~\n" +
- "src/foo/bar/SuppressTest1.java:94: Error: Class requires API level 14 (current min is 1): android.app.ApplicationErrorReport.BatteryInfo [NewApi]\n" +
- " BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
- " ~~~~~~~~~~~\n" +
"src/foo/bar/SuppressTest1.java:94: Error: Field requires API level 14 (current min is 1): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
" BatteryInfo batteryInfo = getReport().batteryInfo;\n" +
" ~~~~~~~~~~~\n" +
@@ -439,16 +378,10 @@ public class ApiDetectorTest extends AbstractCheckTest {
// no effect (because they don't end up in the bytecode)
- "src/foo/bar/SuppressTest4.java:16: Error: Class requires API level 14 (current min is 1): android.app.ApplicationErrorReport [NewApi]\n" +
- " ApplicationErrorReport report = null;\n" +
- " ~~~~~~~~~~~~~~~~~~~~~~\n" +
- "src/foo/bar/SuppressTest4.java:19: Error: Class requires API level 14 (current min is 1): android.app.ApplicationErrorReport.BatteryInfo [NewApi]\n" +
- " BatteryInfo batteryInfo = report.batteryInfo;\n" +
- " ~~~~~~~~~~~\n" +
"src/foo/bar/SuppressTest4.java:19: Error: Field requires API level 14 (current min is 1): android.app.ApplicationErrorReport#batteryInfo [NewApi]\n" +
" BatteryInfo batteryInfo = report.batteryInfo;\n" +
" ~~~~~~~~~~~\n" +
- "13 errors, 0 warnings\n",
+ "8 errors, 0 warnings\n",
lintProject(
"apicheck/classpath=>.classpath",
@@ -578,13 +511,10 @@ public class ApiDetectorTest extends AbstractCheckTest {
"src/test/pkg/TestEnum.java:37: Error: Enum value requires API level 11 (current min is 4): android.graphics.PorterDuff.Mode#OVERLAY [NewApi]\n" +
" case OVERLAY: {\n" +
" ~~~~~~~\n" +
- "src/test/pkg/TestEnum.java:58: Error: Class requires API level 11 (current min is 4): android.renderscript.Element.DataType [NewApi]\n" +
- " public static void test4(final android.renderscript.Element.DataType type) {\n" +
- " ~~~~~~~~\n" +
"src/test/pkg/TestEnum.java:61: Error: Enum for switch requires API level 11 (current min is 4): android.renderscript.Element.DataType [NewApi]\n" +
" switch (type) {\n" +
" ^\n" +
- "4 errors, 0 warnings\n",
+ "3 errors, 0 warnings\n",
lintProject(
"apicheck/classpath=>.classpath",
@@ -623,10 +553,7 @@ public class ApiDetectorTest extends AbstractCheckTest {
public void testInnerClassPositions() throws Exception {
// See http://code.google.com/p/android/issues/detail?id=38113
assertEquals(
- "src/test/pkg/ApiCallTest8.java:8: Error: Class requires API level 8 (current min is 4): android.text.style.LeadingMarginSpan.LeadingMarginSpan2 [NewApi]\n" +
- " LeadingMarginSpan.LeadingMarginSpan2 span = null; \n" +
- " ~~~~~~~~~~~~~~~~~~\n" +
- "1 errors, 0 warnings\n",
+ "No warnings.",
lintProject(
"apicheck/classpath=>.classpath",