blob: 9db3f0271598a93ce188dfd14076ee4d5209d76a [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
72 raw = raw.split()
73 self.split = list(raw)
74
75 for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
76 while r in raw: raw.remove(r)
77
78 self.typ = raw[0]
79 self.name = raw[1].strip(";")
80 if len(raw) >= 4 and raw[2] == "=":
81 self.value = raw[3].strip(';"')
82 else:
83 self.value = None
Jeff Sharkey8b141b92018-04-11 10:05:44 -060084 self.ident = ident(self.raw)
Jeff Sharkey037458a2014-09-04 15:46:20 -070085
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -070086 def __hash__(self):
87 return hash(self.raw)
88
Jeff Sharkey8190f4882014-08-28 12:24:07 -070089 def __repr__(self):
90 return self.raw
91
92
93class Method():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070094 def __init__(self, clazz, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070095 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070096 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070097 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070098 self.blame = blame
99
100 # drop generics for now
101 raw = re.sub("<.+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700102
103 raw = re.split("[\s(),;]+", raw)
104 for r in ["", ";"]:
105 while r in raw: raw.remove(r)
106 self.split = list(raw)
107
Filip Pavlisdb234ef2016-11-29 19:06:17 +0000108 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default"]:
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700109 while r in raw: raw.remove(r)
110
111 self.typ = raw[0]
112 self.name = raw[1]
113 self.args = []
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700114 self.throws = []
115 target = self.args
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700116 for r in raw[2:]:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700117 if r == "throws": target = self.throws
118 else: target.append(r)
Jeff Sharkey8b141b92018-04-11 10:05:44 -0600119 self.ident = ident(self.raw)
Jeff Sharkey037458a2014-09-04 15:46:20 -0700120
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700121 def __hash__(self):
122 return hash(self.raw)
123
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700124 def __repr__(self):
125 return self.raw
126
127
128class Class():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700129 def __init__(self, pkg, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700130 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700131 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700132 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700133 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700134 self.ctors = []
135 self.fields = []
136 self.methods = []
137
138 raw = raw.split()
139 self.split = list(raw)
140 if "class" in raw:
141 self.fullname = raw[raw.index("class")+1]
142 elif "interface" in raw:
143 self.fullname = raw[raw.index("interface")+1]
Jeff Sharkey037458a2014-09-04 15:46:20 -0700144 else:
145 raise ValueError("Funky class type %s" % (self.raw))
146
147 if "extends" in raw:
148 self.extends = raw[raw.index("extends")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800149 self.extends_path = self.extends.split(".")
Jeff Sharkey037458a2014-09-04 15:46:20 -0700150 else:
151 self.extends = None
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800152 self.extends_path = []
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700153
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700154 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800155 self.fullname_path = self.fullname.split(".")
156
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700157 self.name = self.fullname[self.fullname.rindex(".")+1:]
158
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700159 def __hash__(self):
160 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
161
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700162 def __repr__(self):
163 return self.raw
164
165
166class Package():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700167 def __init__(self, line, raw, blame):
168 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700169 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700170 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700171
172 raw = raw.split()
173 self.name = raw[raw.index("package")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800174 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700175
176 def __repr__(self):
177 return self.raw
178
179
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800180def _parse_stream(f, clazz_cb=None):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700181 line = 0
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700182 api = {}
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700183 pkg = None
184 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700185 blame = None
186
187 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800188 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700189 line += 1
190 raw = raw.rstrip()
191 match = re_blame.match(raw)
192 if match is not None:
193 blame = match.groups()[0:2]
194 raw = match.groups()[2]
195 else:
196 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700197
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700198 if raw.startswith("package"):
199 pkg = Package(line, raw, blame)
200 elif raw.startswith(" ") and raw.endswith("{"):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800201 # When provided with class callback, we treat as incremental
202 # parse and don't build up entire API
203 if clazz and clazz_cb:
204 clazz_cb(clazz)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700205 clazz = Class(pkg, line, raw, blame)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800206 if not clazz_cb:
207 api[clazz.fullname] = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700208 elif raw.startswith(" ctor"):
209 clazz.ctors.append(Method(clazz, line, raw, blame))
210 elif raw.startswith(" method"):
211 clazz.methods.append(Method(clazz, line, raw, blame))
212 elif raw.startswith(" field"):
213 clazz.fields.append(Field(clazz, line, raw, blame))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700214
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800215 # Handle last trailing class
216 if clazz and clazz_cb:
217 clazz_cb(clazz)
218
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700219 return api
220
221
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700222class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800223 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700224 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700225 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800226 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700227 self.msg = msg
228
229 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800230 self.head = "Error %s" % (rule) if rule else "Error"
231 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 -0700232 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800233 self.head = "Warning %s" % (rule) if rule else "Warning"
234 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 -0700235
236 self.line = clazz.line
237 blame = clazz.blame
238 if detail is not None:
239 dump += "\n in " + repr(detail)
240 self.line = detail.line
241 blame = detail.blame
242 dump += "\n in " + repr(clazz)
243 dump += "\n in " + repr(clazz.pkg)
244 dump += "\n at line " + repr(self.line)
245 if blame is not None:
246 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
247
248 self.dump = dump
249
250 def __repr__(self):
251 return self.dump
252
253
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700254failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700255
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800256def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700257 """Records an API failure to be processed later."""
258 global failures
259
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700260 sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg)
261 sig = sig.replace(" deprecated ", " ")
262
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800263 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700264
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700265
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800266def warn(clazz, detail, rule, msg):
267 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700268
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800269def error(clazz, detail, rule, msg):
270 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700271
272
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700273noticed = {}
274
275def notice(clazz):
276 global noticed
277
278 noticed[clazz.fullname] = hash(clazz)
279
280
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700281def verify_constants(clazz):
282 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700283 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600284 if clazz.fullname.startswith("android.os.Build"): return
285 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700286
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600287 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700288 for f in clazz.fields:
289 if "static" in f.split and "final" in f.split:
290 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800291 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600292 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700293 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
294 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600295 if f.typ in req and f.value is None:
296 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700297
298
299def verify_enums(clazz):
300 """Enums are bad, mmkay?"""
301 if "extends java.lang.Enum" in clazz.raw:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800302 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700303
304
305def verify_class_names(clazz):
306 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700307 if clazz.fullname.startswith("android.opengl"): return
308 if clazz.fullname.startswith("android.renderscript"): return
309 if re.match("android\.R\.[a-z]+", clazz.fullname): return
310
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700311 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800312 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700313 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800314 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700315 if clazz.name.endswith("Impl"):
316 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700317
318
319def verify_method_names(clazz):
320 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700321 if clazz.fullname.startswith("android.opengl"): return
322 if clazz.fullname.startswith("android.renderscript"): return
323 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700324
325 for m in clazz.methods:
326 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800327 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700328 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800329 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700330
331
332def verify_callbacks(clazz):
333 """Verify Callback classes.
334 All callback classes must be abstract.
335 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700336 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700337
338 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800339 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700340 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800341 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700342
343 if clazz.name.endswith("Callback"):
344 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800345 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700346
347 for m in clazz.methods:
348 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800349 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700350
351
352def verify_listeners(clazz):
353 """Verify Listener classes.
354 All Listener classes must be interface.
355 All methods must follow onFoo() naming style.
356 If only a single method, it must match class name:
357 interface OnFooListener { void onFoo() }"""
358
359 if clazz.name.endswith("Listener"):
360 if " abstract class " in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800361 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700362
363 for m in clazz.methods:
364 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800365 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700366
367 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
368 m = clazz.methods[0]
369 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800370 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700371
372
373def verify_actions(clazz):
374 """Verify intent actions.
375 All action names must be named ACTION_FOO.
376 All action values must be scoped by package and match name:
377 package android.foo {
378 String ACTION_BAR = "android.foo.action.BAR";
379 }"""
380 for f in clazz.fields:
381 if f.value is None: continue
382 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700383 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600384 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700385
386 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
387 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
388 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800389 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700390 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700391 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700392 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700393 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700394 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700395 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
396 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700397 else:
398 prefix = clazz.pkg.name + ".action"
399 expected = prefix + "." + f.name[7:]
400 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700401 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700402
403
404def verify_extras(clazz):
405 """Verify intent extras.
406 All extra names must be named EXTRA_FOO.
407 All extra values must be scoped by package and match name:
408 package android.foo {
409 String EXTRA_BAR = "android.foo.extra.BAR";
410 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700411 if clazz.fullname == "android.app.Notification": return
412 if clazz.fullname == "android.appwidget.AppWidgetManager": return
413
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700414 for f in clazz.fields:
415 if f.value is None: continue
416 if f.name.startswith("ACTION_"): continue
417
418 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
419 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
420 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800421 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700422 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700423 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700424 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700425 elif clazz.pkg.name == "android.app.admin":
426 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700427 else:
428 prefix = clazz.pkg.name + ".extra"
429 expected = prefix + "." + f.name[6:]
430 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700431 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700432
433
434def verify_equals(clazz):
435 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700436 eq = False
437 hc = False
438 for m in clazz.methods:
439 if " static " in m.raw: continue
440 if "boolean equals(java.lang.Object)" in m.raw: eq = True
441 if "int hashCode()" in m.raw: hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700442 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800443 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700444
445
446def verify_parcelable(clazz):
447 """Verify that Parcelable objects aren't hiding required bits."""
448 if "implements android.os.Parcelable" in clazz.raw:
449 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
450 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
451 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
452
453 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800454 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700455
Joe LaPenna45380002017-04-20 12:49:48 -0700456 if ((" final class " not in clazz.raw) and
457 (" final deprecated class " not in clazz.raw)):
Jeff Sharkey331279b2016-02-29 16:02:02 -0700458 error(clazz, None, "FW8", "Parcelable classes must be final")
459
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700460 for c in clazz.ctors:
461 if c.args == ["android.os.Parcel"]:
462 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
463
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700464
465def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800466 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700467 for m in clazz.methods:
468 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800469 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700470 for f in clazz.fields:
471 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800472 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700473
474
475def verify_fields(clazz):
476 """Verify that all exposed fields are final.
477 Exposed fields must follow myName style.
478 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700479
480 IGNORE_BARE_FIELDS = [
481 "android.app.ActivityManager.RecentTaskInfo",
482 "android.app.Notification",
483 "android.content.pm.ActivityInfo",
484 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600485 "android.content.pm.ComponentInfo",
486 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700487 "android.content.pm.FeatureGroupInfo",
488 "android.content.pm.InstrumentationInfo",
489 "android.content.pm.PackageInfo",
490 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600491 "android.content.res.Configuration",
492 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700493 "android.os.Message",
494 "android.system.StructPollfd",
495 ]
496
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700497 for f in clazz.fields:
498 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700499 if clazz.fullname in IGNORE_BARE_FIELDS:
500 pass
501 elif clazz.fullname.endswith("LayoutParams"):
502 pass
503 elif clazz.fullname.startswith("android.util.Mutable"):
504 pass
505 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800506 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700507
508 if not "static" in f.split:
509 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800510 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700511
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700512 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800513 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700514
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700515 if re.match("[A-Z_]+", f.name):
516 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800517 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700518
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700519
520def verify_register(clazz):
521 """Verify parity of registration methods.
522 Callback objects use register/unregister methods.
523 Listener objects use add/remove methods."""
524 methods = [ m.name for m in clazz.methods ]
525 for m in clazz.methods:
526 if "Callback" in m.raw:
527 if m.name.startswith("register"):
528 other = "unregister" + m.name[8:]
529 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800530 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700531 if m.name.startswith("unregister"):
532 other = "register" + m.name[10:]
533 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800534 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700535
536 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800537 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700538
539 if "Listener" in m.raw:
540 if m.name.startswith("add"):
541 other = "remove" + m.name[3:]
542 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800543 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700544 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
545 other = "add" + m.name[6:]
546 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800547 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700548
549 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800550 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700551
552
553def verify_sync(clazz):
554 """Verify synchronized methods aren't exposed."""
555 for m in clazz.methods:
556 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800557 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700558
559
560def verify_intent_builder(clazz):
561 """Verify that Intent builders are createFooIntent() style."""
562 if clazz.name == "Intent": return
563
564 for m in clazz.methods:
565 if m.typ == "android.content.Intent":
566 if m.name.startswith("create") and m.name.endswith("Intent"):
567 pass
568 else:
Adam Powell539ea122015-04-10 13:01:37 -0700569 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700570
571
572def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700573 """Verify that helper classes are named consistently with what they extend.
574 All developer extendable methods should be named onFoo()."""
575 test_methods = False
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700576 if "extends android.app.Service" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700577 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700578 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800579 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700580
581 found = False
582 for f in clazz.fields:
583 if f.name == "SERVICE_INTERFACE":
584 found = True
585 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700586 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700587
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700588 if "extends android.content.ContentProvider" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700589 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700590 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800591 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700592
593 found = False
594 for f in clazz.fields:
595 if f.name == "PROVIDER_INTERFACE":
596 found = True
597 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700598 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700599
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700600 if "extends android.content.BroadcastReceiver" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700601 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700602 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800603 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700604
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700605 if "extends android.app.Activity" in clazz.raw:
606 test_methods = True
607 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800608 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700609
610 if test_methods:
611 for m in clazz.methods:
612 if "final" in m.split: continue
613 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700614 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800615 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700616 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800617 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700618
619
620def verify_builder(clazz):
621 """Verify builder classes.
622 Methods should return the builder to enable chaining."""
623 if " extends " in clazz.raw: return
624 if not clazz.name.endswith("Builder"): return
625
626 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800627 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700628
629 has_build = False
630 for m in clazz.methods:
631 if m.name == "build":
632 has_build = True
633 continue
634
635 if m.name.startswith("get"): continue
636 if m.name.startswith("clear"): continue
637
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700638 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800639 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700640
641 if m.name.startswith("set"):
642 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800643 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700644
645 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800646 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700647
648
649def verify_aidl(clazz):
650 """Catch people exposing raw AIDL."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700651 if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800652 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700653
654
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700655def verify_internal(clazz):
656 """Catch people exposing internal classes."""
657 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800658 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700659
660
661def verify_layering(clazz):
662 """Catch package layering violations.
663 For example, something in android.os depending on android.app."""
664 ranking = [
665 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
666 "android.app",
667 "android.widget",
668 "android.view",
669 "android.animation",
670 "android.provider",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700671 ["android.content","android.graphics.drawable"],
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700672 "android.database",
673 "android.graphics",
674 "android.text",
675 "android.os",
676 "android.util"
677 ]
678
679 def rank(p):
680 for i in range(len(ranking)):
681 if isinstance(ranking[i], list):
682 for j in ranking[i]:
683 if p.startswith(j): return i
684 else:
685 if p.startswith(ranking[i]): return i
686
687 cr = rank(clazz.pkg.name)
688 if cr is None: return
689
690 for f in clazz.fields:
691 ir = rank(f.typ)
692 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800693 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700694
695 for m in clazz.methods:
696 ir = rank(m.typ)
697 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800698 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700699 for arg in m.args:
700 ir = rank(arg)
701 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800702 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700703
704
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800705def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800706 """Verifies that boolean accessors are named correctly.
707 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700708
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800709 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
710 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700711
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800712 gets = [ m for m in clazz.methods if is_get(m) ]
713 sets = [ m for m in clazz.methods if is_set(m) ]
714
715 def error_if_exists(methods, trigger, expected, actual):
716 for m in methods:
717 if m.name == actual:
718 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700719
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700720 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800721 if is_get(m):
722 if re.match("is[A-Z]", m.name):
723 target = m.name[2:]
724 expected = "setIs" + target
725 error_if_exists(sets, m.name, expected, "setHas" + target)
726 elif re.match("has[A-Z]", m.name):
727 target = m.name[3:]
728 expected = "setHas" + target
729 error_if_exists(sets, m.name, expected, "setIs" + target)
730 error_if_exists(sets, m.name, expected, "set" + target)
731 elif re.match("get[A-Z]", m.name):
732 target = m.name[3:]
733 expected = "set" + target
734 error_if_exists(sets, m.name, expected, "setIs" + target)
735 error_if_exists(sets, m.name, expected, "setHas" + target)
736
737 if is_set(m):
738 if re.match("set[A-Z]", m.name):
739 target = m.name[3:]
740 expected = "get" + target
741 error_if_exists(sets, m.name, expected, "is" + target)
742 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700743
744
745def verify_collections(clazz):
746 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700747 if clazz.fullname == "android.os.Bundle": return
748
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700749 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
750 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
751 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700752 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800753 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700754 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700755 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800756 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700757
758
759def verify_flags(clazz):
760 """Verifies that flags are non-overlapping."""
761 known = collections.defaultdict(int)
762 for f in clazz.fields:
763 if "FLAG_" in f.name:
764 try:
765 val = int(f.value)
766 except:
767 continue
768
769 scope = f.name[0:f.name.index("FLAG_")]
770 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800771 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700772 known[scope] |= val
773
774
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800775def verify_exception(clazz):
776 """Verifies that methods don't throw generic exceptions."""
777 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700778 for t in m.throws:
779 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
780 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800781
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700782 if t in ["android.os.RemoteException"]:
783 if clazz.name == "android.content.ContentProviderClient": continue
784 if clazz.name == "android.os.Binder": continue
785 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -0700786
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700787 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
788
789 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
790 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -0700791
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800792
793def verify_google(clazz):
794 """Verifies that APIs never reference Google."""
795
796 if re.search("google", clazz.raw, re.IGNORECASE):
797 error(clazz, None, None, "Must never reference Google")
798
799 test = []
800 test.extend(clazz.ctors)
801 test.extend(clazz.fields)
802 test.extend(clazz.methods)
803
804 for t in test:
805 if re.search("google", t.raw, re.IGNORECASE):
806 error(clazz, t, None, "Must never reference Google")
807
808
809def verify_bitset(clazz):
810 """Verifies that we avoid using heavy BitSet."""
811
812 for f in clazz.fields:
813 if f.typ == "java.util.BitSet":
814 error(clazz, f, None, "Field type must not be heavy BitSet")
815
816 for m in clazz.methods:
817 if m.typ == "java.util.BitSet":
818 error(clazz, m, None, "Return type must not be heavy BitSet")
819 for arg in m.args:
820 if arg == "java.util.BitSet":
821 error(clazz, m, None, "Argument type must not be heavy BitSet")
822
823
824def verify_manager(clazz):
825 """Verifies that FooManager is only obtained from Context."""
826
827 if not clazz.name.endswith("Manager"): return
828
829 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800830 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800831
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600832 for m in clazz.methods:
833 if m.typ == clazz.fullname:
834 error(clazz, m, None, "Managers must always be obtained from Context")
835
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800836
837def verify_boxed(clazz):
838 """Verifies that methods avoid boxed primitives."""
839
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800840 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 -0800841
842 for c in clazz.ctors:
843 for arg in c.args:
844 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800845 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800846
847 for f in clazz.fields:
848 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800849 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800850
851 for m in clazz.methods:
852 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800853 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800854 for arg in m.args:
855 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800856 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800857
858
859def verify_static_utils(clazz):
860 """Verifies that helper classes can't be constructed."""
861 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600862 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800863
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600864 # Only care about classes with default constructors
865 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
866 test = []
867 test.extend(clazz.fields)
868 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800869
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600870 if len(test) == 0: return
871 for t in test:
872 if "static" not in t.split:
873 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800874
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800875 error(clazz, None, None, "Fully-static utility classes must not have constructor")
876
877
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800878def verify_overload_args(clazz):
879 """Verifies that method overloads add new arguments at the end."""
880 if clazz.fullname.startswith("android.opengl"): return
881
882 overloads = collections.defaultdict(list)
883 for m in clazz.methods:
884 if "deprecated" in m.split: continue
885 overloads[m.name].append(m)
886
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800887 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800888 if len(methods) <= 1: continue
889
890 # Look for arguments common across all overloads
891 def cluster(args):
892 count = collections.defaultdict(int)
893 res = set()
894 for i in range(len(args)):
895 a = args[i]
896 res.add("%s#%d" % (a, count[a]))
897 count[a] += 1
898 return res
899
900 common_args = cluster(methods[0].args)
901 for m in methods:
902 common_args = common_args & cluster(m.args)
903
904 if len(common_args) == 0: continue
905
906 # Require that all common arguments are present at start of signature
907 locked_sig = None
908 for m in methods:
909 sig = m.args[0:len(common_args)]
910 if not common_args.issubset(cluster(sig)):
911 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
912 elif not locked_sig:
913 locked_sig = sig
914 elif locked_sig != sig:
915 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
916
917
918def verify_callback_handlers(clazz):
919 """Verifies that methods adding listener/callback have overload
920 for specifying delivery thread."""
921
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800922 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800923 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800924 "animation",
925 "view",
926 "graphics",
927 "transition",
928 "widget",
929 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800930 ]
931 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800932 if s in clazz.pkg.name_path: return
933 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800934
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800935 # Ignore UI classes which assume main thread
936 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
937 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
938 if s in clazz.fullname: return
939 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
940 for s in ["Loader"]:
941 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800942
943 found = {}
944 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700945 examine = clazz.ctors + clazz.methods
946 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800947 if m.name.startswith("unregister"): continue
948 if m.name.startswith("remove"): continue
949 if re.match("on[A-Z]+", m.name): continue
950
951 by_name[m.name].append(m)
952
953 for a in m.args:
954 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
955 found[m.name] = m
956
957 for f in found.values():
958 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -0700959 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800960 for m in by_name[f.name]:
961 if "android.os.Handler" in m.args:
962 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -0700963 if "java.util.concurrent.Executor" in m.args:
964 takes_exec = True
965 if not takes_exec:
966 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800967
968
969def verify_context_first(clazz):
970 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800971 examine = clazz.ctors + clazz.methods
972 for m in examine:
973 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800974 if "android.content.Context" in m.args[1:]:
975 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600976 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
977 if "android.content.ContentResolver" in m.args[1:]:
978 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800979
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800980
981def verify_listener_last(clazz):
982 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
983 examine = clazz.ctors + clazz.methods
984 for m in examine:
985 if "Listener" in m.name or "Callback" in m.name: continue
986 found = False
987 for a in m.args:
988 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
989 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700990 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800991 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
992
993
994def verify_resource_names(clazz):
995 """Verifies that resource names have consistent case."""
996 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
997
998 # Resources defined by files are foo_bar_baz
999 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1000 for f in clazz.fields:
1001 if re.match("[a-z1-9_]+$", f.name): continue
1002 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1003
1004 # Resources defined inside files are fooBarBaz
1005 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1006 for f in clazz.fields:
1007 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1008 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1009 if re.match("state_[a-z_]*$", f.name): continue
1010
1011 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1012 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1013
1014 # Styles are FooBar_Baz
1015 if clazz.name in ["style"]:
1016 for f in clazz.fields:
1017 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1018 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001019
1020
Jeff Sharkey331279b2016-02-29 16:02:02 -07001021def verify_files(clazz):
1022 """Verifies that methods accepting File also accept streams."""
1023
1024 has_file = set()
1025 has_stream = set()
1026
1027 test = []
1028 test.extend(clazz.ctors)
1029 test.extend(clazz.methods)
1030
1031 for m in test:
1032 if "java.io.File" in m.args:
1033 has_file.add(m)
1034 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:
1035 has_stream.add(m.name)
1036
1037 for m in has_file:
1038 if m.name not in has_stream:
1039 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1040
1041
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001042def verify_manager_list(clazz):
1043 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1044
1045 if not clazz.name.endswith("Manager"): return
1046
1047 for m in clazz.methods:
1048 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1049 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1050
1051
Jeff Sharkey26c80902016-12-21 13:41:17 -07001052def verify_abstract_inner(clazz):
1053 """Verifies that abstract inner classes are static."""
1054
1055 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
1056 if " abstract " in clazz.raw and " static " not in clazz.raw:
1057 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1058
1059
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001060def verify_runtime_exceptions(clazz):
1061 """Verifies that runtime exceptions aren't listed in throws."""
1062
1063 banned = [
1064 "java.lang.NullPointerException",
1065 "java.lang.ClassCastException",
1066 "java.lang.IndexOutOfBoundsException",
1067 "java.lang.reflect.UndeclaredThrowableException",
1068 "java.lang.reflect.MalformedParametersException",
1069 "java.lang.reflect.MalformedParameterizedTypeException",
1070 "java.lang.invoke.WrongMethodTypeException",
1071 "java.lang.EnumConstantNotPresentException",
1072 "java.lang.IllegalMonitorStateException",
1073 "java.lang.SecurityException",
1074 "java.lang.UnsupportedOperationException",
1075 "java.lang.annotation.AnnotationTypeMismatchException",
1076 "java.lang.annotation.IncompleteAnnotationException",
1077 "java.lang.TypeNotPresentException",
1078 "java.lang.IllegalStateException",
1079 "java.lang.ArithmeticException",
1080 "java.lang.IllegalArgumentException",
1081 "java.lang.ArrayStoreException",
1082 "java.lang.NegativeArraySizeException",
1083 "java.util.MissingResourceException",
1084 "java.util.EmptyStackException",
1085 "java.util.concurrent.CompletionException",
1086 "java.util.concurrent.RejectedExecutionException",
1087 "java.util.IllformedLocaleException",
1088 "java.util.ConcurrentModificationException",
1089 "java.util.NoSuchElementException",
1090 "java.io.UncheckedIOException",
1091 "java.time.DateTimeException",
1092 "java.security.ProviderException",
1093 "java.nio.BufferUnderflowException",
1094 "java.nio.BufferOverflowException",
1095 ]
1096
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001097 examine = clazz.ctors + clazz.methods
1098 for m in examine:
1099 for t in m.throws:
1100 if t in banned:
1101 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001102
1103
1104def verify_error(clazz):
1105 """Verifies that we always use Exception instead of Error."""
1106 if not clazz.extends: return
1107 if clazz.extends.endswith("Error"):
1108 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1109 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1110 error(clazz, None, None, "Exceptions must be named FooException")
1111
1112
1113def verify_units(clazz):
1114 """Verifies that we use consistent naming for units."""
1115
1116 # If we find K, recommend replacing with V
1117 bad = {
1118 "Ns": "Nanos",
1119 "Ms": "Millis or Micros",
1120 "Sec": "Seconds", "Secs": "Seconds",
1121 "Hr": "Hours", "Hrs": "Hours",
1122 "Mo": "Months", "Mos": "Months",
1123 "Yr": "Years", "Yrs": "Years",
1124 "Byte": "Bytes", "Space": "Bytes",
1125 }
1126
1127 for m in clazz.methods:
1128 if m.typ not in ["short","int","long"]: continue
1129 for k, v in bad.iteritems():
1130 if m.name.endswith(k):
1131 error(clazz, m, None, "Expected method name units to be " + v)
1132 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1133 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1134 if m.name.endswith("Seconds"):
1135 error(clazz, m, None, "Returned time values must be in milliseconds")
1136
1137 for m in clazz.methods:
1138 typ = m.typ
1139 if typ == "void":
1140 if len(m.args) != 1: continue
1141 typ = m.args[0]
1142
1143 if m.name.endswith("Fraction") and typ != "float":
1144 error(clazz, m, None, "Fractions must use floats")
1145 if m.name.endswith("Percentage") and typ != "int":
1146 error(clazz, m, None, "Percentage must use ints")
1147
1148
1149def verify_closable(clazz):
1150 """Verifies that classes are AutoClosable."""
1151 if "implements java.lang.AutoCloseable" in clazz.raw: return
1152 if "implements java.io.Closeable" in clazz.raw: return
1153
1154 for m in clazz.methods:
1155 if len(m.args) > 0: continue
1156 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1157 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1158 return
1159
1160
Jake Wharton9e6738f2017-08-23 11:59:55 -04001161def verify_member_name_not_kotlin_keyword(clazz):
1162 """Prevent method names which are keywords in Kotlin."""
1163
1164 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1165 # This list does not include Java keywords as those are already impossible to use.
1166 keywords = [
1167 'as',
1168 'fun',
1169 'in',
1170 'is',
1171 'object',
1172 'typealias',
1173 'val',
1174 'var',
1175 'when',
1176 ]
1177
1178 for m in clazz.methods:
1179 if m.name in keywords:
1180 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1181 for f in clazz.fields:
1182 if f.name in keywords:
1183 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1184
1185
1186def verify_method_name_not_kotlin_operator(clazz):
1187 """Warn about method names which become operators in Kotlin."""
1188
1189 binary = set()
1190
1191 def unique_binary_op(m, op):
1192 if op in binary:
1193 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1194 binary.add(op)
1195
1196 for m in clazz.methods:
1197 if 'static' in m.split:
1198 continue
1199
1200 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1201 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1202 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1203
1204 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1205 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1206 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1207 # practical way of checking that relationship here.
1208 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1209
1210 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1211 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1212 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1213 unique_binary_op(m, m.name)
1214
1215 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1216 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1217 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1218
1219 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1220 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1221 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1222
1223 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1224 if m.name == 'invoke':
1225 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1226
1227 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1228 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1229 and len(m.args) == 1 \
1230 and m.typ == 'void':
1231 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1232 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1233
1234
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001235def verify_collections_over_arrays(clazz):
1236 """Warn that [] should be Collections."""
1237
1238 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1239 for m in clazz.methods:
1240 if m.typ.endswith("[]") and m.typ not in safe:
1241 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1242 for arg in m.args:
1243 if arg.endswith("[]") and arg not in safe:
1244 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1245
1246
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001247def verify_user_handle(clazz):
1248 """Methods taking UserHandle should be ForUser or AsUser."""
1249 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1250 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1251 if clazz.fullname == "android.content.pm.LauncherApps": return
1252 if clazz.fullname == "android.os.UserHandle": return
1253 if clazz.fullname == "android.os.UserManager": return
1254
1255 for m in clazz.methods:
1256 if m.name.endswith("AsUser") or m.name.endswith("ForUser"): continue
1257 if re.match("on[A-Z]+", m.name): continue
1258 if "android.os.UserHandle" in m.args:
1259 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' or 'queryFooForUser'")
1260
1261
1262def verify_params(clazz):
1263 """Parameter classes should be 'Params'."""
1264 if clazz.name.endswith("Params"): return
1265 if clazz.fullname == "android.app.ActivityOptions": return
1266 if clazz.fullname == "android.app.BroadcastOptions": return
1267 if clazz.fullname == "android.os.Bundle": return
1268 if clazz.fullname == "android.os.BaseBundle": return
1269 if clazz.fullname == "android.os.PersistableBundle": return
1270
1271 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1272 for b in bad:
1273 if clazz.name.endswith(b):
1274 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1275
1276
1277def verify_services(clazz):
1278 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1279 if clazz.fullname != "android.content.Context": return
1280
1281 for f in clazz.fields:
1282 if f.typ != "java.lang.String": continue
1283 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1284 if found:
1285 expected = found.group(1).lower()
1286 if f.value != expected:
1287 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1288
1289
1290def verify_tense(clazz):
1291 """Verify tenses of method names."""
1292 if clazz.fullname.startswith("android.opengl"): return
1293
1294 for m in clazz.methods:
1295 if m.name.endswith("Enable"):
1296 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1297
1298
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001299def verify_icu(clazz):
1300 """Verifies that richer ICU replacements are used."""
1301 better = {
1302 "java.util.TimeZone": "android.icu.util.TimeZone",
1303 "java.util.Calendar": "android.icu.util.Calendar",
1304 "java.util.Locale": "android.icu.util.ULocale",
1305 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1306 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1307 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1308 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1309 "java.lang.Character": "android.icu.lang.UCharacter",
1310 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1311 "java.text.Collator": "android.icu.text.Collator",
1312 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1313 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1314 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1315 "java.text.DateFormat": "android.icu.text.DateFormat",
1316 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1317 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1318 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1319 }
1320
1321 for m in clazz.ctors + clazz.methods:
1322 types = []
1323 types.extend(m.typ)
1324 types.extend(m.args)
1325 for arg in types:
1326 if arg in better:
1327 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1328
1329
1330def verify_clone(clazz):
1331 """Verify that clone() isn't implemented; see EJ page 61."""
1332 for m in clazz.methods:
1333 if m.name == "clone":
1334 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1335
1336
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001337def is_interesting(clazz):
1338 """Test if given class is interesting from an Android PoV."""
1339
1340 if clazz.pkg.name.startswith("java"): return False
1341 if clazz.pkg.name.startswith("junit"): return False
1342 if clazz.pkg.name.startswith("org.apache"): return False
1343 if clazz.pkg.name.startswith("org.xml"): return False
1344 if clazz.pkg.name.startswith("org.json"): return False
1345 if clazz.pkg.name.startswith("org.w3c"): return False
1346 if clazz.pkg.name.startswith("android.icu."): return False
1347 return True
1348
1349
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001350def examine_clazz(clazz):
1351 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001352
1353 notice(clazz)
1354
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001355 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001356
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001357 verify_constants(clazz)
1358 verify_enums(clazz)
1359 verify_class_names(clazz)
1360 verify_method_names(clazz)
1361 verify_callbacks(clazz)
1362 verify_listeners(clazz)
1363 verify_actions(clazz)
1364 verify_extras(clazz)
1365 verify_equals(clazz)
1366 verify_parcelable(clazz)
1367 verify_protected(clazz)
1368 verify_fields(clazz)
1369 verify_register(clazz)
1370 verify_sync(clazz)
1371 verify_intent_builder(clazz)
1372 verify_helper_classes(clazz)
1373 verify_builder(clazz)
1374 verify_aidl(clazz)
1375 verify_internal(clazz)
1376 verify_layering(clazz)
1377 verify_boolean(clazz)
1378 verify_collections(clazz)
1379 verify_flags(clazz)
1380 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001381 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001382 verify_bitset(clazz)
1383 verify_manager(clazz)
1384 verify_boxed(clazz)
1385 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001386 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001387 verify_callback_handlers(clazz)
1388 verify_context_first(clazz)
1389 verify_listener_last(clazz)
1390 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001391 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001392 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001393 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001394 verify_runtime_exceptions(clazz)
1395 verify_error(clazz)
1396 verify_units(clazz)
1397 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001398 verify_member_name_not_kotlin_keyword(clazz)
1399 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001400 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001401 verify_user_handle(clazz)
1402 verify_params(clazz)
1403 verify_services(clazz)
1404 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001405 verify_icu(clazz)
1406 verify_clone(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001407
1408
1409def examine_stream(stream):
1410 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001411 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001412 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001413 noticed = {}
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001414 _parse_stream(stream, examine_clazz)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001415 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001416
1417
1418def examine_api(api):
1419 """Find all style issues in the given parsed API."""
1420 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001421 failures = {}
1422 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001423 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001424 return failures
1425
1426
Jeff Sharkey037458a2014-09-04 15:46:20 -07001427def verify_compat(cur, prev):
1428 """Find any incompatible API changes between two levels."""
1429 global failures
1430
1431 def class_exists(api, test):
1432 return test.fullname in api
1433
1434 def ctor_exists(api, clazz, test):
1435 for m in clazz.ctors:
1436 if m.ident == test.ident: return True
1437 return False
1438
1439 def all_methods(api, clazz):
1440 methods = list(clazz.methods)
1441 if clazz.extends is not None:
1442 methods.extend(all_methods(api, api[clazz.extends]))
1443 return methods
1444
1445 def method_exists(api, clazz, test):
1446 methods = all_methods(api, clazz)
1447 for m in methods:
1448 if m.ident == test.ident: return True
1449 return False
1450
1451 def field_exists(api, clazz, test):
1452 for f in clazz.fields:
1453 if f.ident == test.ident: return True
1454 return False
1455
1456 failures = {}
1457 for key in sorted(prev.keys()):
1458 prev_clazz = prev[key]
1459
1460 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001461 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001462 continue
1463
1464 cur_clazz = cur[key]
1465
1466 for test in prev_clazz.ctors:
1467 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001468 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001469
1470 methods = all_methods(prev, prev_clazz)
1471 for test in methods:
1472 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001473 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001474
1475 for test in prev_clazz.fields:
1476 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001477 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001478
1479 return failures
1480
1481
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001482def show_deprecations_at_birth(cur, prev):
1483 """Show API deprecations at birth."""
1484 global failures
1485
1486 # Remove all existing things so we're left with new
1487 for prev_clazz in prev.values():
1488 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001489 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001490
1491 sigs = { i.ident: i for i in prev_clazz.ctors }
1492 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
1493 sigs = { i.ident: i for i in prev_clazz.methods }
1494 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
1495 sigs = { i.ident: i for i in prev_clazz.fields }
1496 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
1497
1498 # Forget about class entirely when nothing new
1499 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
1500 del cur[prev_clazz.fullname]
1501
1502 for clazz in cur.values():
1503 if " deprecated " in clazz.raw and not clazz.fullname in prev:
1504 error(clazz, None, None, "Found API deprecation at birth")
1505
1506 for i in clazz.ctors + clazz.methods + clazz.fields:
1507 if " deprecated " in i.raw:
1508 error(clazz, i, None, "Found API deprecation at birth")
1509
1510 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
1511 format(reset=True)))
1512 for f in sorted(failures):
1513 print failures[f]
1514 print
1515
1516
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001517def show_stats(cur, prev):
1518 """Show API stats."""
1519
1520 stats = collections.defaultdict(int)
1521 for cur_clazz in cur.values():
1522 if not is_interesting(cur_clazz): continue
1523
1524 if cur_clazz.fullname not in prev:
1525 stats['new_classes'] += 1
1526 stats['new_ctors'] += len(cur_clazz.ctors)
1527 stats['new_methods'] += len(cur_clazz.methods)
1528 stats['new_fields'] += len(cur_clazz.fields)
1529 else:
1530 prev_clazz = prev[cur_clazz.fullname]
1531
1532 sigs = { i.ident: i for i in prev_clazz.ctors }
1533 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
1534 sigs = { i.ident: i for i in prev_clazz.methods }
1535 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
1536 sigs = { i.ident: i for i in prev_clazz.fields }
1537 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
1538
1539 if ctors + methods + fields > 0:
1540 stats['extend_classes'] += 1
1541 stats['extend_ctors'] += ctors
1542 stats['extend_methods'] += methods
1543 stats['extend_fields'] += fields
1544
1545 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
1546 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
1547
1548
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001549if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001550 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
1551 patterns. It ignores lint messages from a previous API level, if provided.")
1552 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
1553 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
1554 help="previous.txt")
1555 parser.add_argument("--no-color", action='store_const', const=True,
1556 help="Disable terminal colors")
1557 parser.add_argument("--allow-google", action='store_const', const=True,
1558 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001559 parser.add_argument("--show-noticed", action='store_const', const=True,
1560 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001561 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
1562 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001563 parser.add_argument("--show-stats", action='store_const', const=True,
1564 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001565 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001566
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001567 if args['no_color']:
1568 USE_COLOR = False
1569
1570 if args['allow_google']:
1571 ALLOW_GOOGLE = True
1572
1573 current_file = args['current.txt']
1574 previous_file = args['previous.txt']
1575
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001576 if args['show_deprecations_at_birth']:
1577 with current_file as f:
1578 cur = _parse_stream(f)
1579 with previous_file as f:
1580 prev = _parse_stream(f)
1581 show_deprecations_at_birth(cur, prev)
1582 sys.exit()
1583
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001584 if args['show_stats']:
1585 with current_file as f:
1586 cur = _parse_stream(f)
1587 with previous_file as f:
1588 prev = _parse_stream(f)
1589 show_stats(cur, prev)
1590 sys.exit()
1591
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001592 with current_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001593 cur_fail, cur_noticed = examine_stream(f)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001594 if not previous_file is None:
1595 with previous_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001596 prev_fail, prev_noticed = examine_stream(f)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001597
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001598 # ignore errors from previous API level
1599 for p in prev_fail:
1600 if p in cur_fail:
1601 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001602
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001603 # ignore classes unchanged from previous API level
1604 for k, v in prev_noticed.iteritems():
1605 if k in cur_noticed and v == cur_noticed[k]:
1606 del cur_noticed[k]
1607
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001608 """
1609 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001610 # look for compatibility issues
1611 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001612
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001613 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1614 for f in sorted(compat_fail):
1615 print compat_fail[f]
1616 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001617 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001618
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001619 if args['show_noticed'] and len(cur_noticed) != 0:
1620 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1621 for f in sorted(cur_noticed.keys()):
1622 print f
1623 print
1624
Jason Monk53b2a732017-11-10 15:43:17 -05001625 if len(cur_fail) != 0:
1626 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1627 for f in sorted(cur_fail):
1628 print cur_fail[f]
1629 print
1630 sys.exit(77)