blob: cb8fef946baf5626d4cb4ef34b40385c5c792e2b [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:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001288 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001289
1290 has_arg = "android.os.UserHandle" in m.args
1291 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1292
1293 if clazz.fullname.endswith("Manager") and has_arg:
1294 warn(clazz, m, None, "When a method overload is needed to target a specific "
1295 "UserHandle, callers should be directed to use "
1296 "Context.createPackageContextAsUser() and re-obtain the relevant "
1297 "Manager, and no new API should be added")
1298 elif has_arg and not has_name:
1299 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1300 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001301
1302
1303def verify_params(clazz):
1304 """Parameter classes should be 'Params'."""
1305 if clazz.name.endswith("Params"): return
1306 if clazz.fullname == "android.app.ActivityOptions": return
1307 if clazz.fullname == "android.app.BroadcastOptions": return
1308 if clazz.fullname == "android.os.Bundle": return
1309 if clazz.fullname == "android.os.BaseBundle": return
1310 if clazz.fullname == "android.os.PersistableBundle": return
1311
1312 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1313 for b in bad:
1314 if clazz.name.endswith(b):
1315 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1316
1317
1318def verify_services(clazz):
1319 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1320 if clazz.fullname != "android.content.Context": return
1321
1322 for f in clazz.fields:
1323 if f.typ != "java.lang.String": continue
1324 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1325 if found:
1326 expected = found.group(1).lower()
1327 if f.value != expected:
1328 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1329
1330
1331def verify_tense(clazz):
1332 """Verify tenses of method names."""
1333 if clazz.fullname.startswith("android.opengl"): return
1334
1335 for m in clazz.methods:
1336 if m.name.endswith("Enable"):
1337 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1338
1339
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001340def verify_icu(clazz):
1341 """Verifies that richer ICU replacements are used."""
1342 better = {
1343 "java.util.TimeZone": "android.icu.util.TimeZone",
1344 "java.util.Calendar": "android.icu.util.Calendar",
1345 "java.util.Locale": "android.icu.util.ULocale",
1346 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1347 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1348 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1349 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1350 "java.lang.Character": "android.icu.lang.UCharacter",
1351 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1352 "java.text.Collator": "android.icu.text.Collator",
1353 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1354 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1355 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1356 "java.text.DateFormat": "android.icu.text.DateFormat",
1357 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1358 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1359 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1360 }
1361
1362 for m in clazz.ctors + clazz.methods:
1363 types = []
1364 types.extend(m.typ)
1365 types.extend(m.args)
1366 for arg in types:
1367 if arg in better:
1368 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1369
1370
1371def verify_clone(clazz):
1372 """Verify that clone() isn't implemented; see EJ page 61."""
1373 for m in clazz.methods:
1374 if m.name == "clone":
1375 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1376
1377
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001378def verify_pfd(clazz):
1379 """Verify that android APIs use PFD over FD."""
1380 examine = clazz.ctors + clazz.methods
1381 for m in examine:
1382 if m.typ == "java.io.FileDescriptor":
1383 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1384 if m.typ == "int":
1385 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1386 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1387 for arg in m.args:
1388 if arg == "java.io.FileDescriptor":
1389 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1390
1391 for f in clazz.fields:
1392 if f.typ == "java.io.FileDescriptor":
1393 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1394
1395
1396def verify_numbers(clazz):
1397 """Discourage small numbers types like short and byte."""
1398
1399 discouraged = ["short","byte"]
1400
1401 for c in clazz.ctors:
1402 for arg in c.args:
1403 if arg in discouraged:
1404 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1405
1406 for f in clazz.fields:
1407 if f.typ in discouraged:
1408 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1409
1410 for m in clazz.methods:
1411 if m.typ in discouraged:
1412 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1413 for arg in m.args:
1414 if arg in discouraged:
1415 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1416
1417
1418def verify_singleton(clazz):
1419 """Catch singleton objects with constructors."""
1420
1421 singleton = False
1422 for m in clazz.methods:
1423 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1424 singleton = True
1425
1426 if singleton:
1427 for c in clazz.ctors:
1428 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1429
1430
1431
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001432def is_interesting(clazz):
1433 """Test if given class is interesting from an Android PoV."""
1434
1435 if clazz.pkg.name.startswith("java"): return False
1436 if clazz.pkg.name.startswith("junit"): return False
1437 if clazz.pkg.name.startswith("org.apache"): return False
1438 if clazz.pkg.name.startswith("org.xml"): return False
1439 if clazz.pkg.name.startswith("org.json"): return False
1440 if clazz.pkg.name.startswith("org.w3c"): return False
1441 if clazz.pkg.name.startswith("android.icu."): return False
1442 return True
1443
1444
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001445def examine_clazz(clazz):
1446 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001447
1448 notice(clazz)
1449
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001450 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001451
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001452 verify_constants(clazz)
1453 verify_enums(clazz)
1454 verify_class_names(clazz)
1455 verify_method_names(clazz)
1456 verify_callbacks(clazz)
1457 verify_listeners(clazz)
1458 verify_actions(clazz)
1459 verify_extras(clazz)
1460 verify_equals(clazz)
1461 verify_parcelable(clazz)
1462 verify_protected(clazz)
1463 verify_fields(clazz)
1464 verify_register(clazz)
1465 verify_sync(clazz)
1466 verify_intent_builder(clazz)
1467 verify_helper_classes(clazz)
1468 verify_builder(clazz)
1469 verify_aidl(clazz)
1470 verify_internal(clazz)
1471 verify_layering(clazz)
1472 verify_boolean(clazz)
1473 verify_collections(clazz)
1474 verify_flags(clazz)
1475 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001476 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001477 verify_bitset(clazz)
1478 verify_manager(clazz)
1479 verify_boxed(clazz)
1480 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001481 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001482 verify_callback_handlers(clazz)
1483 verify_context_first(clazz)
1484 verify_listener_last(clazz)
1485 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001486 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001487 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001488 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001489 verify_runtime_exceptions(clazz)
1490 verify_error(clazz)
1491 verify_units(clazz)
1492 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001493 verify_member_name_not_kotlin_keyword(clazz)
1494 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001495 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001496 verify_user_handle(clazz)
1497 verify_params(clazz)
1498 verify_services(clazz)
1499 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001500 verify_icu(clazz)
1501 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001502 verify_pfd(clazz)
1503 verify_numbers(clazz)
1504 verify_singleton(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001505
1506
1507def examine_stream(stream):
1508 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001509 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001510 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001511 noticed = {}
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001512 _parse_stream(stream, examine_clazz)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001513 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001514
1515
1516def examine_api(api):
1517 """Find all style issues in the given parsed API."""
1518 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001519 failures = {}
1520 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001521 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001522 return failures
1523
1524
Jeff Sharkey037458a2014-09-04 15:46:20 -07001525def verify_compat(cur, prev):
1526 """Find any incompatible API changes between two levels."""
1527 global failures
1528
1529 def class_exists(api, test):
1530 return test.fullname in api
1531
1532 def ctor_exists(api, clazz, test):
1533 for m in clazz.ctors:
1534 if m.ident == test.ident: return True
1535 return False
1536
1537 def all_methods(api, clazz):
1538 methods = list(clazz.methods)
1539 if clazz.extends is not None:
1540 methods.extend(all_methods(api, api[clazz.extends]))
1541 return methods
1542
1543 def method_exists(api, clazz, test):
1544 methods = all_methods(api, clazz)
1545 for m in methods:
1546 if m.ident == test.ident: return True
1547 return False
1548
1549 def field_exists(api, clazz, test):
1550 for f in clazz.fields:
1551 if f.ident == test.ident: return True
1552 return False
1553
1554 failures = {}
1555 for key in sorted(prev.keys()):
1556 prev_clazz = prev[key]
1557
1558 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001559 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001560 continue
1561
1562 cur_clazz = cur[key]
1563
1564 for test in prev_clazz.ctors:
1565 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001566 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001567
1568 methods = all_methods(prev, prev_clazz)
1569 for test in methods:
1570 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001571 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001572
1573 for test in prev_clazz.fields:
1574 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001575 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001576
1577 return failures
1578
1579
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001580def show_deprecations_at_birth(cur, prev):
1581 """Show API deprecations at birth."""
1582 global failures
1583
1584 # Remove all existing things so we're left with new
1585 for prev_clazz in prev.values():
1586 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001587 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001588
1589 sigs = { i.ident: i for i in prev_clazz.ctors }
1590 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
1591 sigs = { i.ident: i for i in prev_clazz.methods }
1592 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
1593 sigs = { i.ident: i for i in prev_clazz.fields }
1594 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
1595
1596 # Forget about class entirely when nothing new
1597 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
1598 del cur[prev_clazz.fullname]
1599
1600 for clazz in cur.values():
1601 if " deprecated " in clazz.raw and not clazz.fullname in prev:
1602 error(clazz, None, None, "Found API deprecation at birth")
1603
1604 for i in clazz.ctors + clazz.methods + clazz.fields:
1605 if " deprecated " in i.raw:
1606 error(clazz, i, None, "Found API deprecation at birth")
1607
1608 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
1609 format(reset=True)))
1610 for f in sorted(failures):
1611 print failures[f]
1612 print
1613
1614
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001615def show_stats(cur, prev):
1616 """Show API stats."""
1617
1618 stats = collections.defaultdict(int)
1619 for cur_clazz in cur.values():
1620 if not is_interesting(cur_clazz): continue
1621
1622 if cur_clazz.fullname not in prev:
1623 stats['new_classes'] += 1
1624 stats['new_ctors'] += len(cur_clazz.ctors)
1625 stats['new_methods'] += len(cur_clazz.methods)
1626 stats['new_fields'] += len(cur_clazz.fields)
1627 else:
1628 prev_clazz = prev[cur_clazz.fullname]
1629
1630 sigs = { i.ident: i for i in prev_clazz.ctors }
1631 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
1632 sigs = { i.ident: i for i in prev_clazz.methods }
1633 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
1634 sigs = { i.ident: i for i in prev_clazz.fields }
1635 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
1636
1637 if ctors + methods + fields > 0:
1638 stats['extend_classes'] += 1
1639 stats['extend_ctors'] += ctors
1640 stats['extend_methods'] += methods
1641 stats['extend_fields'] += fields
1642
1643 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
1644 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
1645
1646
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001647if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001648 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
1649 patterns. It ignores lint messages from a previous API level, if provided.")
1650 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
1651 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
1652 help="previous.txt")
1653 parser.add_argument("--no-color", action='store_const', const=True,
1654 help="Disable terminal colors")
1655 parser.add_argument("--allow-google", action='store_const', const=True,
1656 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001657 parser.add_argument("--show-noticed", action='store_const', const=True,
1658 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001659 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
1660 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001661 parser.add_argument("--show-stats", action='store_const', const=True,
1662 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001663 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001664
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001665 if args['no_color']:
1666 USE_COLOR = False
1667
1668 if args['allow_google']:
1669 ALLOW_GOOGLE = True
1670
1671 current_file = args['current.txt']
1672 previous_file = args['previous.txt']
1673
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001674 if args['show_deprecations_at_birth']:
1675 with current_file as f:
1676 cur = _parse_stream(f)
1677 with previous_file as f:
1678 prev = _parse_stream(f)
1679 show_deprecations_at_birth(cur, prev)
1680 sys.exit()
1681
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001682 if args['show_stats']:
1683 with current_file as f:
1684 cur = _parse_stream(f)
1685 with previous_file as f:
1686 prev = _parse_stream(f)
1687 show_stats(cur, prev)
1688 sys.exit()
1689
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001690 with current_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001691 cur_fail, cur_noticed = examine_stream(f)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001692 if not previous_file is None:
1693 with previous_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001694 prev_fail, prev_noticed = examine_stream(f)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001695
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001696 # ignore errors from previous API level
1697 for p in prev_fail:
1698 if p in cur_fail:
1699 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001700
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001701 # ignore classes unchanged from previous API level
1702 for k, v in prev_noticed.iteritems():
1703 if k in cur_noticed and v == cur_noticed[k]:
1704 del cur_noticed[k]
1705
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001706 """
1707 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001708 # look for compatibility issues
1709 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001710
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001711 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1712 for f in sorted(compat_fail):
1713 print compat_fail[f]
1714 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001715 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001716
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001717 if args['show_noticed'] and len(cur_noticed) != 0:
1718 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1719 for f in sorted(cur_noticed.keys()):
1720 print f
1721 print
1722
Jason Monk53b2a732017-11-10 15:43:17 -05001723 if len(cur_fail) != 0:
1724 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1725 for f in sorted(cur_fail):
1726 print cur_fail[f]
1727 print
1728 sys.exit(77)