blob: 77c1c24b17eb2e4703fa9cacc294b9671c1f04d1 [file] [log] [blame]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001#!/usr/bin/env python
2
3# Copyright (C) 2014 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the 'License');
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an 'AS IS' BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Enforces common Android public API design patterns. It ignores lint messages from
19a previous API level, if provided.
20
21Usage: apilint.py current.txt
22Usage: apilint.py current.txt previous.txt
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070023
24You can also splice in blame details like this:
25$ git blame api/current.txt -t -e > /tmp/currentblame.txt
26$ apilint.py /tmp/currentblame.txt previous.txt --no-color
Jeff Sharkey8190f4882014-08-28 12:24:07 -070027"""
28
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070029import re, sys, collections, traceback, argparse
Jeff Sharkey8190f4882014-08-28 12:24:07 -070030
31
32BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
33
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070034ALLOW_GOOGLE = False
35USE_COLOR = True
36
Jeff Sharkey8190f4882014-08-28 12:24:07 -070037def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
38 # manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070039 if not USE_COLOR: return ""
Jeff Sharkey8190f4882014-08-28 12:24:07 -070040 codes = []
41 if reset: codes.append("0")
42 else:
43 if not fg is None: codes.append("3%d" % (fg))
44 if not bg is None:
45 if not bright: codes.append("4%d" % (bg))
46 else: codes.append("10%d" % (bg))
47 if bold: codes.append("1")
48 elif dim: codes.append("2")
49 else: codes.append("22")
50 return "\033[%sm" % (";".join(codes))
51
52
53class Field():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070054 def __init__(self, clazz, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070055 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070056 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070057 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070058 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -070059
60 raw = raw.split()
61 self.split = list(raw)
62
63 for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
64 while r in raw: raw.remove(r)
65
66 self.typ = raw[0]
67 self.name = raw[1].strip(";")
68 if len(raw) >= 4 and raw[2] == "=":
69 self.value = raw[3].strip(';"')
70 else:
71 self.value = None
72
Jeff Sharkey037458a2014-09-04 15:46:20 -070073 self.ident = self.raw.replace(" deprecated ", " ")
74
Jeff Sharkey8190f4882014-08-28 12:24:07 -070075 def __repr__(self):
76 return self.raw
77
78
79class Method():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070080 def __init__(self, clazz, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070081 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070082 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070083 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070084 self.blame = blame
85
86 # drop generics for now
87 raw = re.sub("<.+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070088
89 raw = re.split("[\s(),;]+", raw)
90 for r in ["", ";"]:
91 while r in raw: raw.remove(r)
92 self.split = list(raw)
93
Filip Pavlisdb234ef2016-11-29 19:06:17 +000094 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default"]:
Jeff Sharkey8190f4882014-08-28 12:24:07 -070095 while r in raw: raw.remove(r)
96
97 self.typ = raw[0]
98 self.name = raw[1]
99 self.args = []
100 for r in raw[2:]:
101 if r == "throws": break
102 self.args.append(r)
103
Jeff Sharkey037458a2014-09-04 15:46:20 -0700104 # identity for compat purposes
105 ident = self.raw
106 ident = ident.replace(" deprecated ", " ")
107 ident = ident.replace(" synchronized ", " ")
108 ident = re.sub("<.+?>", "", ident)
109 if " throws " in ident:
110 ident = ident[:ident.index(" throws ")]
111 self.ident = ident
112
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700113 def __repr__(self):
114 return self.raw
115
116
117class Class():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700118 def __init__(self, pkg, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700119 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700120 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700121 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700122 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700123 self.ctors = []
124 self.fields = []
125 self.methods = []
126
127 raw = raw.split()
128 self.split = list(raw)
129 if "class" in raw:
130 self.fullname = raw[raw.index("class")+1]
131 elif "interface" in raw:
132 self.fullname = raw[raw.index("interface")+1]
Jeff Sharkey037458a2014-09-04 15:46:20 -0700133 else:
134 raise ValueError("Funky class type %s" % (self.raw))
135
136 if "extends" in raw:
137 self.extends = raw[raw.index("extends")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800138 self.extends_path = self.extends.split(".")
Jeff Sharkey037458a2014-09-04 15:46:20 -0700139 else:
140 self.extends = None
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800141 self.extends_path = []
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700142
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700143 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800144 self.fullname_path = self.fullname.split(".")
145
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700146 self.name = self.fullname[self.fullname.rindex(".")+1:]
147
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700148 def __repr__(self):
149 return self.raw
150
151
152class Package():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700153 def __init__(self, line, raw, blame):
154 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700155 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700156 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700157
158 raw = raw.split()
159 self.name = raw[raw.index("package")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800160 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700161
162 def __repr__(self):
163 return self.raw
164
165
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800166def _parse_stream(f, clazz_cb=None):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700167 line = 0
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700168 api = {}
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700169 pkg = None
170 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700171 blame = None
172
173 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800174 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700175 line += 1
176 raw = raw.rstrip()
177 match = re_blame.match(raw)
178 if match is not None:
179 blame = match.groups()[0:2]
180 raw = match.groups()[2]
181 else:
182 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700183
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700184 if raw.startswith("package"):
185 pkg = Package(line, raw, blame)
186 elif raw.startswith(" ") and raw.endswith("{"):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800187 # When provided with class callback, we treat as incremental
188 # parse and don't build up entire API
189 if clazz and clazz_cb:
190 clazz_cb(clazz)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700191 clazz = Class(pkg, line, raw, blame)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800192 if not clazz_cb:
193 api[clazz.fullname] = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700194 elif raw.startswith(" ctor"):
195 clazz.ctors.append(Method(clazz, line, raw, blame))
196 elif raw.startswith(" method"):
197 clazz.methods.append(Method(clazz, line, raw, blame))
198 elif raw.startswith(" field"):
199 clazz.fields.append(Field(clazz, line, raw, blame))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700200
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800201 # Handle last trailing class
202 if clazz and clazz_cb:
203 clazz_cb(clazz)
204
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700205 return api
206
207
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700208class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800209 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700210 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700211 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800212 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700213 self.msg = msg
214
215 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800216 self.head = "Error %s" % (rule) if rule else "Error"
217 dump = "%s%s:%s %s" % (format(fg=RED, bg=BLACK, bold=True), self.head, format(reset=True), msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700218 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800219 self.head = "Warning %s" % (rule) if rule else "Warning"
220 dump = "%s%s:%s %s" % (format(fg=YELLOW, bg=BLACK, bold=True), self.head, format(reset=True), msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700221
222 self.line = clazz.line
223 blame = clazz.blame
224 if detail is not None:
225 dump += "\n in " + repr(detail)
226 self.line = detail.line
227 blame = detail.blame
228 dump += "\n in " + repr(clazz)
229 dump += "\n in " + repr(clazz.pkg)
230 dump += "\n at line " + repr(self.line)
231 if blame is not None:
232 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
233
234 self.dump = dump
235
236 def __repr__(self):
237 return self.dump
238
239
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700240failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700241
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800242def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700243 """Records an API failure to be processed later."""
244 global failures
245
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700246 sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg)
247 sig = sig.replace(" deprecated ", " ")
248
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800249 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700250
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700251
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800252def warn(clazz, detail, rule, msg):
253 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700254
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800255def error(clazz, detail, rule, msg):
256 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700257
258
259def verify_constants(clazz):
260 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700261 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600262 if clazz.fullname.startswith("android.os.Build"): return
263 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700264
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600265 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700266 for f in clazz.fields:
267 if "static" in f.split and "final" in f.split:
268 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800269 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600270 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700271 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
272 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600273 if f.typ in req and f.value is None:
274 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700275
276
277def verify_enums(clazz):
278 """Enums are bad, mmkay?"""
279 if "extends java.lang.Enum" in clazz.raw:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800280 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700281
282
283def verify_class_names(clazz):
284 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700285 if clazz.fullname.startswith("android.opengl"): return
286 if clazz.fullname.startswith("android.renderscript"): return
287 if re.match("android\.R\.[a-z]+", clazz.fullname): return
288
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700289 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800290 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700291 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800292 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700293
294
295def verify_method_names(clazz):
296 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700297 if clazz.fullname.startswith("android.opengl"): return
298 if clazz.fullname.startswith("android.renderscript"): return
299 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700300
301 for m in clazz.methods:
302 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800303 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700304 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800305 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700306
307
308def verify_callbacks(clazz):
309 """Verify Callback classes.
310 All callback classes must be abstract.
311 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700312 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700313
314 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800315 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700316 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800317 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700318
319 if clazz.name.endswith("Callback"):
320 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800321 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700322
323 for m in clazz.methods:
324 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800325 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700326
327
328def verify_listeners(clazz):
329 """Verify Listener classes.
330 All Listener classes must be interface.
331 All methods must follow onFoo() naming style.
332 If only a single method, it must match class name:
333 interface OnFooListener { void onFoo() }"""
334
335 if clazz.name.endswith("Listener"):
336 if " abstract class " in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800337 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700338
339 for m in clazz.methods:
340 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800341 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700342
343 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
344 m = clazz.methods[0]
345 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800346 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700347
348
349def verify_actions(clazz):
350 """Verify intent actions.
351 All action names must be named ACTION_FOO.
352 All action values must be scoped by package and match name:
353 package android.foo {
354 String ACTION_BAR = "android.foo.action.BAR";
355 }"""
356 for f in clazz.fields:
357 if f.value is None: continue
358 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700359 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600360 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700361
362 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
363 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
364 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800365 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700366 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700367 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700368 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700369 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700370 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700371 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
372 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700373 else:
374 prefix = clazz.pkg.name + ".action"
375 expected = prefix + "." + f.name[7:]
376 if f.value != expected:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800377 error(clazz, f, "C4", "Inconsistent action value; expected %s" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700378
379
380def verify_extras(clazz):
381 """Verify intent extras.
382 All extra names must be named EXTRA_FOO.
383 All extra values must be scoped by package and match name:
384 package android.foo {
385 String EXTRA_BAR = "android.foo.extra.BAR";
386 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700387 if clazz.fullname == "android.app.Notification": return
388 if clazz.fullname == "android.appwidget.AppWidgetManager": return
389
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700390 for f in clazz.fields:
391 if f.value is None: continue
392 if f.name.startswith("ACTION_"): continue
393
394 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
395 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
396 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800397 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700398 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700399 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700400 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700401 elif clazz.pkg.name == "android.app.admin":
402 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700403 else:
404 prefix = clazz.pkg.name + ".extra"
405 expected = prefix + "." + f.name[6:]
406 if f.value != expected:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800407 error(clazz, f, "C4", "Inconsistent extra value; expected %s" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700408
409
410def verify_equals(clazz):
411 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700412 eq = False
413 hc = False
414 for m in clazz.methods:
415 if " static " in m.raw: continue
416 if "boolean equals(java.lang.Object)" in m.raw: eq = True
417 if "int hashCode()" in m.raw: hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700418 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800419 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700420
421
422def verify_parcelable(clazz):
423 """Verify that Parcelable objects aren't hiding required bits."""
424 if "implements android.os.Parcelable" in clazz.raw:
425 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
426 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
427 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
428
429 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800430 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700431
Joe LaPenna45380002017-04-20 12:49:48 -0700432 if ((" final class " not in clazz.raw) and
433 (" final deprecated class " not in clazz.raw)):
Jeff Sharkey331279b2016-02-29 16:02:02 -0700434 error(clazz, None, "FW8", "Parcelable classes must be final")
435
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700436
437def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800438 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700439 for m in clazz.methods:
440 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800441 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700442 for f in clazz.fields:
443 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800444 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700445
446
447def verify_fields(clazz):
448 """Verify that all exposed fields are final.
449 Exposed fields must follow myName style.
450 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700451
452 IGNORE_BARE_FIELDS = [
453 "android.app.ActivityManager.RecentTaskInfo",
454 "android.app.Notification",
455 "android.content.pm.ActivityInfo",
456 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600457 "android.content.pm.ComponentInfo",
458 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700459 "android.content.pm.FeatureGroupInfo",
460 "android.content.pm.InstrumentationInfo",
461 "android.content.pm.PackageInfo",
462 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600463 "android.content.res.Configuration",
464 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700465 "android.os.Message",
466 "android.system.StructPollfd",
467 ]
468
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700469 for f in clazz.fields:
470 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700471 if clazz.fullname in IGNORE_BARE_FIELDS:
472 pass
473 elif clazz.fullname.endswith("LayoutParams"):
474 pass
475 elif clazz.fullname.startswith("android.util.Mutable"):
476 pass
477 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800478 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700479
480 if not "static" in f.split:
481 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800482 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700483
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700484 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800485 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700486
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700487 if re.match("[A-Z_]+", f.name):
488 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800489 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700490
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700491
492def verify_register(clazz):
493 """Verify parity of registration methods.
494 Callback objects use register/unregister methods.
495 Listener objects use add/remove methods."""
496 methods = [ m.name for m in clazz.methods ]
497 for m in clazz.methods:
498 if "Callback" in m.raw:
499 if m.name.startswith("register"):
500 other = "unregister" + m.name[8:]
501 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800502 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700503 if m.name.startswith("unregister"):
504 other = "register" + m.name[10:]
505 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800506 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700507
508 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800509 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700510
511 if "Listener" in m.raw:
512 if m.name.startswith("add"):
513 other = "remove" + m.name[3:]
514 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800515 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700516 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
517 other = "add" + m.name[6:]
518 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800519 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700520
521 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800522 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700523
524
525def verify_sync(clazz):
526 """Verify synchronized methods aren't exposed."""
527 for m in clazz.methods:
528 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800529 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700530
531
532def verify_intent_builder(clazz):
533 """Verify that Intent builders are createFooIntent() style."""
534 if clazz.name == "Intent": return
535
536 for m in clazz.methods:
537 if m.typ == "android.content.Intent":
538 if m.name.startswith("create") and m.name.endswith("Intent"):
539 pass
540 else:
Adam Powell539ea122015-04-10 13:01:37 -0700541 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700542
543
544def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700545 """Verify that helper classes are named consistently with what they extend.
546 All developer extendable methods should be named onFoo()."""
547 test_methods = False
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700548 if "extends android.app.Service" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700549 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700550 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800551 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700552
553 found = False
554 for f in clazz.fields:
555 if f.name == "SERVICE_INTERFACE":
556 found = True
557 if f.value != clazz.fullname:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800558 error(clazz, f, "C4", "Inconsistent interface constant; expected %s" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700559
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700560 if "extends android.content.ContentProvider" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700561 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700562 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800563 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700564
565 found = False
566 for f in clazz.fields:
567 if f.name == "PROVIDER_INTERFACE":
568 found = True
569 if f.value != clazz.fullname:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800570 error(clazz, f, "C4", "Inconsistent interface constant; expected %s" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700571
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700572 if "extends android.content.BroadcastReceiver" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700573 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700574 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800575 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700576
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700577 if "extends android.app.Activity" in clazz.raw:
578 test_methods = True
579 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800580 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700581
582 if test_methods:
583 for m in clazz.methods:
584 if "final" in m.split: continue
585 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700586 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800587 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700588 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800589 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700590
591
592def verify_builder(clazz):
593 """Verify builder classes.
594 Methods should return the builder to enable chaining."""
595 if " extends " in clazz.raw: return
596 if not clazz.name.endswith("Builder"): return
597
598 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800599 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700600
601 has_build = False
602 for m in clazz.methods:
603 if m.name == "build":
604 has_build = True
605 continue
606
607 if m.name.startswith("get"): continue
608 if m.name.startswith("clear"): continue
609
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700610 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800611 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700612
613 if m.name.startswith("set"):
614 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800615 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700616
617 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800618 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700619
620
621def verify_aidl(clazz):
622 """Catch people exposing raw AIDL."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700623 if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800624 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700625
626
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700627def verify_internal(clazz):
628 """Catch people exposing internal classes."""
629 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800630 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700631
632
633def verify_layering(clazz):
634 """Catch package layering violations.
635 For example, something in android.os depending on android.app."""
636 ranking = [
637 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
638 "android.app",
639 "android.widget",
640 "android.view",
641 "android.animation",
642 "android.provider",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700643 ["android.content","android.graphics.drawable"],
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700644 "android.database",
645 "android.graphics",
646 "android.text",
647 "android.os",
648 "android.util"
649 ]
650
651 def rank(p):
652 for i in range(len(ranking)):
653 if isinstance(ranking[i], list):
654 for j in ranking[i]:
655 if p.startswith(j): return i
656 else:
657 if p.startswith(ranking[i]): return i
658
659 cr = rank(clazz.pkg.name)
660 if cr is None: return
661
662 for f in clazz.fields:
663 ir = rank(f.typ)
664 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800665 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700666
667 for m in clazz.methods:
668 ir = rank(m.typ)
669 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800670 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700671 for arg in m.args:
672 ir = rank(arg)
673 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800674 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700675
676
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800677def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800678 """Verifies that boolean accessors are named correctly.
679 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700680
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800681 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
682 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700683
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800684 gets = [ m for m in clazz.methods if is_get(m) ]
685 sets = [ m for m in clazz.methods if is_set(m) ]
686
687 def error_if_exists(methods, trigger, expected, actual):
688 for m in methods:
689 if m.name == actual:
690 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700691
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700692 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800693 if is_get(m):
694 if re.match("is[A-Z]", m.name):
695 target = m.name[2:]
696 expected = "setIs" + target
697 error_if_exists(sets, m.name, expected, "setHas" + target)
698 elif re.match("has[A-Z]", m.name):
699 target = m.name[3:]
700 expected = "setHas" + target
701 error_if_exists(sets, m.name, expected, "setIs" + target)
702 error_if_exists(sets, m.name, expected, "set" + target)
703 elif re.match("get[A-Z]", m.name):
704 target = m.name[3:]
705 expected = "set" + target
706 error_if_exists(sets, m.name, expected, "setIs" + target)
707 error_if_exists(sets, m.name, expected, "setHas" + target)
708
709 if is_set(m):
710 if re.match("set[A-Z]", m.name):
711 target = m.name[3:]
712 expected = "get" + target
713 error_if_exists(sets, m.name, expected, "is" + target)
714 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700715
716
717def verify_collections(clazz):
718 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700719 if clazz.fullname == "android.os.Bundle": return
720
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700721 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
722 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
723 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700724 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800725 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700726 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700727 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800728 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700729
730
731def verify_flags(clazz):
732 """Verifies that flags are non-overlapping."""
733 known = collections.defaultdict(int)
734 for f in clazz.fields:
735 if "FLAG_" in f.name:
736 try:
737 val = int(f.value)
738 except:
739 continue
740
741 scope = f.name[0:f.name.index("FLAG_")]
742 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800743 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700744 known[scope] |= val
745
746
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800747def verify_exception(clazz):
748 """Verifies that methods don't throw generic exceptions."""
749 for m in clazz.methods:
750 if "throws java.lang.Exception" in m.raw or "throws java.lang.Throwable" in m.raw or "throws java.lang.Error" in m.raw:
751 error(clazz, m, "S1", "Methods must not throw generic exceptions")
752
Jeff Sharkey331279b2016-02-29 16:02:02 -0700753 if "throws android.os.RemoteException" in m.raw:
754 if clazz.name == "android.content.ContentProviderClient": continue
755 if clazz.name == "android.os.Binder": continue
756 if clazz.name == "android.os.IBinder": continue
757
758 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
759
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800760
761def verify_google(clazz):
762 """Verifies that APIs never reference Google."""
763
764 if re.search("google", clazz.raw, re.IGNORECASE):
765 error(clazz, None, None, "Must never reference Google")
766
767 test = []
768 test.extend(clazz.ctors)
769 test.extend(clazz.fields)
770 test.extend(clazz.methods)
771
772 for t in test:
773 if re.search("google", t.raw, re.IGNORECASE):
774 error(clazz, t, None, "Must never reference Google")
775
776
777def verify_bitset(clazz):
778 """Verifies that we avoid using heavy BitSet."""
779
780 for f in clazz.fields:
781 if f.typ == "java.util.BitSet":
782 error(clazz, f, None, "Field type must not be heavy BitSet")
783
784 for m in clazz.methods:
785 if m.typ == "java.util.BitSet":
786 error(clazz, m, None, "Return type must not be heavy BitSet")
787 for arg in m.args:
788 if arg == "java.util.BitSet":
789 error(clazz, m, None, "Argument type must not be heavy BitSet")
790
791
792def verify_manager(clazz):
793 """Verifies that FooManager is only obtained from Context."""
794
795 if not clazz.name.endswith("Manager"): return
796
797 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800798 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800799
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600800 for m in clazz.methods:
801 if m.typ == clazz.fullname:
802 error(clazz, m, None, "Managers must always be obtained from Context")
803
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800804
805def verify_boxed(clazz):
806 """Verifies that methods avoid boxed primitives."""
807
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800808 boxed = ["java.lang.Number","java.lang.Byte","java.lang.Double","java.lang.Float","java.lang.Integer","java.lang.Long","java.lang.Short"]
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800809
810 for c in clazz.ctors:
811 for arg in c.args:
812 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800813 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800814
815 for f in clazz.fields:
816 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800817 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800818
819 for m in clazz.methods:
820 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800821 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800822 for arg in m.args:
823 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800824 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800825
826
827def verify_static_utils(clazz):
828 """Verifies that helper classes can't be constructed."""
829 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600830 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800831
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600832 # Only care about classes with default constructors
833 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
834 test = []
835 test.extend(clazz.fields)
836 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800837
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600838 if len(test) == 0: return
839 for t in test:
840 if "static" not in t.split:
841 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800842
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800843 error(clazz, None, None, "Fully-static utility classes must not have constructor")
844
845
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800846def verify_overload_args(clazz):
847 """Verifies that method overloads add new arguments at the end."""
848 if clazz.fullname.startswith("android.opengl"): return
849
850 overloads = collections.defaultdict(list)
851 for m in clazz.methods:
852 if "deprecated" in m.split: continue
853 overloads[m.name].append(m)
854
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800855 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800856 if len(methods) <= 1: continue
857
858 # Look for arguments common across all overloads
859 def cluster(args):
860 count = collections.defaultdict(int)
861 res = set()
862 for i in range(len(args)):
863 a = args[i]
864 res.add("%s#%d" % (a, count[a]))
865 count[a] += 1
866 return res
867
868 common_args = cluster(methods[0].args)
869 for m in methods:
870 common_args = common_args & cluster(m.args)
871
872 if len(common_args) == 0: continue
873
874 # Require that all common arguments are present at start of signature
875 locked_sig = None
876 for m in methods:
877 sig = m.args[0:len(common_args)]
878 if not common_args.issubset(cluster(sig)):
879 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
880 elif not locked_sig:
881 locked_sig = sig
882 elif locked_sig != sig:
883 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
884
885
886def verify_callback_handlers(clazz):
887 """Verifies that methods adding listener/callback have overload
888 for specifying delivery thread."""
889
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800890 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800891 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800892 "animation",
893 "view",
894 "graphics",
895 "transition",
896 "widget",
897 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800898 ]
899 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800900 if s in clazz.pkg.name_path: return
901 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800902
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800903 # Ignore UI classes which assume main thread
904 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
905 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
906 if s in clazz.fullname: return
907 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
908 for s in ["Loader"]:
909 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800910
911 found = {}
912 by_name = collections.defaultdict(list)
913 for m in clazz.methods:
914 if m.name.startswith("unregister"): continue
915 if m.name.startswith("remove"): continue
916 if re.match("on[A-Z]+", m.name): continue
917
918 by_name[m.name].append(m)
919
920 for a in m.args:
921 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
922 found[m.name] = m
923
924 for f in found.values():
925 takes_handler = False
926 for m in by_name[f.name]:
927 if "android.os.Handler" in m.args:
928 takes_handler = True
929 if not takes_handler:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800930 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Handler")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800931
932
933def verify_context_first(clazz):
934 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800935 examine = clazz.ctors + clazz.methods
936 for m in examine:
937 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800938 if "android.content.Context" in m.args[1:]:
939 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600940 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
941 if "android.content.ContentResolver" in m.args[1:]:
942 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800943
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800944
945def verify_listener_last(clazz):
946 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
947 examine = clazz.ctors + clazz.methods
948 for m in examine:
949 if "Listener" in m.name or "Callback" in m.name: continue
950 found = False
951 for a in m.args:
952 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
953 found = True
954 elif found and a != "android.os.Handler":
955 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
956
957
958def verify_resource_names(clazz):
959 """Verifies that resource names have consistent case."""
960 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
961
962 # Resources defined by files are foo_bar_baz
963 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
964 for f in clazz.fields:
965 if re.match("[a-z1-9_]+$", f.name): continue
966 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
967
968 # Resources defined inside files are fooBarBaz
969 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
970 for f in clazz.fields:
971 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
972 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
973 if re.match("state_[a-z_]*$", f.name): continue
974
975 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
976 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
977
978 # Styles are FooBar_Baz
979 if clazz.name in ["style"]:
980 for f in clazz.fields:
981 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
982 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800983
984
Jeff Sharkey331279b2016-02-29 16:02:02 -0700985def verify_files(clazz):
986 """Verifies that methods accepting File also accept streams."""
987
988 has_file = set()
989 has_stream = set()
990
991 test = []
992 test.extend(clazz.ctors)
993 test.extend(clazz.methods)
994
995 for m in test:
996 if "java.io.File" in m.args:
997 has_file.add(m)
998 if "java.io.FileDescriptor" in m.args or "android.os.ParcelFileDescriptor" in m.args or "java.io.InputStream" in m.args or "java.io.OutputStream" in m.args:
999 has_stream.add(m.name)
1000
1001 for m in has_file:
1002 if m.name not in has_stream:
1003 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1004
1005
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001006def verify_manager_list(clazz):
1007 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1008
1009 if not clazz.name.endswith("Manager"): return
1010
1011 for m in clazz.methods:
1012 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1013 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1014
1015
Jeff Sharkey26c80902016-12-21 13:41:17 -07001016def verify_abstract_inner(clazz):
1017 """Verifies that abstract inner classes are static."""
1018
1019 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
1020 if " abstract " in clazz.raw and " static " not in clazz.raw:
1021 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1022
1023
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001024def verify_runtime_exceptions(clazz):
1025 """Verifies that runtime exceptions aren't listed in throws."""
1026
1027 banned = [
1028 "java.lang.NullPointerException",
1029 "java.lang.ClassCastException",
1030 "java.lang.IndexOutOfBoundsException",
1031 "java.lang.reflect.UndeclaredThrowableException",
1032 "java.lang.reflect.MalformedParametersException",
1033 "java.lang.reflect.MalformedParameterizedTypeException",
1034 "java.lang.invoke.WrongMethodTypeException",
1035 "java.lang.EnumConstantNotPresentException",
1036 "java.lang.IllegalMonitorStateException",
1037 "java.lang.SecurityException",
1038 "java.lang.UnsupportedOperationException",
1039 "java.lang.annotation.AnnotationTypeMismatchException",
1040 "java.lang.annotation.IncompleteAnnotationException",
1041 "java.lang.TypeNotPresentException",
1042 "java.lang.IllegalStateException",
1043 "java.lang.ArithmeticException",
1044 "java.lang.IllegalArgumentException",
1045 "java.lang.ArrayStoreException",
1046 "java.lang.NegativeArraySizeException",
1047 "java.util.MissingResourceException",
1048 "java.util.EmptyStackException",
1049 "java.util.concurrent.CompletionException",
1050 "java.util.concurrent.RejectedExecutionException",
1051 "java.util.IllformedLocaleException",
1052 "java.util.ConcurrentModificationException",
1053 "java.util.NoSuchElementException",
1054 "java.io.UncheckedIOException",
1055 "java.time.DateTimeException",
1056 "java.security.ProviderException",
1057 "java.nio.BufferUnderflowException",
1058 "java.nio.BufferOverflowException",
1059 ]
1060
1061 test = []
1062 test.extend(clazz.ctors)
1063 test.extend(clazz.methods)
1064
1065 for t in test:
1066 if " throws " not in t.raw: continue
1067 throws = t.raw[t.raw.index(" throws "):]
1068 for b in banned:
1069 if b in throws:
1070 error(clazz, t, None, "Methods must not mention RuntimeException subclasses in throws clauses")
1071
1072
1073def verify_error(clazz):
1074 """Verifies that we always use Exception instead of Error."""
1075 if not clazz.extends: return
1076 if clazz.extends.endswith("Error"):
1077 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1078 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1079 error(clazz, None, None, "Exceptions must be named FooException")
1080
1081
1082def verify_units(clazz):
1083 """Verifies that we use consistent naming for units."""
1084
1085 # If we find K, recommend replacing with V
1086 bad = {
1087 "Ns": "Nanos",
1088 "Ms": "Millis or Micros",
1089 "Sec": "Seconds", "Secs": "Seconds",
1090 "Hr": "Hours", "Hrs": "Hours",
1091 "Mo": "Months", "Mos": "Months",
1092 "Yr": "Years", "Yrs": "Years",
1093 "Byte": "Bytes", "Space": "Bytes",
1094 }
1095
1096 for m in clazz.methods:
1097 if m.typ not in ["short","int","long"]: continue
1098 for k, v in bad.iteritems():
1099 if m.name.endswith(k):
1100 error(clazz, m, None, "Expected method name units to be " + v)
1101 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1102 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1103 if m.name.endswith("Seconds"):
1104 error(clazz, m, None, "Returned time values must be in milliseconds")
1105
1106 for m in clazz.methods:
1107 typ = m.typ
1108 if typ == "void":
1109 if len(m.args) != 1: continue
1110 typ = m.args[0]
1111
1112 if m.name.endswith("Fraction") and typ != "float":
1113 error(clazz, m, None, "Fractions must use floats")
1114 if m.name.endswith("Percentage") and typ != "int":
1115 error(clazz, m, None, "Percentage must use ints")
1116
1117
1118def verify_closable(clazz):
1119 """Verifies that classes are AutoClosable."""
1120 if "implements java.lang.AutoCloseable" in clazz.raw: return
1121 if "implements java.io.Closeable" in clazz.raw: return
1122
1123 for m in clazz.methods:
1124 if len(m.args) > 0: continue
1125 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1126 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1127 return
1128
1129
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001130def examine_clazz(clazz):
1131 """Find all style issues in the given class."""
1132 if clazz.pkg.name.startswith("java"): return
1133 if clazz.pkg.name.startswith("junit"): return
1134 if clazz.pkg.name.startswith("org.apache"): return
1135 if clazz.pkg.name.startswith("org.xml"): return
1136 if clazz.pkg.name.startswith("org.json"): return
1137 if clazz.pkg.name.startswith("org.w3c"): return
Jeff Sharkey331279b2016-02-29 16:02:02 -07001138 if clazz.pkg.name.startswith("android.icu."): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001139
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001140 verify_constants(clazz)
1141 verify_enums(clazz)
1142 verify_class_names(clazz)
1143 verify_method_names(clazz)
1144 verify_callbacks(clazz)
1145 verify_listeners(clazz)
1146 verify_actions(clazz)
1147 verify_extras(clazz)
1148 verify_equals(clazz)
1149 verify_parcelable(clazz)
1150 verify_protected(clazz)
1151 verify_fields(clazz)
1152 verify_register(clazz)
1153 verify_sync(clazz)
1154 verify_intent_builder(clazz)
1155 verify_helper_classes(clazz)
1156 verify_builder(clazz)
1157 verify_aidl(clazz)
1158 verify_internal(clazz)
1159 verify_layering(clazz)
1160 verify_boolean(clazz)
1161 verify_collections(clazz)
1162 verify_flags(clazz)
1163 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001164 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001165 verify_bitset(clazz)
1166 verify_manager(clazz)
1167 verify_boxed(clazz)
1168 verify_static_utils(clazz)
1169 verify_overload_args(clazz)
1170 verify_callback_handlers(clazz)
1171 verify_context_first(clazz)
1172 verify_listener_last(clazz)
1173 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001174 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001175 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001176 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001177 verify_runtime_exceptions(clazz)
1178 verify_error(clazz)
1179 verify_units(clazz)
1180 verify_closable(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001181
1182
1183def examine_stream(stream):
1184 """Find all style issues in the given API stream."""
1185 global failures
1186 failures = {}
1187 _parse_stream(stream, examine_clazz)
1188 return failures
1189
1190
1191def examine_api(api):
1192 """Find all style issues in the given parsed API."""
1193 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001194 failures = {}
1195 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001196 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001197 return failures
1198
1199
Jeff Sharkey037458a2014-09-04 15:46:20 -07001200def verify_compat(cur, prev):
1201 """Find any incompatible API changes between two levels."""
1202 global failures
1203
1204 def class_exists(api, test):
1205 return test.fullname in api
1206
1207 def ctor_exists(api, clazz, test):
1208 for m in clazz.ctors:
1209 if m.ident == test.ident: return True
1210 return False
1211
1212 def all_methods(api, clazz):
1213 methods = list(clazz.methods)
1214 if clazz.extends is not None:
1215 methods.extend(all_methods(api, api[clazz.extends]))
1216 return methods
1217
1218 def method_exists(api, clazz, test):
1219 methods = all_methods(api, clazz)
1220 for m in methods:
1221 if m.ident == test.ident: return True
1222 return False
1223
1224 def field_exists(api, clazz, test):
1225 for f in clazz.fields:
1226 if f.ident == test.ident: return True
1227 return False
1228
1229 failures = {}
1230 for key in sorted(prev.keys()):
1231 prev_clazz = prev[key]
1232
1233 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001234 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001235 continue
1236
1237 cur_clazz = cur[key]
1238
1239 for test in prev_clazz.ctors:
1240 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001241 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001242
1243 methods = all_methods(prev, prev_clazz)
1244 for test in methods:
1245 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001246 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001247
1248 for test in prev_clazz.fields:
1249 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001250 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001251
1252 return failures
1253
1254
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001255if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001256 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
1257 patterns. It ignores lint messages from a previous API level, if provided.")
1258 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
1259 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
1260 help="previous.txt")
1261 parser.add_argument("--no-color", action='store_const', const=True,
1262 help="Disable terminal colors")
1263 parser.add_argument("--allow-google", action='store_const', const=True,
1264 help="Allow references to Google")
1265 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001266
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001267 if args['no_color']:
1268 USE_COLOR = False
1269
1270 if args['allow_google']:
1271 ALLOW_GOOGLE = True
1272
1273 current_file = args['current.txt']
1274 previous_file = args['previous.txt']
1275
1276 with current_file as f:
1277 cur_fail = examine_stream(f)
1278 if not previous_file is None:
1279 with previous_file as f:
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001280 prev_fail = examine_stream(f)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001281
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001282 # ignore errors from previous API level
1283 for p in prev_fail:
1284 if p in cur_fail:
1285 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001286
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001287 """
1288 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001289 # look for compatibility issues
1290 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001291
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001292 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1293 for f in sorted(compat_fail):
1294 print compat_fail[f]
1295 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001296 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001297
1298 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1299 for f in sorted(cur_fail):
1300 print cur_fail[f]
Jeff Sharkey037458a2014-09-04 15:46:20 -07001301 print