blob: 91cd1cba964d652385c3efecd7fbeb9b5f63c19e [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
Jeff Sharkey8b141b92018-04-11 10:05:44 -060053def ident(raw):
54 """Strips superficial signature changes, giving us a strong key that
55 can be used to identify members across API levels."""
56 raw = raw.replace(" deprecated ", " ")
57 raw = raw.replace(" synchronized ", " ")
58 raw = raw.replace(" final ", " ")
59 raw = re.sub("<.+?>", "", raw)
60 if " throws " in raw:
61 raw = raw[:raw.index(" throws ")]
62 return raw
63
64
Jeff Sharkey8190f4882014-08-28 12:24:07 -070065class Field():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070066 def __init__(self, clazz, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070067 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070068 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070069 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070070 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -070071
Jeff Sharkey40d67f42018-07-17 13:29:40 -060072 # drop generics for now; may need multiple passes
73 raw = re.sub("<[^<]+?>", "", raw)
74 raw = re.sub("<[^<]+?>", "", raw)
75
Jeff Sharkey8190f4882014-08-28 12:24:07 -070076 raw = raw.split()
77 self.split = list(raw)
78
79 for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
80 while r in raw: raw.remove(r)
81
Jeff Sharkey40d67f42018-07-17 13:29:40 -060082 # ignore annotations for now
83 raw = [ r for r in raw if not r.startswith("@") ]
84
Jeff Sharkey8190f4882014-08-28 12:24:07 -070085 self.typ = raw[0]
86 self.name = raw[1].strip(";")
87 if len(raw) >= 4 and raw[2] == "=":
88 self.value = raw[3].strip(';"')
89 else:
90 self.value = None
Jeff Sharkey8b141b92018-04-11 10:05:44 -060091 self.ident = ident(self.raw)
Jeff Sharkey037458a2014-09-04 15:46:20 -070092
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -070093 def __hash__(self):
94 return hash(self.raw)
95
Jeff Sharkey8190f4882014-08-28 12:24:07 -070096 def __repr__(self):
97 return self.raw
98
99
100class Method():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700101 def __init__(self, clazz, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700102 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700103 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700104 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700105 self.blame = blame
106
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600107 # drop generics for now; may need multiple passes
108 raw = re.sub("<[^<]+?>", "", raw)
109 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700110
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600111 # handle each clause differently
112 raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
113
114 # parse prefixes
115 raw = re.split("[\s]+", raw_prefix)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700116 for r in ["", ";"]:
117 while r in raw: raw.remove(r)
118 self.split = list(raw)
119
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600120 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator"]:
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700121 while r in raw: raw.remove(r)
122
123 self.typ = raw[0]
124 self.name = raw[1]
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600125
126 # parse args
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700127 self.args = []
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600128 for arg in re.split(",\s*", raw_args):
129 arg = re.split("\s", arg)
130 # ignore annotations for now
131 arg = [ a for a in arg if not a.startswith("@") ]
132 if len(arg[0]) > 0:
133 self.args.append(arg[0])
134
135 # parse throws
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700136 self.throws = []
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600137 for throw in re.split(",\s*", raw_throws):
138 self.throws.append(throw)
139
Jeff Sharkey8b141b92018-04-11 10:05:44 -0600140 self.ident = ident(self.raw)
Jeff Sharkey037458a2014-09-04 15:46:20 -0700141
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700142 def __hash__(self):
143 return hash(self.raw)
144
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700145 def __repr__(self):
146 return self.raw
147
148
149class Class():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700150 def __init__(self, pkg, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700151 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700152 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700153 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700154 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700155 self.ctors = []
156 self.fields = []
157 self.methods = []
158
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600159 # drop generics for now; may need multiple passes
160 raw = re.sub("<[^<]+?>", "", raw)
161 raw = re.sub("<[^<]+?>", "", raw)
162
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700163 raw = raw.split()
164 self.split = list(raw)
165 if "class" in raw:
166 self.fullname = raw[raw.index("class")+1]
167 elif "interface" in raw:
168 self.fullname = raw[raw.index("interface")+1]
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600169 elif "@interface" in raw:
170 self.fullname = raw[raw.index("@interface")+1]
Jeff Sharkey037458a2014-09-04 15:46:20 -0700171 else:
172 raise ValueError("Funky class type %s" % (self.raw))
173
174 if "extends" in raw:
175 self.extends = raw[raw.index("extends")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800176 self.extends_path = self.extends.split(".")
Jeff Sharkey037458a2014-09-04 15:46:20 -0700177 else:
178 self.extends = None
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800179 self.extends_path = []
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700180
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700181 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800182 self.fullname_path = self.fullname.split(".")
183
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700184 self.name = self.fullname[self.fullname.rindex(".")+1:]
185
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700186 def __hash__(self):
187 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
188
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700189 def __repr__(self):
190 return self.raw
191
192
193class Package():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700194 def __init__(self, line, raw, blame):
195 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700196 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700197 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700198
199 raw = raw.split()
200 self.name = raw[raw.index("package")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800201 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700202
203 def __repr__(self):
204 return self.raw
205
206
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800207def _parse_stream(f, clazz_cb=None):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700208 line = 0
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700209 api = {}
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700210 pkg = None
211 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700212 blame = None
213
214 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800215 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700216 line += 1
217 raw = raw.rstrip()
218 match = re_blame.match(raw)
219 if match is not None:
220 blame = match.groups()[0:2]
221 raw = match.groups()[2]
222 else:
223 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700224
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700225 if raw.startswith("package"):
226 pkg = Package(line, raw, blame)
227 elif raw.startswith(" ") and raw.endswith("{"):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800228 # When provided with class callback, we treat as incremental
229 # parse and don't build up entire API
230 if clazz and clazz_cb:
231 clazz_cb(clazz)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700232 clazz = Class(pkg, line, raw, blame)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800233 if not clazz_cb:
234 api[clazz.fullname] = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700235 elif raw.startswith(" ctor"):
236 clazz.ctors.append(Method(clazz, line, raw, blame))
237 elif raw.startswith(" method"):
238 clazz.methods.append(Method(clazz, line, raw, blame))
239 elif raw.startswith(" field"):
240 clazz.fields.append(Field(clazz, line, raw, blame))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700241
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800242 # Handle last trailing class
243 if clazz and clazz_cb:
244 clazz_cb(clazz)
245
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700246 return api
247
248
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700249class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800250 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700251 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700252 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800253 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700254 self.msg = msg
255
256 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800257 self.head = "Error %s" % (rule) if rule else "Error"
258 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 -0700259 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800260 self.head = "Warning %s" % (rule) if rule else "Warning"
261 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 -0700262
263 self.line = clazz.line
264 blame = clazz.blame
265 if detail is not None:
266 dump += "\n in " + repr(detail)
267 self.line = detail.line
268 blame = detail.blame
269 dump += "\n in " + repr(clazz)
270 dump += "\n in " + repr(clazz.pkg)
271 dump += "\n at line " + repr(self.line)
272 if blame is not None:
273 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
274
275 self.dump = dump
276
277 def __repr__(self):
278 return self.dump
279
280
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700281failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700282
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800283def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700284 """Records an API failure to be processed later."""
285 global failures
286
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700287 sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg)
288 sig = sig.replace(" deprecated ", " ")
289
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800290 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700291
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700292
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800293def warn(clazz, detail, rule, msg):
294 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700295
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800296def error(clazz, detail, rule, msg):
297 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700298
299
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700300noticed = {}
301
302def notice(clazz):
303 global noticed
304
305 noticed[clazz.fullname] = hash(clazz)
306
307
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700308def verify_constants(clazz):
309 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700310 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600311 if clazz.fullname.startswith("android.os.Build"): return
312 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700313
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600314 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700315 for f in clazz.fields:
316 if "static" in f.split and "final" in f.split:
317 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800318 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600319 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700320 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
321 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600322 if f.typ in req and f.value is None:
323 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700324
325
326def verify_enums(clazz):
327 """Enums are bad, mmkay?"""
328 if "extends java.lang.Enum" in clazz.raw:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800329 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700330
331
332def verify_class_names(clazz):
333 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700334 if clazz.fullname.startswith("android.opengl"): return
335 if clazz.fullname.startswith("android.renderscript"): return
336 if re.match("android\.R\.[a-z]+", clazz.fullname): return
337
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700338 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800339 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700340 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800341 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700342 if clazz.name.endswith("Impl"):
343 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700344
345
346def verify_method_names(clazz):
347 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700348 if clazz.fullname.startswith("android.opengl"): return
349 if clazz.fullname.startswith("android.renderscript"): return
350 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700351
352 for m in clazz.methods:
353 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800354 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700355 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800356 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700357
358
359def verify_callbacks(clazz):
360 """Verify Callback classes.
361 All callback classes must be abstract.
362 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700363 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700364
365 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800366 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700367 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800368 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700369
370 if clazz.name.endswith("Callback"):
371 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800372 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700373
374 for m in clazz.methods:
375 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800376 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700377
378
379def verify_listeners(clazz):
380 """Verify Listener classes.
381 All Listener classes must be interface.
382 All methods must follow onFoo() naming style.
383 If only a single method, it must match class name:
384 interface OnFooListener { void onFoo() }"""
385
386 if clazz.name.endswith("Listener"):
387 if " abstract class " in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800388 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700389
390 for m in clazz.methods:
391 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800392 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700393
394 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
395 m = clazz.methods[0]
396 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800397 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700398
399
400def verify_actions(clazz):
401 """Verify intent actions.
402 All action names must be named ACTION_FOO.
403 All action values must be scoped by package and match name:
404 package android.foo {
405 String ACTION_BAR = "android.foo.action.BAR";
406 }"""
407 for f in clazz.fields:
408 if f.value is None: continue
409 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700410 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600411 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700412
413 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
414 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
415 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800416 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700417 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700418 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700419 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700420 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700421 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700422 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
423 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700424 else:
425 prefix = clazz.pkg.name + ".action"
426 expected = prefix + "." + f.name[7:]
427 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700428 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700429
430
431def verify_extras(clazz):
432 """Verify intent extras.
433 All extra names must be named EXTRA_FOO.
434 All extra values must be scoped by package and match name:
435 package android.foo {
436 String EXTRA_BAR = "android.foo.extra.BAR";
437 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700438 if clazz.fullname == "android.app.Notification": return
439 if clazz.fullname == "android.appwidget.AppWidgetManager": return
440
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700441 for f in clazz.fields:
442 if f.value is None: continue
443 if f.name.startswith("ACTION_"): continue
444
445 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
446 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
447 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800448 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700449 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700450 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700451 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700452 elif clazz.pkg.name == "android.app.admin":
453 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700454 else:
455 prefix = clazz.pkg.name + ".extra"
456 expected = prefix + "." + f.name[6:]
457 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700458 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700459
460
461def verify_equals(clazz):
462 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700463 eq = False
464 hc = False
465 for m in clazz.methods:
466 if " static " in m.raw: continue
467 if "boolean equals(java.lang.Object)" in m.raw: eq = True
468 if "int hashCode()" in m.raw: hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700469 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800470 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700471
472
473def verify_parcelable(clazz):
474 """Verify that Parcelable objects aren't hiding required bits."""
475 if "implements android.os.Parcelable" in clazz.raw:
476 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
477 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
478 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
479
480 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800481 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700482
Joe LaPenna45380002017-04-20 12:49:48 -0700483 if ((" final class " not in clazz.raw) and
484 (" final deprecated class " not in clazz.raw)):
Jeff Sharkey331279b2016-02-29 16:02:02 -0700485 error(clazz, None, "FW8", "Parcelable classes must be final")
486
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700487 for c in clazz.ctors:
488 if c.args == ["android.os.Parcel"]:
489 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
490
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700491
492def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800493 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700494 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600495 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700496 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800497 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700498 for f in clazz.fields:
499 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800500 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700501
502
503def verify_fields(clazz):
504 """Verify that all exposed fields are final.
505 Exposed fields must follow myName style.
506 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700507
508 IGNORE_BARE_FIELDS = [
509 "android.app.ActivityManager.RecentTaskInfo",
510 "android.app.Notification",
511 "android.content.pm.ActivityInfo",
512 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600513 "android.content.pm.ComponentInfo",
514 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700515 "android.content.pm.FeatureGroupInfo",
516 "android.content.pm.InstrumentationInfo",
517 "android.content.pm.PackageInfo",
518 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600519 "android.content.res.Configuration",
520 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700521 "android.os.Message",
522 "android.system.StructPollfd",
523 ]
524
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700525 for f in clazz.fields:
526 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700527 if clazz.fullname in IGNORE_BARE_FIELDS:
528 pass
529 elif clazz.fullname.endswith("LayoutParams"):
530 pass
531 elif clazz.fullname.startswith("android.util.Mutable"):
532 pass
533 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800534 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700535
536 if not "static" in f.split:
537 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800538 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700539
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700540 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800541 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700542
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700543 if re.match("[A-Z_]+", f.name):
544 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800545 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700546
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700547
548def verify_register(clazz):
549 """Verify parity of registration methods.
550 Callback objects use register/unregister methods.
551 Listener objects use add/remove methods."""
552 methods = [ m.name for m in clazz.methods ]
553 for m in clazz.methods:
554 if "Callback" in m.raw:
555 if m.name.startswith("register"):
556 other = "unregister" + m.name[8:]
557 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800558 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700559 if m.name.startswith("unregister"):
560 other = "register" + m.name[10:]
561 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800562 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700563
564 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800565 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700566
567 if "Listener" in m.raw:
568 if m.name.startswith("add"):
569 other = "remove" + m.name[3:]
570 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800571 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700572 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
573 other = "add" + m.name[6:]
574 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800575 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700576
577 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800578 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700579
580
581def verify_sync(clazz):
582 """Verify synchronized methods aren't exposed."""
583 for m in clazz.methods:
584 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800585 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700586
587
588def verify_intent_builder(clazz):
589 """Verify that Intent builders are createFooIntent() style."""
590 if clazz.name == "Intent": return
591
592 for m in clazz.methods:
593 if m.typ == "android.content.Intent":
594 if m.name.startswith("create") and m.name.endswith("Intent"):
595 pass
596 else:
Adam Powell539ea122015-04-10 13:01:37 -0700597 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700598
599
600def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700601 """Verify that helper classes are named consistently with what they extend.
602 All developer extendable methods should be named onFoo()."""
603 test_methods = False
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700604 if "extends android.app.Service" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700605 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700606 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800607 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700608
609 found = False
610 for f in clazz.fields:
611 if f.name == "SERVICE_INTERFACE":
612 found = True
613 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700614 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700615
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700616 if "extends android.content.ContentProvider" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700617 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700618 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800619 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700620
621 found = False
622 for f in clazz.fields:
623 if f.name == "PROVIDER_INTERFACE":
624 found = True
625 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700626 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700627
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700628 if "extends android.content.BroadcastReceiver" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700629 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700630 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800631 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700632
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700633 if "extends android.app.Activity" in clazz.raw:
634 test_methods = True
635 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800636 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700637
638 if test_methods:
639 for m in clazz.methods:
640 if "final" in m.split: continue
641 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700642 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800643 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700644 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800645 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700646
647
648def verify_builder(clazz):
649 """Verify builder classes.
650 Methods should return the builder to enable chaining."""
651 if " extends " in clazz.raw: return
652 if not clazz.name.endswith("Builder"): return
653
654 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800655 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700656
657 has_build = False
658 for m in clazz.methods:
659 if m.name == "build":
660 has_build = True
661 continue
662
663 if m.name.startswith("get"): continue
664 if m.name.startswith("clear"): continue
665
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700666 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800667 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700668
669 if m.name.startswith("set"):
670 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800671 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700672
673 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800674 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700675
676
677def verify_aidl(clazz):
678 """Catch people exposing raw AIDL."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700679 if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800680 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700681
682
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700683def verify_internal(clazz):
684 """Catch people exposing internal classes."""
685 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800686 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700687
688
689def verify_layering(clazz):
690 """Catch package layering violations.
691 For example, something in android.os depending on android.app."""
692 ranking = [
693 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
694 "android.app",
695 "android.widget",
696 "android.view",
697 "android.animation",
698 "android.provider",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700699 ["android.content","android.graphics.drawable"],
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700700 "android.database",
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700701 "android.text",
Siyamed Sinir0a2e15d2018-09-13 16:06:59 -0700702 "android.graphics",
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700703 "android.os",
704 "android.util"
705 ]
706
707 def rank(p):
708 for i in range(len(ranking)):
709 if isinstance(ranking[i], list):
710 for j in ranking[i]:
711 if p.startswith(j): return i
712 else:
713 if p.startswith(ranking[i]): return i
714
715 cr = rank(clazz.pkg.name)
716 if cr is None: return
717
718 for f in clazz.fields:
719 ir = rank(f.typ)
720 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800721 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700722
723 for m in clazz.methods:
724 ir = rank(m.typ)
725 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800726 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700727 for arg in m.args:
728 ir = rank(arg)
729 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800730 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700731
732
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800733def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800734 """Verifies that boolean accessors are named correctly.
735 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700736
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800737 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
738 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700739
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800740 gets = [ m for m in clazz.methods if is_get(m) ]
741 sets = [ m for m in clazz.methods if is_set(m) ]
742
743 def error_if_exists(methods, trigger, expected, actual):
744 for m in methods:
745 if m.name == actual:
746 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700747
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700748 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800749 if is_get(m):
750 if re.match("is[A-Z]", m.name):
751 target = m.name[2:]
752 expected = "setIs" + target
753 error_if_exists(sets, m.name, expected, "setHas" + target)
754 elif re.match("has[A-Z]", m.name):
755 target = m.name[3:]
756 expected = "setHas" + target
757 error_if_exists(sets, m.name, expected, "setIs" + target)
758 error_if_exists(sets, m.name, expected, "set" + target)
759 elif re.match("get[A-Z]", m.name):
760 target = m.name[3:]
761 expected = "set" + target
762 error_if_exists(sets, m.name, expected, "setIs" + target)
763 error_if_exists(sets, m.name, expected, "setHas" + target)
764
765 if is_set(m):
766 if re.match("set[A-Z]", m.name):
767 target = m.name[3:]
768 expected = "get" + target
769 error_if_exists(sets, m.name, expected, "is" + target)
770 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700771
772
773def verify_collections(clazz):
774 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700775 if clazz.fullname == "android.os.Bundle": return
776
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700777 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
778 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
779 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700780 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800781 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700782 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700783 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800784 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700785
786
787def verify_flags(clazz):
788 """Verifies that flags are non-overlapping."""
789 known = collections.defaultdict(int)
790 for f in clazz.fields:
791 if "FLAG_" in f.name:
792 try:
793 val = int(f.value)
794 except:
795 continue
796
797 scope = f.name[0:f.name.index("FLAG_")]
798 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800799 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700800 known[scope] |= val
801
802
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800803def verify_exception(clazz):
804 """Verifies that methods don't throw generic exceptions."""
805 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700806 for t in m.throws:
807 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
808 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800809
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700810 if t in ["android.os.RemoteException"]:
811 if clazz.name == "android.content.ContentProviderClient": continue
812 if clazz.name == "android.os.Binder": continue
813 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -0700814
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700815 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
816
817 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
818 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -0700819
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800820
821def verify_google(clazz):
822 """Verifies that APIs never reference Google."""
823
824 if re.search("google", clazz.raw, re.IGNORECASE):
825 error(clazz, None, None, "Must never reference Google")
826
827 test = []
828 test.extend(clazz.ctors)
829 test.extend(clazz.fields)
830 test.extend(clazz.methods)
831
832 for t in test:
833 if re.search("google", t.raw, re.IGNORECASE):
834 error(clazz, t, None, "Must never reference Google")
835
836
837def verify_bitset(clazz):
838 """Verifies that we avoid using heavy BitSet."""
839
840 for f in clazz.fields:
841 if f.typ == "java.util.BitSet":
842 error(clazz, f, None, "Field type must not be heavy BitSet")
843
844 for m in clazz.methods:
845 if m.typ == "java.util.BitSet":
846 error(clazz, m, None, "Return type must not be heavy BitSet")
847 for arg in m.args:
848 if arg == "java.util.BitSet":
849 error(clazz, m, None, "Argument type must not be heavy BitSet")
850
851
852def verify_manager(clazz):
853 """Verifies that FooManager is only obtained from Context."""
854
855 if not clazz.name.endswith("Manager"): return
856
857 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800858 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800859
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600860 for m in clazz.methods:
861 if m.typ == clazz.fullname:
862 error(clazz, m, None, "Managers must always be obtained from Context")
863
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800864
865def verify_boxed(clazz):
866 """Verifies that methods avoid boxed primitives."""
867
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800868 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 -0800869
870 for c in clazz.ctors:
871 for arg in c.args:
872 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800873 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800874
875 for f in clazz.fields:
876 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800877 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800878
879 for m in clazz.methods:
880 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800881 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800882 for arg in m.args:
883 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800884 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800885
886
887def verify_static_utils(clazz):
888 """Verifies that helper classes can't be constructed."""
889 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600890 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800891
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600892 # Only care about classes with default constructors
893 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
894 test = []
895 test.extend(clazz.fields)
896 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800897
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600898 if len(test) == 0: return
899 for t in test:
900 if "static" not in t.split:
901 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800902
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800903 error(clazz, None, None, "Fully-static utility classes must not have constructor")
904
905
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800906def verify_overload_args(clazz):
907 """Verifies that method overloads add new arguments at the end."""
908 if clazz.fullname.startswith("android.opengl"): return
909
910 overloads = collections.defaultdict(list)
911 for m in clazz.methods:
912 if "deprecated" in m.split: continue
913 overloads[m.name].append(m)
914
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800915 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800916 if len(methods) <= 1: continue
917
918 # Look for arguments common across all overloads
919 def cluster(args):
920 count = collections.defaultdict(int)
921 res = set()
922 for i in range(len(args)):
923 a = args[i]
924 res.add("%s#%d" % (a, count[a]))
925 count[a] += 1
926 return res
927
928 common_args = cluster(methods[0].args)
929 for m in methods:
930 common_args = common_args & cluster(m.args)
931
932 if len(common_args) == 0: continue
933
934 # Require that all common arguments are present at start of signature
935 locked_sig = None
936 for m in methods:
937 sig = m.args[0:len(common_args)]
938 if not common_args.issubset(cluster(sig)):
939 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
940 elif not locked_sig:
941 locked_sig = sig
942 elif locked_sig != sig:
943 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
944
945
946def verify_callback_handlers(clazz):
947 """Verifies that methods adding listener/callback have overload
948 for specifying delivery thread."""
949
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800950 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800951 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800952 "animation",
953 "view",
954 "graphics",
955 "transition",
956 "widget",
957 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800958 ]
959 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800960 if s in clazz.pkg.name_path: return
961 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800962
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800963 # Ignore UI classes which assume main thread
964 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
965 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
966 if s in clazz.fullname: return
967 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
968 for s in ["Loader"]:
969 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800970
971 found = {}
972 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700973 examine = clazz.ctors + clazz.methods
974 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800975 if m.name.startswith("unregister"): continue
976 if m.name.startswith("remove"): continue
977 if re.match("on[A-Z]+", m.name): continue
978
979 by_name[m.name].append(m)
980
981 for a in m.args:
982 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
983 found[m.name] = m
984
985 for f in found.values():
986 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -0700987 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800988 for m in by_name[f.name]:
989 if "android.os.Handler" in m.args:
990 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -0700991 if "java.util.concurrent.Executor" in m.args:
992 takes_exec = True
993 if not takes_exec:
994 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800995
996
997def verify_context_first(clazz):
998 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800999 examine = clazz.ctors + clazz.methods
1000 for m in examine:
1001 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001002 if "android.content.Context" in m.args[1:]:
1003 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001004 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1005 if "android.content.ContentResolver" in m.args[1:]:
1006 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001007
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001008
1009def verify_listener_last(clazz):
1010 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1011 examine = clazz.ctors + clazz.methods
1012 for m in examine:
1013 if "Listener" in m.name or "Callback" in m.name: continue
1014 found = False
1015 for a in m.args:
1016 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1017 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001018 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001019 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1020
1021
1022def verify_resource_names(clazz):
1023 """Verifies that resource names have consistent case."""
1024 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1025
1026 # Resources defined by files are foo_bar_baz
1027 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1028 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001029 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1030 if f.name.startswith("config_"):
1031 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1032
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001033 if re.match("[a-z1-9_]+$", f.name): continue
1034 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1035
1036 # Resources defined inside files are fooBarBaz
1037 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1038 for f in clazz.fields:
1039 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1040 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1041 if re.match("state_[a-z_]*$", f.name): continue
1042
1043 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1044 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1045
1046 # Styles are FooBar_Baz
1047 if clazz.name in ["style"]:
1048 for f in clazz.fields:
1049 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1050 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001051
1052
Jeff Sharkey331279b2016-02-29 16:02:02 -07001053def verify_files(clazz):
1054 """Verifies that methods accepting File also accept streams."""
1055
1056 has_file = set()
1057 has_stream = set()
1058
1059 test = []
1060 test.extend(clazz.ctors)
1061 test.extend(clazz.methods)
1062
1063 for m in test:
1064 if "java.io.File" in m.args:
1065 has_file.add(m)
1066 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:
1067 has_stream.add(m.name)
1068
1069 for m in has_file:
1070 if m.name not in has_stream:
1071 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1072
1073
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001074def verify_manager_list(clazz):
1075 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1076
1077 if not clazz.name.endswith("Manager"): return
1078
1079 for m in clazz.methods:
1080 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1081 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1082
1083
Jeff Sharkey26c80902016-12-21 13:41:17 -07001084def verify_abstract_inner(clazz):
1085 """Verifies that abstract inner classes are static."""
1086
1087 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
1088 if " abstract " in clazz.raw and " static " not in clazz.raw:
1089 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1090
1091
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001092def verify_runtime_exceptions(clazz):
1093 """Verifies that runtime exceptions aren't listed in throws."""
1094
1095 banned = [
1096 "java.lang.NullPointerException",
1097 "java.lang.ClassCastException",
1098 "java.lang.IndexOutOfBoundsException",
1099 "java.lang.reflect.UndeclaredThrowableException",
1100 "java.lang.reflect.MalformedParametersException",
1101 "java.lang.reflect.MalformedParameterizedTypeException",
1102 "java.lang.invoke.WrongMethodTypeException",
1103 "java.lang.EnumConstantNotPresentException",
1104 "java.lang.IllegalMonitorStateException",
1105 "java.lang.SecurityException",
1106 "java.lang.UnsupportedOperationException",
1107 "java.lang.annotation.AnnotationTypeMismatchException",
1108 "java.lang.annotation.IncompleteAnnotationException",
1109 "java.lang.TypeNotPresentException",
1110 "java.lang.IllegalStateException",
1111 "java.lang.ArithmeticException",
1112 "java.lang.IllegalArgumentException",
1113 "java.lang.ArrayStoreException",
1114 "java.lang.NegativeArraySizeException",
1115 "java.util.MissingResourceException",
1116 "java.util.EmptyStackException",
1117 "java.util.concurrent.CompletionException",
1118 "java.util.concurrent.RejectedExecutionException",
1119 "java.util.IllformedLocaleException",
1120 "java.util.ConcurrentModificationException",
1121 "java.util.NoSuchElementException",
1122 "java.io.UncheckedIOException",
1123 "java.time.DateTimeException",
1124 "java.security.ProviderException",
1125 "java.nio.BufferUnderflowException",
1126 "java.nio.BufferOverflowException",
1127 ]
1128
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001129 examine = clazz.ctors + clazz.methods
1130 for m in examine:
1131 for t in m.throws:
1132 if t in banned:
1133 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001134
1135
1136def verify_error(clazz):
1137 """Verifies that we always use Exception instead of Error."""
1138 if not clazz.extends: return
1139 if clazz.extends.endswith("Error"):
1140 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1141 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1142 error(clazz, None, None, "Exceptions must be named FooException")
1143
1144
1145def verify_units(clazz):
1146 """Verifies that we use consistent naming for units."""
1147
1148 # If we find K, recommend replacing with V
1149 bad = {
1150 "Ns": "Nanos",
1151 "Ms": "Millis or Micros",
1152 "Sec": "Seconds", "Secs": "Seconds",
1153 "Hr": "Hours", "Hrs": "Hours",
1154 "Mo": "Months", "Mos": "Months",
1155 "Yr": "Years", "Yrs": "Years",
1156 "Byte": "Bytes", "Space": "Bytes",
1157 }
1158
1159 for m in clazz.methods:
1160 if m.typ not in ["short","int","long"]: continue
1161 for k, v in bad.iteritems():
1162 if m.name.endswith(k):
1163 error(clazz, m, None, "Expected method name units to be " + v)
1164 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1165 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1166 if m.name.endswith("Seconds"):
1167 error(clazz, m, None, "Returned time values must be in milliseconds")
1168
1169 for m in clazz.methods:
1170 typ = m.typ
1171 if typ == "void":
1172 if len(m.args) != 1: continue
1173 typ = m.args[0]
1174
1175 if m.name.endswith("Fraction") and typ != "float":
1176 error(clazz, m, None, "Fractions must use floats")
1177 if m.name.endswith("Percentage") and typ != "int":
1178 error(clazz, m, None, "Percentage must use ints")
1179
1180
1181def verify_closable(clazz):
1182 """Verifies that classes are AutoClosable."""
1183 if "implements java.lang.AutoCloseable" in clazz.raw: return
1184 if "implements java.io.Closeable" in clazz.raw: return
1185
1186 for m in clazz.methods:
1187 if len(m.args) > 0: continue
1188 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1189 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1190 return
1191
1192
Jake Wharton9e6738f2017-08-23 11:59:55 -04001193def verify_member_name_not_kotlin_keyword(clazz):
1194 """Prevent method names which are keywords in Kotlin."""
1195
1196 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1197 # This list does not include Java keywords as those are already impossible to use.
1198 keywords = [
1199 'as',
1200 'fun',
1201 'in',
1202 'is',
1203 'object',
1204 'typealias',
1205 'val',
1206 'var',
1207 'when',
1208 ]
1209
1210 for m in clazz.methods:
1211 if m.name in keywords:
1212 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1213 for f in clazz.fields:
1214 if f.name in keywords:
1215 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1216
1217
1218def verify_method_name_not_kotlin_operator(clazz):
1219 """Warn about method names which become operators in Kotlin."""
1220
1221 binary = set()
1222
1223 def unique_binary_op(m, op):
1224 if op in binary:
1225 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1226 binary.add(op)
1227
1228 for m in clazz.methods:
1229 if 'static' in m.split:
1230 continue
1231
1232 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1233 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1234 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1235
1236 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1237 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1238 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1239 # practical way of checking that relationship here.
1240 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1241
1242 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1243 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1244 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1245 unique_binary_op(m, m.name)
1246
1247 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1248 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1249 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1250
1251 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1252 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1253 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1254
1255 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1256 if m.name == 'invoke':
1257 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1258
1259 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1260 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1261 and len(m.args) == 1 \
1262 and m.typ == 'void':
1263 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1264 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1265
1266
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001267def verify_collections_over_arrays(clazz):
1268 """Warn that [] should be Collections."""
1269
1270 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1271 for m in clazz.methods:
1272 if m.typ.endswith("[]") and m.typ not in safe:
1273 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1274 for arg in m.args:
1275 if arg.endswith("[]") and arg not in safe:
1276 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1277
1278
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001279def verify_user_handle(clazz):
1280 """Methods taking UserHandle should be ForUser or AsUser."""
1281 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1282 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1283 if clazz.fullname == "android.content.pm.LauncherApps": return
1284 if clazz.fullname == "android.os.UserHandle": return
1285 if clazz.fullname == "android.os.UserManager": return
1286
1287 for m in clazz.methods:
1288 if m.name.endswith("AsUser") or m.name.endswith("ForUser"): continue
1289 if re.match("on[A-Z]+", m.name): continue
1290 if "android.os.UserHandle" in m.args:
1291 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' or 'queryFooForUser'")
1292
1293
1294def verify_params(clazz):
1295 """Parameter classes should be 'Params'."""
1296 if clazz.name.endswith("Params"): return
1297 if clazz.fullname == "android.app.ActivityOptions": return
1298 if clazz.fullname == "android.app.BroadcastOptions": return
1299 if clazz.fullname == "android.os.Bundle": return
1300 if clazz.fullname == "android.os.BaseBundle": return
1301 if clazz.fullname == "android.os.PersistableBundle": return
1302
1303 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1304 for b in bad:
1305 if clazz.name.endswith(b):
1306 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1307
1308
1309def verify_services(clazz):
1310 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1311 if clazz.fullname != "android.content.Context": return
1312
1313 for f in clazz.fields:
1314 if f.typ != "java.lang.String": continue
1315 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1316 if found:
1317 expected = found.group(1).lower()
1318 if f.value != expected:
1319 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1320
1321
1322def verify_tense(clazz):
1323 """Verify tenses of method names."""
1324 if clazz.fullname.startswith("android.opengl"): return
1325
1326 for m in clazz.methods:
1327 if m.name.endswith("Enable"):
1328 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1329
1330
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001331def verify_icu(clazz):
1332 """Verifies that richer ICU replacements are used."""
1333 better = {
1334 "java.util.TimeZone": "android.icu.util.TimeZone",
1335 "java.util.Calendar": "android.icu.util.Calendar",
1336 "java.util.Locale": "android.icu.util.ULocale",
1337 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1338 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1339 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1340 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1341 "java.lang.Character": "android.icu.lang.UCharacter",
1342 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1343 "java.text.Collator": "android.icu.text.Collator",
1344 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1345 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1346 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1347 "java.text.DateFormat": "android.icu.text.DateFormat",
1348 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1349 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1350 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1351 }
1352
1353 for m in clazz.ctors + clazz.methods:
1354 types = []
1355 types.extend(m.typ)
1356 types.extend(m.args)
1357 for arg in types:
1358 if arg in better:
1359 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1360
1361
1362def verify_clone(clazz):
1363 """Verify that clone() isn't implemented; see EJ page 61."""
1364 for m in clazz.methods:
1365 if m.name == "clone":
1366 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1367
1368
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001369def verify_pfd(clazz):
1370 """Verify that android APIs use PFD over FD."""
1371 examine = clazz.ctors + clazz.methods
1372 for m in examine:
1373 if m.typ == "java.io.FileDescriptor":
1374 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1375 if m.typ == "int":
1376 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1377 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1378 for arg in m.args:
1379 if arg == "java.io.FileDescriptor":
1380 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1381
1382 for f in clazz.fields:
1383 if f.typ == "java.io.FileDescriptor":
1384 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1385
1386
1387def verify_numbers(clazz):
1388 """Discourage small numbers types like short and byte."""
1389
1390 discouraged = ["short","byte"]
1391
1392 for c in clazz.ctors:
1393 for arg in c.args:
1394 if arg in discouraged:
1395 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1396
1397 for f in clazz.fields:
1398 if f.typ in discouraged:
1399 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1400
1401 for m in clazz.methods:
1402 if m.typ in discouraged:
1403 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1404 for arg in m.args:
1405 if arg in discouraged:
1406 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1407
1408
1409def verify_singleton(clazz):
1410 """Catch singleton objects with constructors."""
1411
1412 singleton = False
1413 for m in clazz.methods:
1414 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1415 singleton = True
1416
1417 if singleton:
1418 for c in clazz.ctors:
1419 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1420
1421
1422
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001423def is_interesting(clazz):
1424 """Test if given class is interesting from an Android PoV."""
1425
1426 if clazz.pkg.name.startswith("java"): return False
1427 if clazz.pkg.name.startswith("junit"): return False
1428 if clazz.pkg.name.startswith("org.apache"): return False
1429 if clazz.pkg.name.startswith("org.xml"): return False
1430 if clazz.pkg.name.startswith("org.json"): return False
1431 if clazz.pkg.name.startswith("org.w3c"): return False
1432 if clazz.pkg.name.startswith("android.icu."): return False
1433 return True
1434
1435
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001436def examine_clazz(clazz):
1437 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001438
1439 notice(clazz)
1440
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001441 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001442
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001443 verify_constants(clazz)
1444 verify_enums(clazz)
1445 verify_class_names(clazz)
1446 verify_method_names(clazz)
1447 verify_callbacks(clazz)
1448 verify_listeners(clazz)
1449 verify_actions(clazz)
1450 verify_extras(clazz)
1451 verify_equals(clazz)
1452 verify_parcelable(clazz)
1453 verify_protected(clazz)
1454 verify_fields(clazz)
1455 verify_register(clazz)
1456 verify_sync(clazz)
1457 verify_intent_builder(clazz)
1458 verify_helper_classes(clazz)
1459 verify_builder(clazz)
1460 verify_aidl(clazz)
1461 verify_internal(clazz)
1462 verify_layering(clazz)
1463 verify_boolean(clazz)
1464 verify_collections(clazz)
1465 verify_flags(clazz)
1466 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001467 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001468 verify_bitset(clazz)
1469 verify_manager(clazz)
1470 verify_boxed(clazz)
1471 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001472 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001473 verify_callback_handlers(clazz)
1474 verify_context_first(clazz)
1475 verify_listener_last(clazz)
1476 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001477 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001478 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001479 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001480 verify_runtime_exceptions(clazz)
1481 verify_error(clazz)
1482 verify_units(clazz)
1483 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001484 verify_member_name_not_kotlin_keyword(clazz)
1485 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001486 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001487 verify_user_handle(clazz)
1488 verify_params(clazz)
1489 verify_services(clazz)
1490 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001491 verify_icu(clazz)
1492 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001493 verify_pfd(clazz)
1494 verify_numbers(clazz)
1495 verify_singleton(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001496
1497
1498def examine_stream(stream):
1499 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001500 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001501 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001502 noticed = {}
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001503 _parse_stream(stream, examine_clazz)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001504 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001505
1506
1507def examine_api(api):
1508 """Find all style issues in the given parsed API."""
1509 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001510 failures = {}
1511 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001512 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001513 return failures
1514
1515
Jeff Sharkey037458a2014-09-04 15:46:20 -07001516def verify_compat(cur, prev):
1517 """Find any incompatible API changes between two levels."""
1518 global failures
1519
1520 def class_exists(api, test):
1521 return test.fullname in api
1522
1523 def ctor_exists(api, clazz, test):
1524 for m in clazz.ctors:
1525 if m.ident == test.ident: return True
1526 return False
1527
1528 def all_methods(api, clazz):
1529 methods = list(clazz.methods)
1530 if clazz.extends is not None:
1531 methods.extend(all_methods(api, api[clazz.extends]))
1532 return methods
1533
1534 def method_exists(api, clazz, test):
1535 methods = all_methods(api, clazz)
1536 for m in methods:
1537 if m.ident == test.ident: return True
1538 return False
1539
1540 def field_exists(api, clazz, test):
1541 for f in clazz.fields:
1542 if f.ident == test.ident: return True
1543 return False
1544
1545 failures = {}
1546 for key in sorted(prev.keys()):
1547 prev_clazz = prev[key]
1548
1549 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001550 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001551 continue
1552
1553 cur_clazz = cur[key]
1554
1555 for test in prev_clazz.ctors:
1556 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001557 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001558
1559 methods = all_methods(prev, prev_clazz)
1560 for test in methods:
1561 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001562 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001563
1564 for test in prev_clazz.fields:
1565 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001566 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001567
1568 return failures
1569
1570
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001571def show_deprecations_at_birth(cur, prev):
1572 """Show API deprecations at birth."""
1573 global failures
1574
1575 # Remove all existing things so we're left with new
1576 for prev_clazz in prev.values():
1577 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001578 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001579
1580 sigs = { i.ident: i for i in prev_clazz.ctors }
1581 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
1582 sigs = { i.ident: i for i in prev_clazz.methods }
1583 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
1584 sigs = { i.ident: i for i in prev_clazz.fields }
1585 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
1586
1587 # Forget about class entirely when nothing new
1588 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
1589 del cur[prev_clazz.fullname]
1590
1591 for clazz in cur.values():
1592 if " deprecated " in clazz.raw and not clazz.fullname in prev:
1593 error(clazz, None, None, "Found API deprecation at birth")
1594
1595 for i in clazz.ctors + clazz.methods + clazz.fields:
1596 if " deprecated " in i.raw:
1597 error(clazz, i, None, "Found API deprecation at birth")
1598
1599 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
1600 format(reset=True)))
1601 for f in sorted(failures):
1602 print failures[f]
1603 print
1604
1605
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001606def show_stats(cur, prev):
1607 """Show API stats."""
1608
1609 stats = collections.defaultdict(int)
1610 for cur_clazz in cur.values():
1611 if not is_interesting(cur_clazz): continue
1612
1613 if cur_clazz.fullname not in prev:
1614 stats['new_classes'] += 1
1615 stats['new_ctors'] += len(cur_clazz.ctors)
1616 stats['new_methods'] += len(cur_clazz.methods)
1617 stats['new_fields'] += len(cur_clazz.fields)
1618 else:
1619 prev_clazz = prev[cur_clazz.fullname]
1620
1621 sigs = { i.ident: i for i in prev_clazz.ctors }
1622 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
1623 sigs = { i.ident: i for i in prev_clazz.methods }
1624 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
1625 sigs = { i.ident: i for i in prev_clazz.fields }
1626 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
1627
1628 if ctors + methods + fields > 0:
1629 stats['extend_classes'] += 1
1630 stats['extend_ctors'] += ctors
1631 stats['extend_methods'] += methods
1632 stats['extend_fields'] += fields
1633
1634 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
1635 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
1636
1637
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001638if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001639 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
1640 patterns. It ignores lint messages from a previous API level, if provided.")
1641 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
1642 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
1643 help="previous.txt")
1644 parser.add_argument("--no-color", action='store_const', const=True,
1645 help="Disable terminal colors")
1646 parser.add_argument("--allow-google", action='store_const', const=True,
1647 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001648 parser.add_argument("--show-noticed", action='store_const', const=True,
1649 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001650 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
1651 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001652 parser.add_argument("--show-stats", action='store_const', const=True,
1653 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001654 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001655
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001656 if args['no_color']:
1657 USE_COLOR = False
1658
1659 if args['allow_google']:
1660 ALLOW_GOOGLE = True
1661
1662 current_file = args['current.txt']
1663 previous_file = args['previous.txt']
1664
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001665 if args['show_deprecations_at_birth']:
1666 with current_file as f:
1667 cur = _parse_stream(f)
1668 with previous_file as f:
1669 prev = _parse_stream(f)
1670 show_deprecations_at_birth(cur, prev)
1671 sys.exit()
1672
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001673 if args['show_stats']:
1674 with current_file as f:
1675 cur = _parse_stream(f)
1676 with previous_file as f:
1677 prev = _parse_stream(f)
1678 show_stats(cur, prev)
1679 sys.exit()
1680
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001681 with current_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001682 cur_fail, cur_noticed = examine_stream(f)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001683 if not previous_file is None:
1684 with previous_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001685 prev_fail, prev_noticed = examine_stream(f)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001686
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001687 # ignore errors from previous API level
1688 for p in prev_fail:
1689 if p in cur_fail:
1690 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001691
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001692 # ignore classes unchanged from previous API level
1693 for k, v in prev_noticed.iteritems():
1694 if k in cur_noticed and v == cur_noticed[k]:
1695 del cur_noticed[k]
1696
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001697 """
1698 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001699 # look for compatibility issues
1700 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001701
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001702 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1703 for f in sorted(compat_fail):
1704 print compat_fail[f]
1705 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001706 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001707
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001708 if args['show_noticed'] and len(cur_noticed) != 0:
1709 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1710 for f in sorted(cur_noticed.keys()):
1711 print f
1712 print
1713
Jason Monk53b2a732017-11-10 15:43:17 -05001714 if len(cur_fail) != 0:
1715 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1716 for f in sorted(cur_fail):
1717 print cur_fail[f]
1718 print
1719 sys.exit(77)