blob: 26248e51b884fd0022e74d988a8f2ce9ccad99c1 [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 Sharkeyc6c6c342017-11-13 09:13:37 -070075 def __hash__(self):
76 return hash(self.raw)
77
Jeff Sharkey8190f4882014-08-28 12:24:07 -070078 def __repr__(self):
79 return self.raw
80
81
82class Method():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070083 def __init__(self, clazz, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070084 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070085 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070086 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070087 self.blame = blame
88
89 # drop generics for now
90 raw = re.sub("<.+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070091
92 raw = re.split("[\s(),;]+", raw)
93 for r in ["", ";"]:
94 while r in raw: raw.remove(r)
95 self.split = list(raw)
96
Filip Pavlisdb234ef2016-11-29 19:06:17 +000097 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default"]:
Jeff Sharkey8190f4882014-08-28 12:24:07 -070098 while r in raw: raw.remove(r)
99
100 self.typ = raw[0]
101 self.name = raw[1]
102 self.args = []
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700103 self.throws = []
104 target = self.args
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700105 for r in raw[2:]:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700106 if r == "throws": target = self.throws
107 else: target.append(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700108
Jeff Sharkey037458a2014-09-04 15:46:20 -0700109 # identity for compat purposes
110 ident = self.raw
111 ident = ident.replace(" deprecated ", " ")
112 ident = ident.replace(" synchronized ", " ")
113 ident = re.sub("<.+?>", "", ident)
114 if " throws " in ident:
115 ident = ident[:ident.index(" throws ")]
116 self.ident = ident
117
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700118 def __hash__(self):
119 return hash(self.raw)
120
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700121 def __repr__(self):
122 return self.raw
123
124
125class Class():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700126 def __init__(self, pkg, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700127 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700128 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700129 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700130 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700131 self.ctors = []
132 self.fields = []
133 self.methods = []
134
135 raw = raw.split()
136 self.split = list(raw)
137 if "class" in raw:
138 self.fullname = raw[raw.index("class")+1]
139 elif "interface" in raw:
140 self.fullname = raw[raw.index("interface")+1]
Jeff Sharkey037458a2014-09-04 15:46:20 -0700141 else:
142 raise ValueError("Funky class type %s" % (self.raw))
143
144 if "extends" in raw:
145 self.extends = raw[raw.index("extends")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800146 self.extends_path = self.extends.split(".")
Jeff Sharkey037458a2014-09-04 15:46:20 -0700147 else:
148 self.extends = None
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800149 self.extends_path = []
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700150
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700151 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800152 self.fullname_path = self.fullname.split(".")
153
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700154 self.name = self.fullname[self.fullname.rindex(".")+1:]
155
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700156 def __hash__(self):
157 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
158
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700159 def __repr__(self):
160 return self.raw
161
162
163class Package():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700164 def __init__(self, line, raw, blame):
165 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700166 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700167 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700168
169 raw = raw.split()
170 self.name = raw[raw.index("package")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800171 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700172
173 def __repr__(self):
174 return self.raw
175
176
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800177def _parse_stream(f, clazz_cb=None):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700178 line = 0
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700179 api = {}
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700180 pkg = None
181 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700182 blame = None
183
184 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800185 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700186 line += 1
187 raw = raw.rstrip()
188 match = re_blame.match(raw)
189 if match is not None:
190 blame = match.groups()[0:2]
191 raw = match.groups()[2]
192 else:
193 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700194
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700195 if raw.startswith("package"):
196 pkg = Package(line, raw, blame)
197 elif raw.startswith(" ") and raw.endswith("{"):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800198 # When provided with class callback, we treat as incremental
199 # parse and don't build up entire API
200 if clazz and clazz_cb:
201 clazz_cb(clazz)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700202 clazz = Class(pkg, line, raw, blame)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800203 if not clazz_cb:
204 api[clazz.fullname] = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700205 elif raw.startswith(" ctor"):
206 clazz.ctors.append(Method(clazz, line, raw, blame))
207 elif raw.startswith(" method"):
208 clazz.methods.append(Method(clazz, line, raw, blame))
209 elif raw.startswith(" field"):
210 clazz.fields.append(Field(clazz, line, raw, blame))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700211
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800212 # Handle last trailing class
213 if clazz and clazz_cb:
214 clazz_cb(clazz)
215
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700216 return api
217
218
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700219class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800220 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700221 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700222 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800223 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700224 self.msg = msg
225
226 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800227 self.head = "Error %s" % (rule) if rule else "Error"
228 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 -0700229 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800230 self.head = "Warning %s" % (rule) if rule else "Warning"
231 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 -0700232
233 self.line = clazz.line
234 blame = clazz.blame
235 if detail is not None:
236 dump += "\n in " + repr(detail)
237 self.line = detail.line
238 blame = detail.blame
239 dump += "\n in " + repr(clazz)
240 dump += "\n in " + repr(clazz.pkg)
241 dump += "\n at line " + repr(self.line)
242 if blame is not None:
243 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
244
245 self.dump = dump
246
247 def __repr__(self):
248 return self.dump
249
250
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700251failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700252
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800253def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700254 """Records an API failure to be processed later."""
255 global failures
256
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700257 sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg)
258 sig = sig.replace(" deprecated ", " ")
259
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800260 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700261
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700262
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800263def warn(clazz, detail, rule, msg):
264 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700265
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800266def error(clazz, detail, rule, msg):
267 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700268
269
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700270noticed = {}
271
272def notice(clazz):
273 global noticed
274
275 noticed[clazz.fullname] = hash(clazz)
276
277
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700278def verify_constants(clazz):
279 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700280 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600281 if clazz.fullname.startswith("android.os.Build"): return
282 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700283
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600284 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700285 for f in clazz.fields:
286 if "static" in f.split and "final" in f.split:
287 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800288 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600289 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700290 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
291 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600292 if f.typ in req and f.value is None:
293 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700294
295
296def verify_enums(clazz):
297 """Enums are bad, mmkay?"""
298 if "extends java.lang.Enum" in clazz.raw:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800299 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700300
301
302def verify_class_names(clazz):
303 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700304 if clazz.fullname.startswith("android.opengl"): return
305 if clazz.fullname.startswith("android.renderscript"): return
306 if re.match("android\.R\.[a-z]+", clazz.fullname): return
307
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700308 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800309 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700310 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800311 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700312 if clazz.name.endswith("Impl"):
313 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700314
315
316def verify_method_names(clazz):
317 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700318 if clazz.fullname.startswith("android.opengl"): return
319 if clazz.fullname.startswith("android.renderscript"): return
320 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700321
322 for m in clazz.methods:
323 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800324 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700325 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800326 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700327
328
329def verify_callbacks(clazz):
330 """Verify Callback classes.
331 All callback classes must be abstract.
332 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700333 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700334
335 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800336 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700337 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800338 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700339
340 if clazz.name.endswith("Callback"):
341 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800342 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700343
344 for m in clazz.methods:
345 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800346 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700347
348
349def verify_listeners(clazz):
350 """Verify Listener classes.
351 All Listener classes must be interface.
352 All methods must follow onFoo() naming style.
353 If only a single method, it must match class name:
354 interface OnFooListener { void onFoo() }"""
355
356 if clazz.name.endswith("Listener"):
357 if " abstract class " in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800358 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700359
360 for m in clazz.methods:
361 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800362 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700363
364 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
365 m = clazz.methods[0]
366 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800367 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700368
369
370def verify_actions(clazz):
371 """Verify intent actions.
372 All action names must be named ACTION_FOO.
373 All action values must be scoped by package and match name:
374 package android.foo {
375 String ACTION_BAR = "android.foo.action.BAR";
376 }"""
377 for f in clazz.fields:
378 if f.value is None: continue
379 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700380 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600381 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700382
383 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
384 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
385 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800386 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700387 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700388 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700389 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700390 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700391 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700392 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
393 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700394 else:
395 prefix = clazz.pkg.name + ".action"
396 expected = prefix + "." + f.name[7:]
397 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700398 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700399
400
401def verify_extras(clazz):
402 """Verify intent extras.
403 All extra names must be named EXTRA_FOO.
404 All extra values must be scoped by package and match name:
405 package android.foo {
406 String EXTRA_BAR = "android.foo.extra.BAR";
407 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700408 if clazz.fullname == "android.app.Notification": return
409 if clazz.fullname == "android.appwidget.AppWidgetManager": return
410
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700411 for f in clazz.fields:
412 if f.value is None: continue
413 if f.name.startswith("ACTION_"): continue
414
415 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
416 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
417 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800418 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700419 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700420 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700421 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700422 elif clazz.pkg.name == "android.app.admin":
423 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700424 else:
425 prefix = clazz.pkg.name + ".extra"
426 expected = prefix + "." + f.name[6:]
427 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700428 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700429
430
431def verify_equals(clazz):
432 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700433 eq = False
434 hc = False
435 for m in clazz.methods:
436 if " static " in m.raw: continue
437 if "boolean equals(java.lang.Object)" in m.raw: eq = True
438 if "int hashCode()" in m.raw: hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700439 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800440 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700441
442
443def verify_parcelable(clazz):
444 """Verify that Parcelable objects aren't hiding required bits."""
445 if "implements android.os.Parcelable" in clazz.raw:
446 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
447 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
448 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
449
450 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800451 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700452
Joe LaPenna45380002017-04-20 12:49:48 -0700453 if ((" final class " not in clazz.raw) and
454 (" final deprecated class " not in clazz.raw)):
Jeff Sharkey331279b2016-02-29 16:02:02 -0700455 error(clazz, None, "FW8", "Parcelable classes must be final")
456
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700457 for c in clazz.ctors:
458 if c.args == ["android.os.Parcel"]:
459 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
460
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700461
462def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800463 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700464 for m in clazz.methods:
465 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800466 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700467 for f in clazz.fields:
468 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800469 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700470
471
472def verify_fields(clazz):
473 """Verify that all exposed fields are final.
474 Exposed fields must follow myName style.
475 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700476
477 IGNORE_BARE_FIELDS = [
478 "android.app.ActivityManager.RecentTaskInfo",
479 "android.app.Notification",
480 "android.content.pm.ActivityInfo",
481 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600482 "android.content.pm.ComponentInfo",
483 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700484 "android.content.pm.FeatureGroupInfo",
485 "android.content.pm.InstrumentationInfo",
486 "android.content.pm.PackageInfo",
487 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600488 "android.content.res.Configuration",
489 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700490 "android.os.Message",
491 "android.system.StructPollfd",
492 ]
493
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700494 for f in clazz.fields:
495 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700496 if clazz.fullname in IGNORE_BARE_FIELDS:
497 pass
498 elif clazz.fullname.endswith("LayoutParams"):
499 pass
500 elif clazz.fullname.startswith("android.util.Mutable"):
501 pass
502 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800503 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700504
505 if not "static" in f.split:
506 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800507 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700508
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700509 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800510 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700511
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700512 if re.match("[A-Z_]+", f.name):
513 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800514 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700515
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700516
517def verify_register(clazz):
518 """Verify parity of registration methods.
519 Callback objects use register/unregister methods.
520 Listener objects use add/remove methods."""
521 methods = [ m.name for m in clazz.methods ]
522 for m in clazz.methods:
523 if "Callback" in m.raw:
524 if m.name.startswith("register"):
525 other = "unregister" + m.name[8:]
526 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800527 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700528 if m.name.startswith("unregister"):
529 other = "register" + m.name[10:]
530 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800531 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700532
533 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800534 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700535
536 if "Listener" in m.raw:
537 if m.name.startswith("add"):
538 other = "remove" + m.name[3:]
539 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800540 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700541 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
542 other = "add" + m.name[6:]
543 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800544 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700545
546 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800547 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700548
549
550def verify_sync(clazz):
551 """Verify synchronized methods aren't exposed."""
552 for m in clazz.methods:
553 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800554 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700555
556
557def verify_intent_builder(clazz):
558 """Verify that Intent builders are createFooIntent() style."""
559 if clazz.name == "Intent": return
560
561 for m in clazz.methods:
562 if m.typ == "android.content.Intent":
563 if m.name.startswith("create") and m.name.endswith("Intent"):
564 pass
565 else:
Adam Powell539ea122015-04-10 13:01:37 -0700566 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700567
568
569def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700570 """Verify that helper classes are named consistently with what they extend.
571 All developer extendable methods should be named onFoo()."""
572 test_methods = False
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700573 if "extends android.app.Service" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700574 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700575 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800576 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700577
578 found = False
579 for f in clazz.fields:
580 if f.name == "SERVICE_INTERFACE":
581 found = True
582 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700583 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700584
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700585 if "extends android.content.ContentProvider" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700586 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700587 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800588 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700589
590 found = False
591 for f in clazz.fields:
592 if f.name == "PROVIDER_INTERFACE":
593 found = True
594 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700595 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700596
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700597 if "extends android.content.BroadcastReceiver" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700598 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700599 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800600 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700601
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700602 if "extends android.app.Activity" in clazz.raw:
603 test_methods = True
604 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800605 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700606
607 if test_methods:
608 for m in clazz.methods:
609 if "final" in m.split: continue
610 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700611 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800612 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700613 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800614 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700615
616
617def verify_builder(clazz):
618 """Verify builder classes.
619 Methods should return the builder to enable chaining."""
620 if " extends " in clazz.raw: return
621 if not clazz.name.endswith("Builder"): return
622
623 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800624 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700625
626 has_build = False
627 for m in clazz.methods:
628 if m.name == "build":
629 has_build = True
630 continue
631
632 if m.name.startswith("get"): continue
633 if m.name.startswith("clear"): continue
634
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700635 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800636 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700637
638 if m.name.startswith("set"):
639 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800640 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700641
642 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800643 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700644
645
646def verify_aidl(clazz):
647 """Catch people exposing raw AIDL."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700648 if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800649 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700650
651
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700652def verify_internal(clazz):
653 """Catch people exposing internal classes."""
654 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800655 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700656
657
658def verify_layering(clazz):
659 """Catch package layering violations.
660 For example, something in android.os depending on android.app."""
661 ranking = [
662 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
663 "android.app",
664 "android.widget",
665 "android.view",
666 "android.animation",
667 "android.provider",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700668 ["android.content","android.graphics.drawable"],
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700669 "android.database",
670 "android.graphics",
671 "android.text",
672 "android.os",
673 "android.util"
674 ]
675
676 def rank(p):
677 for i in range(len(ranking)):
678 if isinstance(ranking[i], list):
679 for j in ranking[i]:
680 if p.startswith(j): return i
681 else:
682 if p.startswith(ranking[i]): return i
683
684 cr = rank(clazz.pkg.name)
685 if cr is None: return
686
687 for f in clazz.fields:
688 ir = rank(f.typ)
689 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800690 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700691
692 for m in clazz.methods:
693 ir = rank(m.typ)
694 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800695 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700696 for arg in m.args:
697 ir = rank(arg)
698 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800699 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700700
701
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800702def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800703 """Verifies that boolean accessors are named correctly.
704 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700705
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800706 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
707 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700708
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800709 gets = [ m for m in clazz.methods if is_get(m) ]
710 sets = [ m for m in clazz.methods if is_set(m) ]
711
712 def error_if_exists(methods, trigger, expected, actual):
713 for m in methods:
714 if m.name == actual:
715 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700716
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700717 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800718 if is_get(m):
719 if re.match("is[A-Z]", m.name):
720 target = m.name[2:]
721 expected = "setIs" + target
722 error_if_exists(sets, m.name, expected, "setHas" + target)
723 elif re.match("has[A-Z]", m.name):
724 target = m.name[3:]
725 expected = "setHas" + target
726 error_if_exists(sets, m.name, expected, "setIs" + target)
727 error_if_exists(sets, m.name, expected, "set" + target)
728 elif re.match("get[A-Z]", m.name):
729 target = m.name[3:]
730 expected = "set" + target
731 error_if_exists(sets, m.name, expected, "setIs" + target)
732 error_if_exists(sets, m.name, expected, "setHas" + target)
733
734 if is_set(m):
735 if re.match("set[A-Z]", m.name):
736 target = m.name[3:]
737 expected = "get" + target
738 error_if_exists(sets, m.name, expected, "is" + target)
739 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700740
741
742def verify_collections(clazz):
743 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700744 if clazz.fullname == "android.os.Bundle": return
745
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700746 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
747 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
748 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700749 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800750 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700751 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700752 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800753 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700754
755
756def verify_flags(clazz):
757 """Verifies that flags are non-overlapping."""
758 known = collections.defaultdict(int)
759 for f in clazz.fields:
760 if "FLAG_" in f.name:
761 try:
762 val = int(f.value)
763 except:
764 continue
765
766 scope = f.name[0:f.name.index("FLAG_")]
767 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800768 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700769 known[scope] |= val
770
771
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800772def verify_exception(clazz):
773 """Verifies that methods don't throw generic exceptions."""
774 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700775 for t in m.throws:
776 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
777 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800778
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700779 if t in ["android.os.RemoteException"]:
780 if clazz.name == "android.content.ContentProviderClient": continue
781 if clazz.name == "android.os.Binder": continue
782 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -0700783
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700784 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
785
786 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
787 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -0700788
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800789
790def verify_google(clazz):
791 """Verifies that APIs never reference Google."""
792
793 if re.search("google", clazz.raw, re.IGNORECASE):
794 error(clazz, None, None, "Must never reference Google")
795
796 test = []
797 test.extend(clazz.ctors)
798 test.extend(clazz.fields)
799 test.extend(clazz.methods)
800
801 for t in test:
802 if re.search("google", t.raw, re.IGNORECASE):
803 error(clazz, t, None, "Must never reference Google")
804
805
806def verify_bitset(clazz):
807 """Verifies that we avoid using heavy BitSet."""
808
809 for f in clazz.fields:
810 if f.typ == "java.util.BitSet":
811 error(clazz, f, None, "Field type must not be heavy BitSet")
812
813 for m in clazz.methods:
814 if m.typ == "java.util.BitSet":
815 error(clazz, m, None, "Return type must not be heavy BitSet")
816 for arg in m.args:
817 if arg == "java.util.BitSet":
818 error(clazz, m, None, "Argument type must not be heavy BitSet")
819
820
821def verify_manager(clazz):
822 """Verifies that FooManager is only obtained from Context."""
823
824 if not clazz.name.endswith("Manager"): return
825
826 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800827 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800828
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600829 for m in clazz.methods:
830 if m.typ == clazz.fullname:
831 error(clazz, m, None, "Managers must always be obtained from Context")
832
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800833
834def verify_boxed(clazz):
835 """Verifies that methods avoid boxed primitives."""
836
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800837 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 -0800838
839 for c in clazz.ctors:
840 for arg in c.args:
841 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800842 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800843
844 for f in clazz.fields:
845 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800846 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800847
848 for m in clazz.methods:
849 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800850 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800851 for arg in m.args:
852 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800853 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800854
855
856def verify_static_utils(clazz):
857 """Verifies that helper classes can't be constructed."""
858 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600859 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800860
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600861 # Only care about classes with default constructors
862 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
863 test = []
864 test.extend(clazz.fields)
865 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800866
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600867 if len(test) == 0: return
868 for t in test:
869 if "static" not in t.split:
870 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800871
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800872 error(clazz, None, None, "Fully-static utility classes must not have constructor")
873
874
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800875def verify_overload_args(clazz):
876 """Verifies that method overloads add new arguments at the end."""
877 if clazz.fullname.startswith("android.opengl"): return
878
879 overloads = collections.defaultdict(list)
880 for m in clazz.methods:
881 if "deprecated" in m.split: continue
882 overloads[m.name].append(m)
883
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800884 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800885 if len(methods) <= 1: continue
886
887 # Look for arguments common across all overloads
888 def cluster(args):
889 count = collections.defaultdict(int)
890 res = set()
891 for i in range(len(args)):
892 a = args[i]
893 res.add("%s#%d" % (a, count[a]))
894 count[a] += 1
895 return res
896
897 common_args = cluster(methods[0].args)
898 for m in methods:
899 common_args = common_args & cluster(m.args)
900
901 if len(common_args) == 0: continue
902
903 # Require that all common arguments are present at start of signature
904 locked_sig = None
905 for m in methods:
906 sig = m.args[0:len(common_args)]
907 if not common_args.issubset(cluster(sig)):
908 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
909 elif not locked_sig:
910 locked_sig = sig
911 elif locked_sig != sig:
912 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
913
914
915def verify_callback_handlers(clazz):
916 """Verifies that methods adding listener/callback have overload
917 for specifying delivery thread."""
918
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800919 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800920 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800921 "animation",
922 "view",
923 "graphics",
924 "transition",
925 "widget",
926 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800927 ]
928 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800929 if s in clazz.pkg.name_path: return
930 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800931
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800932 # Ignore UI classes which assume main thread
933 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
934 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
935 if s in clazz.fullname: return
936 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
937 for s in ["Loader"]:
938 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800939
940 found = {}
941 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700942 examine = clazz.ctors + clazz.methods
943 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800944 if m.name.startswith("unregister"): continue
945 if m.name.startswith("remove"): continue
946 if re.match("on[A-Z]+", m.name): continue
947
948 by_name[m.name].append(m)
949
950 for a in m.args:
951 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
952 found[m.name] = m
953
954 for f in found.values():
955 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -0700956 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800957 for m in by_name[f.name]:
958 if "android.os.Handler" in m.args:
959 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -0700960 if "java.util.concurrent.Executor" in m.args:
961 takes_exec = True
962 if not takes_exec:
963 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800964
965
966def verify_context_first(clazz):
967 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800968 examine = clazz.ctors + clazz.methods
969 for m in examine:
970 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800971 if "android.content.Context" in m.args[1:]:
972 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600973 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
974 if "android.content.ContentResolver" in m.args[1:]:
975 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800976
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800977
978def verify_listener_last(clazz):
979 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
980 examine = clazz.ctors + clazz.methods
981 for m in examine:
982 if "Listener" in m.name or "Callback" in m.name: continue
983 found = False
984 for a in m.args:
985 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
986 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700987 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800988 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
989
990
991def verify_resource_names(clazz):
992 """Verifies that resource names have consistent case."""
993 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
994
995 # Resources defined by files are foo_bar_baz
996 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
997 for f in clazz.fields:
998 if re.match("[a-z1-9_]+$", f.name): continue
999 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1000
1001 # Resources defined inside files are fooBarBaz
1002 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1003 for f in clazz.fields:
1004 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1005 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1006 if re.match("state_[a-z_]*$", f.name): continue
1007
1008 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1009 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1010
1011 # Styles are FooBar_Baz
1012 if clazz.name in ["style"]:
1013 for f in clazz.fields:
1014 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1015 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001016
1017
Jeff Sharkey331279b2016-02-29 16:02:02 -07001018def verify_files(clazz):
1019 """Verifies that methods accepting File also accept streams."""
1020
1021 has_file = set()
1022 has_stream = set()
1023
1024 test = []
1025 test.extend(clazz.ctors)
1026 test.extend(clazz.methods)
1027
1028 for m in test:
1029 if "java.io.File" in m.args:
1030 has_file.add(m)
1031 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:
1032 has_stream.add(m.name)
1033
1034 for m in has_file:
1035 if m.name not in has_stream:
1036 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1037
1038
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001039def verify_manager_list(clazz):
1040 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1041
1042 if not clazz.name.endswith("Manager"): return
1043
1044 for m in clazz.methods:
1045 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1046 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1047
1048
Jeff Sharkey26c80902016-12-21 13:41:17 -07001049def verify_abstract_inner(clazz):
1050 """Verifies that abstract inner classes are static."""
1051
1052 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
1053 if " abstract " in clazz.raw and " static " not in clazz.raw:
1054 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1055
1056
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001057def verify_runtime_exceptions(clazz):
1058 """Verifies that runtime exceptions aren't listed in throws."""
1059
1060 banned = [
1061 "java.lang.NullPointerException",
1062 "java.lang.ClassCastException",
1063 "java.lang.IndexOutOfBoundsException",
1064 "java.lang.reflect.UndeclaredThrowableException",
1065 "java.lang.reflect.MalformedParametersException",
1066 "java.lang.reflect.MalformedParameterizedTypeException",
1067 "java.lang.invoke.WrongMethodTypeException",
1068 "java.lang.EnumConstantNotPresentException",
1069 "java.lang.IllegalMonitorStateException",
1070 "java.lang.SecurityException",
1071 "java.lang.UnsupportedOperationException",
1072 "java.lang.annotation.AnnotationTypeMismatchException",
1073 "java.lang.annotation.IncompleteAnnotationException",
1074 "java.lang.TypeNotPresentException",
1075 "java.lang.IllegalStateException",
1076 "java.lang.ArithmeticException",
1077 "java.lang.IllegalArgumentException",
1078 "java.lang.ArrayStoreException",
1079 "java.lang.NegativeArraySizeException",
1080 "java.util.MissingResourceException",
1081 "java.util.EmptyStackException",
1082 "java.util.concurrent.CompletionException",
1083 "java.util.concurrent.RejectedExecutionException",
1084 "java.util.IllformedLocaleException",
1085 "java.util.ConcurrentModificationException",
1086 "java.util.NoSuchElementException",
1087 "java.io.UncheckedIOException",
1088 "java.time.DateTimeException",
1089 "java.security.ProviderException",
1090 "java.nio.BufferUnderflowException",
1091 "java.nio.BufferOverflowException",
1092 ]
1093
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001094 examine = clazz.ctors + clazz.methods
1095 for m in examine:
1096 for t in m.throws:
1097 if t in banned:
1098 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001099
1100
1101def verify_error(clazz):
1102 """Verifies that we always use Exception instead of Error."""
1103 if not clazz.extends: return
1104 if clazz.extends.endswith("Error"):
1105 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1106 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1107 error(clazz, None, None, "Exceptions must be named FooException")
1108
1109
1110def verify_units(clazz):
1111 """Verifies that we use consistent naming for units."""
1112
1113 # If we find K, recommend replacing with V
1114 bad = {
1115 "Ns": "Nanos",
1116 "Ms": "Millis or Micros",
1117 "Sec": "Seconds", "Secs": "Seconds",
1118 "Hr": "Hours", "Hrs": "Hours",
1119 "Mo": "Months", "Mos": "Months",
1120 "Yr": "Years", "Yrs": "Years",
1121 "Byte": "Bytes", "Space": "Bytes",
1122 }
1123
1124 for m in clazz.methods:
1125 if m.typ not in ["short","int","long"]: continue
1126 for k, v in bad.iteritems():
1127 if m.name.endswith(k):
1128 error(clazz, m, None, "Expected method name units to be " + v)
1129 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1130 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1131 if m.name.endswith("Seconds"):
1132 error(clazz, m, None, "Returned time values must be in milliseconds")
1133
1134 for m in clazz.methods:
1135 typ = m.typ
1136 if typ == "void":
1137 if len(m.args) != 1: continue
1138 typ = m.args[0]
1139
1140 if m.name.endswith("Fraction") and typ != "float":
1141 error(clazz, m, None, "Fractions must use floats")
1142 if m.name.endswith("Percentage") and typ != "int":
1143 error(clazz, m, None, "Percentage must use ints")
1144
1145
1146def verify_closable(clazz):
1147 """Verifies that classes are AutoClosable."""
1148 if "implements java.lang.AutoCloseable" in clazz.raw: return
1149 if "implements java.io.Closeable" in clazz.raw: return
1150
1151 for m in clazz.methods:
1152 if len(m.args) > 0: continue
1153 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1154 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1155 return
1156
1157
Jake Wharton9e6738f2017-08-23 11:59:55 -04001158def verify_member_name_not_kotlin_keyword(clazz):
1159 """Prevent method names which are keywords in Kotlin."""
1160
1161 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1162 # This list does not include Java keywords as those are already impossible to use.
1163 keywords = [
1164 'as',
1165 'fun',
1166 'in',
1167 'is',
1168 'object',
1169 'typealias',
1170 'val',
1171 'var',
1172 'when',
1173 ]
1174
1175 for m in clazz.methods:
1176 if m.name in keywords:
1177 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1178 for f in clazz.fields:
1179 if f.name in keywords:
1180 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1181
1182
1183def verify_method_name_not_kotlin_operator(clazz):
1184 """Warn about method names which become operators in Kotlin."""
1185
1186 binary = set()
1187
1188 def unique_binary_op(m, op):
1189 if op in binary:
1190 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1191 binary.add(op)
1192
1193 for m in clazz.methods:
1194 if 'static' in m.split:
1195 continue
1196
1197 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1198 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1199 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1200
1201 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1202 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1203 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1204 # practical way of checking that relationship here.
1205 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1206
1207 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1208 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1209 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1210 unique_binary_op(m, m.name)
1211
1212 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1213 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1214 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1215
1216 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1217 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1218 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1219
1220 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1221 if m.name == 'invoke':
1222 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1223
1224 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1225 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1226 and len(m.args) == 1 \
1227 and m.typ == 'void':
1228 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1229 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1230
1231
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001232def verify_collections_over_arrays(clazz):
1233 """Warn that [] should be Collections."""
1234
1235 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1236 for m in clazz.methods:
1237 if m.typ.endswith("[]") and m.typ not in safe:
1238 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1239 for arg in m.args:
1240 if arg.endswith("[]") and arg not in safe:
1241 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1242
1243
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001244def verify_user_handle(clazz):
1245 """Methods taking UserHandle should be ForUser or AsUser."""
1246 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1247 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1248 if clazz.fullname == "android.content.pm.LauncherApps": return
1249 if clazz.fullname == "android.os.UserHandle": return
1250 if clazz.fullname == "android.os.UserManager": return
1251
1252 for m in clazz.methods:
1253 if m.name.endswith("AsUser") or m.name.endswith("ForUser"): continue
1254 if re.match("on[A-Z]+", m.name): continue
1255 if "android.os.UserHandle" in m.args:
1256 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' or 'queryFooForUser'")
1257
1258
1259def verify_params(clazz):
1260 """Parameter classes should be 'Params'."""
1261 if clazz.name.endswith("Params"): return
1262 if clazz.fullname == "android.app.ActivityOptions": return
1263 if clazz.fullname == "android.app.BroadcastOptions": return
1264 if clazz.fullname == "android.os.Bundle": return
1265 if clazz.fullname == "android.os.BaseBundle": return
1266 if clazz.fullname == "android.os.PersistableBundle": return
1267
1268 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1269 for b in bad:
1270 if clazz.name.endswith(b):
1271 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1272
1273
1274def verify_services(clazz):
1275 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1276 if clazz.fullname != "android.content.Context": return
1277
1278 for f in clazz.fields:
1279 if f.typ != "java.lang.String": continue
1280 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1281 if found:
1282 expected = found.group(1).lower()
1283 if f.value != expected:
1284 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1285
1286
1287def verify_tense(clazz):
1288 """Verify tenses of method names."""
1289 if clazz.fullname.startswith("android.opengl"): return
1290
1291 for m in clazz.methods:
1292 if m.name.endswith("Enable"):
1293 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1294
1295
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001296def verify_icu(clazz):
1297 """Verifies that richer ICU replacements are used."""
1298 better = {
1299 "java.util.TimeZone": "android.icu.util.TimeZone",
1300 "java.util.Calendar": "android.icu.util.Calendar",
1301 "java.util.Locale": "android.icu.util.ULocale",
1302 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1303 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1304 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1305 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1306 "java.lang.Character": "android.icu.lang.UCharacter",
1307 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1308 "java.text.Collator": "android.icu.text.Collator",
1309 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1310 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1311 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1312 "java.text.DateFormat": "android.icu.text.DateFormat",
1313 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1314 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1315 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1316 }
1317
1318 for m in clazz.ctors + clazz.methods:
1319 types = []
1320 types.extend(m.typ)
1321 types.extend(m.args)
1322 for arg in types:
1323 if arg in better:
1324 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1325
1326
1327def verify_clone(clazz):
1328 """Verify that clone() isn't implemented; see EJ page 61."""
1329 for m in clazz.methods:
1330 if m.name == "clone":
1331 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1332
1333
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001334def examine_clazz(clazz):
1335 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001336
1337 notice(clazz)
1338
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001339 if clazz.pkg.name.startswith("java"): return
1340 if clazz.pkg.name.startswith("junit"): return
1341 if clazz.pkg.name.startswith("org.apache"): return
1342 if clazz.pkg.name.startswith("org.xml"): return
1343 if clazz.pkg.name.startswith("org.json"): return
1344 if clazz.pkg.name.startswith("org.w3c"): return
Jeff Sharkey331279b2016-02-29 16:02:02 -07001345 if clazz.pkg.name.startswith("android.icu."): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001346
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001347 verify_constants(clazz)
1348 verify_enums(clazz)
1349 verify_class_names(clazz)
1350 verify_method_names(clazz)
1351 verify_callbacks(clazz)
1352 verify_listeners(clazz)
1353 verify_actions(clazz)
1354 verify_extras(clazz)
1355 verify_equals(clazz)
1356 verify_parcelable(clazz)
1357 verify_protected(clazz)
1358 verify_fields(clazz)
1359 verify_register(clazz)
1360 verify_sync(clazz)
1361 verify_intent_builder(clazz)
1362 verify_helper_classes(clazz)
1363 verify_builder(clazz)
1364 verify_aidl(clazz)
1365 verify_internal(clazz)
1366 verify_layering(clazz)
1367 verify_boolean(clazz)
1368 verify_collections(clazz)
1369 verify_flags(clazz)
1370 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001371 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001372 verify_bitset(clazz)
1373 verify_manager(clazz)
1374 verify_boxed(clazz)
1375 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001376 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001377 verify_callback_handlers(clazz)
1378 verify_context_first(clazz)
1379 verify_listener_last(clazz)
1380 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001381 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001382 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001383 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001384 verify_runtime_exceptions(clazz)
1385 verify_error(clazz)
1386 verify_units(clazz)
1387 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001388 verify_member_name_not_kotlin_keyword(clazz)
1389 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001390 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001391 verify_user_handle(clazz)
1392 verify_params(clazz)
1393 verify_services(clazz)
1394 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001395 verify_icu(clazz)
1396 verify_clone(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001397
1398
1399def examine_stream(stream):
1400 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001401 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001402 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001403 noticed = {}
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001404 _parse_stream(stream, examine_clazz)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001405 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001406
1407
1408def examine_api(api):
1409 """Find all style issues in the given parsed API."""
1410 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001411 failures = {}
1412 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001413 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001414 return failures
1415
1416
Jeff Sharkey037458a2014-09-04 15:46:20 -07001417def verify_compat(cur, prev):
1418 """Find any incompatible API changes between two levels."""
1419 global failures
1420
1421 def class_exists(api, test):
1422 return test.fullname in api
1423
1424 def ctor_exists(api, clazz, test):
1425 for m in clazz.ctors:
1426 if m.ident == test.ident: return True
1427 return False
1428
1429 def all_methods(api, clazz):
1430 methods = list(clazz.methods)
1431 if clazz.extends is not None:
1432 methods.extend(all_methods(api, api[clazz.extends]))
1433 return methods
1434
1435 def method_exists(api, clazz, test):
1436 methods = all_methods(api, clazz)
1437 for m in methods:
1438 if m.ident == test.ident: return True
1439 return False
1440
1441 def field_exists(api, clazz, test):
1442 for f in clazz.fields:
1443 if f.ident == test.ident: return True
1444 return False
1445
1446 failures = {}
1447 for key in sorted(prev.keys()):
1448 prev_clazz = prev[key]
1449
1450 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001451 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001452 continue
1453
1454 cur_clazz = cur[key]
1455
1456 for test in prev_clazz.ctors:
1457 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001458 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001459
1460 methods = all_methods(prev, prev_clazz)
1461 for test in methods:
1462 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001463 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001464
1465 for test in prev_clazz.fields:
1466 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001467 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001468
1469 return failures
1470
1471
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001472if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001473 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
1474 patterns. It ignores lint messages from a previous API level, if provided.")
1475 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
1476 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
1477 help="previous.txt")
1478 parser.add_argument("--no-color", action='store_const', const=True,
1479 help="Disable terminal colors")
1480 parser.add_argument("--allow-google", action='store_const', const=True,
1481 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001482 parser.add_argument("--show-noticed", action='store_const', const=True,
1483 help="Show API changes noticed")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001484 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001485
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001486 if args['no_color']:
1487 USE_COLOR = False
1488
1489 if args['allow_google']:
1490 ALLOW_GOOGLE = True
1491
1492 current_file = args['current.txt']
1493 previous_file = args['previous.txt']
1494
1495 with current_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001496 cur_fail, cur_noticed = examine_stream(f)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001497 if not previous_file is None:
1498 with previous_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001499 prev_fail, prev_noticed = examine_stream(f)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001500
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001501 # ignore errors from previous API level
1502 for p in prev_fail:
1503 if p in cur_fail:
1504 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001505
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001506 # ignore classes unchanged from previous API level
1507 for k, v in prev_noticed.iteritems():
1508 if k in cur_noticed and v == cur_noticed[k]:
1509 del cur_noticed[k]
1510
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001511 """
1512 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001513 # look for compatibility issues
1514 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001515
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001516 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1517 for f in sorted(compat_fail):
1518 print compat_fail[f]
1519 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001520 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001521
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001522 if args['show_noticed'] and len(cur_noticed) != 0:
1523 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1524 for f in sorted(cur_noticed.keys()):
1525 print f
1526 print
1527
Jason Monk53b2a732017-11-10 15:43:17 -05001528 if len(cur_fail) != 0:
1529 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1530 for f in sorted(cur_fail):
1531 print cur_fail[f]
1532 print
1533 sys.exit(77)