blob: 421e54558df45b7607f9fe412f7a918876873daa [file] [log] [blame]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001#!/usr/bin/env python
2
3# Copyright (C) 2014 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the 'License');
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an 'AS IS' BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Enforces common Android public API design patterns. It ignores lint messages from
19a previous API level, if provided.
20
21Usage: apilint.py current.txt
22Usage: apilint.py current.txt previous.txt
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070023
24You can also splice in blame details like this:
25$ git blame api/current.txt -t -e > /tmp/currentblame.txt
26$ apilint.py /tmp/currentblame.txt previous.txt --no-color
Jeff Sharkey8190f4882014-08-28 12:24:07 -070027"""
28
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070029import re, sys, collections, traceback, argparse
Jeff Sharkey8190f4882014-08-28 12:24:07 -070030
31
32BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
33
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070034ALLOW_GOOGLE = False
35USE_COLOR = True
36
Jeff Sharkey8190f4882014-08-28 12:24:07 -070037def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
38 # manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070039 if not USE_COLOR: return ""
Jeff Sharkey8190f4882014-08-28 12:24:07 -070040 codes = []
41 if reset: codes.append("0")
42 else:
43 if not fg is None: codes.append("3%d" % (fg))
44 if not bg is None:
45 if not bright: codes.append("4%d" % (bg))
46 else: codes.append("10%d" % (bg))
47 if bold: codes.append("1")
48 elif dim: codes.append("2")
49 else: codes.append("22")
50 return "\033[%sm" % (";".join(codes))
51
52
53class Field():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070054 def __init__(self, clazz, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070055 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070056 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070057 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070058 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -070059
60 raw = raw.split()
61 self.split = list(raw)
62
63 for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
64 while r in raw: raw.remove(r)
65
66 self.typ = raw[0]
67 self.name = raw[1].strip(";")
68 if len(raw) >= 4 and raw[2] == "=":
69 self.value = raw[3].strip(';"')
70 else:
71 self.value = None
72
Jeff Sharkey037458a2014-09-04 15:46:20 -070073 self.ident = self.raw.replace(" deprecated ", " ")
74
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -070075 def __hash__(self):
76 return hash(self.raw)
77
Jeff Sharkey8190f4882014-08-28 12:24:07 -070078 def __repr__(self):
79 return self.raw
80
81
82class Method():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070083 def __init__(self, clazz, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070084 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070085 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070086 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070087 self.blame = blame
88
89 # drop generics for now
90 raw = re.sub("<.+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070091
92 raw = re.split("[\s(),;]+", raw)
93 for r in ["", ";"]:
94 while r in raw: raw.remove(r)
95 self.split = list(raw)
96
Filip Pavlisdb234ef2016-11-29 19:06:17 +000097 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default"]:
Jeff Sharkey8190f4882014-08-28 12:24:07 -070098 while r in raw: raw.remove(r)
99
100 self.typ = raw[0]
101 self.name = raw[1]
102 self.args = []
103 for r in raw[2:]:
104 if r == "throws": break
105 self.args.append(r)
106
Jeff Sharkey037458a2014-09-04 15:46:20 -0700107 # identity for compat purposes
108 ident = self.raw
109 ident = ident.replace(" deprecated ", " ")
110 ident = ident.replace(" synchronized ", " ")
111 ident = re.sub("<.+?>", "", ident)
112 if " throws " in ident:
113 ident = ident[:ident.index(" throws ")]
114 self.ident = ident
115
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700116 def __hash__(self):
117 return hash(self.raw)
118
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700119 def __repr__(self):
120 return self.raw
121
122
123class Class():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700124 def __init__(self, pkg, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700125 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700126 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700127 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700128 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700129 self.ctors = []
130 self.fields = []
131 self.methods = []
132
133 raw = raw.split()
134 self.split = list(raw)
135 if "class" in raw:
136 self.fullname = raw[raw.index("class")+1]
137 elif "interface" in raw:
138 self.fullname = raw[raw.index("interface")+1]
Jeff Sharkey037458a2014-09-04 15:46:20 -0700139 else:
140 raise ValueError("Funky class type %s" % (self.raw))
141
142 if "extends" in raw:
143 self.extends = raw[raw.index("extends")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800144 self.extends_path = self.extends.split(".")
Jeff Sharkey037458a2014-09-04 15:46:20 -0700145 else:
146 self.extends = None
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800147 self.extends_path = []
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700148
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700149 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800150 self.fullname_path = self.fullname.split(".")
151
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700152 self.name = self.fullname[self.fullname.rindex(".")+1:]
153
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700154 def __hash__(self):
155 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
156
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700157 def __repr__(self):
158 return self.raw
159
160
161class Package():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700162 def __init__(self, line, raw, blame):
163 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700164 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700165 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700166
167 raw = raw.split()
168 self.name = raw[raw.index("package")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800169 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700170
171 def __repr__(self):
172 return self.raw
173
174
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800175def _parse_stream(f, clazz_cb=None):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700176 line = 0
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700177 api = {}
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700178 pkg = None
179 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700180 blame = None
181
182 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800183 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700184 line += 1
185 raw = raw.rstrip()
186 match = re_blame.match(raw)
187 if match is not None:
188 blame = match.groups()[0:2]
189 raw = match.groups()[2]
190 else:
191 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700192
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700193 if raw.startswith("package"):
194 pkg = Package(line, raw, blame)
195 elif raw.startswith(" ") and raw.endswith("{"):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800196 # When provided with class callback, we treat as incremental
197 # parse and don't build up entire API
198 if clazz and clazz_cb:
199 clazz_cb(clazz)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700200 clazz = Class(pkg, line, raw, blame)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800201 if not clazz_cb:
202 api[clazz.fullname] = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700203 elif raw.startswith(" ctor"):
204 clazz.ctors.append(Method(clazz, line, raw, blame))
205 elif raw.startswith(" method"):
206 clazz.methods.append(Method(clazz, line, raw, blame))
207 elif raw.startswith(" field"):
208 clazz.fields.append(Field(clazz, line, raw, blame))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700209
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800210 # Handle last trailing class
211 if clazz and clazz_cb:
212 clazz_cb(clazz)
213
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700214 return api
215
216
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700217class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800218 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700219 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700220 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800221 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700222 self.msg = msg
223
224 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800225 self.head = "Error %s" % (rule) if rule else "Error"
226 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 -0700227 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800228 self.head = "Warning %s" % (rule) if rule else "Warning"
229 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 -0700230
231 self.line = clazz.line
232 blame = clazz.blame
233 if detail is not None:
234 dump += "\n in " + repr(detail)
235 self.line = detail.line
236 blame = detail.blame
237 dump += "\n in " + repr(clazz)
238 dump += "\n in " + repr(clazz.pkg)
239 dump += "\n at line " + repr(self.line)
240 if blame is not None:
241 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
242
243 self.dump = dump
244
245 def __repr__(self):
246 return self.dump
247
248
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700249failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700250
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800251def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700252 """Records an API failure to be processed later."""
253 global failures
254
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700255 sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg)
256 sig = sig.replace(" deprecated ", " ")
257
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800258 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700259
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700260
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800261def warn(clazz, detail, rule, msg):
262 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700263
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800264def error(clazz, detail, rule, msg):
265 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700266
267
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700268noticed = {}
269
270def notice(clazz):
271 global noticed
272
273 noticed[clazz.fullname] = hash(clazz)
274
275
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700276def verify_constants(clazz):
277 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700278 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600279 if clazz.fullname.startswith("android.os.Build"): return
280 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700281
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600282 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700283 for f in clazz.fields:
284 if "static" in f.split and "final" in f.split:
285 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800286 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600287 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700288 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
289 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600290 if f.typ in req and f.value is None:
291 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700292
293
294def verify_enums(clazz):
295 """Enums are bad, mmkay?"""
296 if "extends java.lang.Enum" in clazz.raw:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800297 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700298
299
300def verify_class_names(clazz):
301 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700302 if clazz.fullname.startswith("android.opengl"): return
303 if clazz.fullname.startswith("android.renderscript"): return
304 if re.match("android\.R\.[a-z]+", clazz.fullname): return
305
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700306 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800307 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700308 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800309 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700310
311
312def verify_method_names(clazz):
313 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700314 if clazz.fullname.startswith("android.opengl"): return
315 if clazz.fullname.startswith("android.renderscript"): return
316 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700317
318 for m in clazz.methods:
319 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800320 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700321 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800322 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700323
324
325def verify_callbacks(clazz):
326 """Verify Callback classes.
327 All callback classes must be abstract.
328 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700329 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700330
331 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800332 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700333 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800334 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700335
336 if clazz.name.endswith("Callback"):
337 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800338 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700339
340 for m in clazz.methods:
341 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800342 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700343
344
345def verify_listeners(clazz):
346 """Verify Listener classes.
347 All Listener classes must be interface.
348 All methods must follow onFoo() naming style.
349 If only a single method, it must match class name:
350 interface OnFooListener { void onFoo() }"""
351
352 if clazz.name.endswith("Listener"):
353 if " abstract class " in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800354 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700355
356 for m in clazz.methods:
357 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800358 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700359
360 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
361 m = clazz.methods[0]
362 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800363 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700364
365
366def verify_actions(clazz):
367 """Verify intent actions.
368 All action names must be named ACTION_FOO.
369 All action values must be scoped by package and match name:
370 package android.foo {
371 String ACTION_BAR = "android.foo.action.BAR";
372 }"""
373 for f in clazz.fields:
374 if f.value is None: continue
375 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700376 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600377 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700378
379 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
380 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
381 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800382 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700383 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700384 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700385 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700386 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700387 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700388 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
389 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700390 else:
391 prefix = clazz.pkg.name + ".action"
392 expected = prefix + "." + f.name[7:]
393 if f.value != expected:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800394 error(clazz, f, "C4", "Inconsistent action value; expected %s" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700395
396
397def verify_extras(clazz):
398 """Verify intent extras.
399 All extra names must be named EXTRA_FOO.
400 All extra values must be scoped by package and match name:
401 package android.foo {
402 String EXTRA_BAR = "android.foo.extra.BAR";
403 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700404 if clazz.fullname == "android.app.Notification": return
405 if clazz.fullname == "android.appwidget.AppWidgetManager": return
406
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700407 for f in clazz.fields:
408 if f.value is None: continue
409 if f.name.startswith("ACTION_"): continue
410
411 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
412 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
413 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800414 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700415 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700416 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700417 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700418 elif clazz.pkg.name == "android.app.admin":
419 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700420 else:
421 prefix = clazz.pkg.name + ".extra"
422 expected = prefix + "." + f.name[6:]
423 if f.value != expected:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800424 error(clazz, f, "C4", "Inconsistent extra value; expected %s" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700425
426
427def verify_equals(clazz):
428 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700429 eq = False
430 hc = False
431 for m in clazz.methods:
432 if " static " in m.raw: continue
433 if "boolean equals(java.lang.Object)" in m.raw: eq = True
434 if "int hashCode()" in m.raw: hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700435 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800436 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700437
438
439def verify_parcelable(clazz):
440 """Verify that Parcelable objects aren't hiding required bits."""
441 if "implements android.os.Parcelable" in clazz.raw:
442 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
443 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
444 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
445
446 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800447 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700448
Joe LaPenna45380002017-04-20 12:49:48 -0700449 if ((" final class " not in clazz.raw) and
450 (" final deprecated class " not in clazz.raw)):
Jeff Sharkey331279b2016-02-29 16:02:02 -0700451 error(clazz, None, "FW8", "Parcelable classes must be final")
452
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700453
454def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800455 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700456 for m in clazz.methods:
457 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800458 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700459 for f in clazz.fields:
460 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800461 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700462
463
464def verify_fields(clazz):
465 """Verify that all exposed fields are final.
466 Exposed fields must follow myName style.
467 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700468
469 IGNORE_BARE_FIELDS = [
470 "android.app.ActivityManager.RecentTaskInfo",
471 "android.app.Notification",
472 "android.content.pm.ActivityInfo",
473 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600474 "android.content.pm.ComponentInfo",
475 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700476 "android.content.pm.FeatureGroupInfo",
477 "android.content.pm.InstrumentationInfo",
478 "android.content.pm.PackageInfo",
479 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600480 "android.content.res.Configuration",
481 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700482 "android.os.Message",
483 "android.system.StructPollfd",
484 ]
485
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700486 for f in clazz.fields:
487 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700488 if clazz.fullname in IGNORE_BARE_FIELDS:
489 pass
490 elif clazz.fullname.endswith("LayoutParams"):
491 pass
492 elif clazz.fullname.startswith("android.util.Mutable"):
493 pass
494 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800495 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700496
497 if not "static" in f.split:
498 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800499 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700500
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700501 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800502 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700503
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700504 if re.match("[A-Z_]+", f.name):
505 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800506 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700507
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700508
509def verify_register(clazz):
510 """Verify parity of registration methods.
511 Callback objects use register/unregister methods.
512 Listener objects use add/remove methods."""
513 methods = [ m.name for m in clazz.methods ]
514 for m in clazz.methods:
515 if "Callback" in m.raw:
516 if m.name.startswith("register"):
517 other = "unregister" + m.name[8:]
518 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800519 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700520 if m.name.startswith("unregister"):
521 other = "register" + m.name[10:]
522 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800523 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700524
525 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800526 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700527
528 if "Listener" in m.raw:
529 if m.name.startswith("add"):
530 other = "remove" + m.name[3:]
531 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800532 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700533 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
534 other = "add" + m.name[6:]
535 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800536 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700537
538 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800539 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700540
541
542def verify_sync(clazz):
543 """Verify synchronized methods aren't exposed."""
544 for m in clazz.methods:
545 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800546 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700547
548
549def verify_intent_builder(clazz):
550 """Verify that Intent builders are createFooIntent() style."""
551 if clazz.name == "Intent": return
552
553 for m in clazz.methods:
554 if m.typ == "android.content.Intent":
555 if m.name.startswith("create") and m.name.endswith("Intent"):
556 pass
557 else:
Adam Powell539ea122015-04-10 13:01:37 -0700558 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700559
560
561def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700562 """Verify that helper classes are named consistently with what they extend.
563 All developer extendable methods should be named onFoo()."""
564 test_methods = False
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700565 if "extends android.app.Service" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700566 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700567 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800568 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700569
570 found = False
571 for f in clazz.fields:
572 if f.name == "SERVICE_INTERFACE":
573 found = True
574 if f.value != clazz.fullname:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800575 error(clazz, f, "C4", "Inconsistent interface constant; expected %s" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700576
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700577 if "extends android.content.ContentProvider" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700578 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700579 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800580 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700581
582 found = False
583 for f in clazz.fields:
584 if f.name == "PROVIDER_INTERFACE":
585 found = True
586 if f.value != clazz.fullname:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800587 error(clazz, f, "C4", "Inconsistent interface constant; expected %s" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700588
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700589 if "extends android.content.BroadcastReceiver" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700590 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700591 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800592 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700593
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700594 if "extends android.app.Activity" in clazz.raw:
595 test_methods = True
596 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800597 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700598
599 if test_methods:
600 for m in clazz.methods:
601 if "final" in m.split: continue
602 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700603 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800604 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700605 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800606 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700607
608
609def verify_builder(clazz):
610 """Verify builder classes.
611 Methods should return the builder to enable chaining."""
612 if " extends " in clazz.raw: return
613 if not clazz.name.endswith("Builder"): return
614
615 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800616 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700617
618 has_build = False
619 for m in clazz.methods:
620 if m.name == "build":
621 has_build = True
622 continue
623
624 if m.name.startswith("get"): continue
625 if m.name.startswith("clear"): continue
626
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700627 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800628 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700629
630 if m.name.startswith("set"):
631 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800632 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700633
634 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800635 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700636
637
638def verify_aidl(clazz):
639 """Catch people exposing raw AIDL."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700640 if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800641 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700642
643
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700644def verify_internal(clazz):
645 """Catch people exposing internal classes."""
646 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800647 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700648
649
650def verify_layering(clazz):
651 """Catch package layering violations.
652 For example, something in android.os depending on android.app."""
653 ranking = [
654 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
655 "android.app",
656 "android.widget",
657 "android.view",
658 "android.animation",
659 "android.provider",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700660 ["android.content","android.graphics.drawable"],
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700661 "android.database",
662 "android.graphics",
663 "android.text",
664 "android.os",
665 "android.util"
666 ]
667
668 def rank(p):
669 for i in range(len(ranking)):
670 if isinstance(ranking[i], list):
671 for j in ranking[i]:
672 if p.startswith(j): return i
673 else:
674 if p.startswith(ranking[i]): return i
675
676 cr = rank(clazz.pkg.name)
677 if cr is None: return
678
679 for f in clazz.fields:
680 ir = rank(f.typ)
681 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800682 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700683
684 for m in clazz.methods:
685 ir = rank(m.typ)
686 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800687 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700688 for arg in m.args:
689 ir = rank(arg)
690 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800691 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700692
693
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800694def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800695 """Verifies that boolean accessors are named correctly.
696 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700697
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800698 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
699 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700700
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800701 gets = [ m for m in clazz.methods if is_get(m) ]
702 sets = [ m for m in clazz.methods if is_set(m) ]
703
704 def error_if_exists(methods, trigger, expected, actual):
705 for m in methods:
706 if m.name == actual:
707 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700708
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700709 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800710 if is_get(m):
711 if re.match("is[A-Z]", m.name):
712 target = m.name[2:]
713 expected = "setIs" + target
714 error_if_exists(sets, m.name, expected, "setHas" + target)
715 elif re.match("has[A-Z]", m.name):
716 target = m.name[3:]
717 expected = "setHas" + target
718 error_if_exists(sets, m.name, expected, "setIs" + target)
719 error_if_exists(sets, m.name, expected, "set" + target)
720 elif re.match("get[A-Z]", m.name):
721 target = m.name[3:]
722 expected = "set" + target
723 error_if_exists(sets, m.name, expected, "setIs" + target)
724 error_if_exists(sets, m.name, expected, "setHas" + target)
725
726 if is_set(m):
727 if re.match("set[A-Z]", m.name):
728 target = m.name[3:]
729 expected = "get" + target
730 error_if_exists(sets, m.name, expected, "is" + target)
731 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700732
733
734def verify_collections(clazz):
735 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700736 if clazz.fullname == "android.os.Bundle": return
737
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700738 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
739 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
740 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700741 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800742 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700743 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700744 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800745 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700746
747
748def verify_flags(clazz):
749 """Verifies that flags are non-overlapping."""
750 known = collections.defaultdict(int)
751 for f in clazz.fields:
752 if "FLAG_" in f.name:
753 try:
754 val = int(f.value)
755 except:
756 continue
757
758 scope = f.name[0:f.name.index("FLAG_")]
759 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800760 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700761 known[scope] |= val
762
763
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800764def verify_exception(clazz):
765 """Verifies that methods don't throw generic exceptions."""
766 for m in clazz.methods:
767 if "throws java.lang.Exception" in m.raw or "throws java.lang.Throwable" in m.raw or "throws java.lang.Error" in m.raw:
768 error(clazz, m, "S1", "Methods must not throw generic exceptions")
769
Jeff Sharkey331279b2016-02-29 16:02:02 -0700770 if "throws android.os.RemoteException" in m.raw:
771 if clazz.name == "android.content.ContentProviderClient": continue
772 if clazz.name == "android.os.Binder": continue
773 if clazz.name == "android.os.IBinder": continue
774
775 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
776
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800777
778def verify_google(clazz):
779 """Verifies that APIs never reference Google."""
780
781 if re.search("google", clazz.raw, re.IGNORECASE):
782 error(clazz, None, None, "Must never reference Google")
783
784 test = []
785 test.extend(clazz.ctors)
786 test.extend(clazz.fields)
787 test.extend(clazz.methods)
788
789 for t in test:
790 if re.search("google", t.raw, re.IGNORECASE):
791 error(clazz, t, None, "Must never reference Google")
792
793
794def verify_bitset(clazz):
795 """Verifies that we avoid using heavy BitSet."""
796
797 for f in clazz.fields:
798 if f.typ == "java.util.BitSet":
799 error(clazz, f, None, "Field type must not be heavy BitSet")
800
801 for m in clazz.methods:
802 if m.typ == "java.util.BitSet":
803 error(clazz, m, None, "Return type must not be heavy BitSet")
804 for arg in m.args:
805 if arg == "java.util.BitSet":
806 error(clazz, m, None, "Argument type must not be heavy BitSet")
807
808
809def verify_manager(clazz):
810 """Verifies that FooManager is only obtained from Context."""
811
812 if not clazz.name.endswith("Manager"): return
813
814 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800815 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800816
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600817 for m in clazz.methods:
818 if m.typ == clazz.fullname:
819 error(clazz, m, None, "Managers must always be obtained from Context")
820
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800821
822def verify_boxed(clazz):
823 """Verifies that methods avoid boxed primitives."""
824
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800825 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 -0800826
827 for c in clazz.ctors:
828 for arg in c.args:
829 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800830 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800831
832 for f in clazz.fields:
833 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800834 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800835
836 for m in clazz.methods:
837 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800838 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800839 for arg in m.args:
840 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800841 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800842
843
844def verify_static_utils(clazz):
845 """Verifies that helper classes can't be constructed."""
846 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600847 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800848
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600849 # Only care about classes with default constructors
850 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
851 test = []
852 test.extend(clazz.fields)
853 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800854
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600855 if len(test) == 0: return
856 for t in test:
857 if "static" not in t.split:
858 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800859
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800860 error(clazz, None, None, "Fully-static utility classes must not have constructor")
861
862
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800863def verify_overload_args(clazz):
864 """Verifies that method overloads add new arguments at the end."""
865 if clazz.fullname.startswith("android.opengl"): return
866
867 overloads = collections.defaultdict(list)
868 for m in clazz.methods:
869 if "deprecated" in m.split: continue
870 overloads[m.name].append(m)
871
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800872 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800873 if len(methods) <= 1: continue
874
875 # Look for arguments common across all overloads
876 def cluster(args):
877 count = collections.defaultdict(int)
878 res = set()
879 for i in range(len(args)):
880 a = args[i]
881 res.add("%s#%d" % (a, count[a]))
882 count[a] += 1
883 return res
884
885 common_args = cluster(methods[0].args)
886 for m in methods:
887 common_args = common_args & cluster(m.args)
888
889 if len(common_args) == 0: continue
890
891 # Require that all common arguments are present at start of signature
892 locked_sig = None
893 for m in methods:
894 sig = m.args[0:len(common_args)]
895 if not common_args.issubset(cluster(sig)):
896 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
897 elif not locked_sig:
898 locked_sig = sig
899 elif locked_sig != sig:
900 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
901
902
903def verify_callback_handlers(clazz):
904 """Verifies that methods adding listener/callback have overload
905 for specifying delivery thread."""
906
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800907 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800908 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800909 "animation",
910 "view",
911 "graphics",
912 "transition",
913 "widget",
914 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800915 ]
916 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800917 if s in clazz.pkg.name_path: return
918 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800919
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800920 # Ignore UI classes which assume main thread
921 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
922 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
923 if s in clazz.fullname: return
924 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
925 for s in ["Loader"]:
926 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800927
928 found = {}
929 by_name = collections.defaultdict(list)
930 for m in clazz.methods:
931 if m.name.startswith("unregister"): continue
932 if m.name.startswith("remove"): continue
933 if re.match("on[A-Z]+", m.name): continue
934
935 by_name[m.name].append(m)
936
937 for a in m.args:
938 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
939 found[m.name] = m
940
941 for f in found.values():
942 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -0700943 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800944 for m in by_name[f.name]:
945 if "android.os.Handler" in m.args:
946 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -0700947 if "java.util.concurrent.Executor" in m.args:
948 takes_exec = True
949 if not takes_exec:
950 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800951
952
953def verify_context_first(clazz):
954 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800955 examine = clazz.ctors + clazz.methods
956 for m in examine:
957 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800958 if "android.content.Context" in m.args[1:]:
959 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600960 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
961 if "android.content.ContentResolver" in m.args[1:]:
962 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800963
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800964
965def verify_listener_last(clazz):
966 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
967 examine = clazz.ctors + clazz.methods
968 for m in examine:
969 if "Listener" in m.name or "Callback" in m.name: continue
970 found = False
971 for a in m.args:
972 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
973 found = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -0700974 elif found and a != "android.os.Handler" and a != "java.util.concurrent.Executor":
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800975 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
976
977
978def verify_resource_names(clazz):
979 """Verifies that resource names have consistent case."""
980 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
981
982 # Resources defined by files are foo_bar_baz
983 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
984 for f in clazz.fields:
985 if re.match("[a-z1-9_]+$", f.name): continue
986 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
987
988 # Resources defined inside files are fooBarBaz
989 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
990 for f in clazz.fields:
991 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
992 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
993 if re.match("state_[a-z_]*$", f.name): continue
994
995 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
996 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
997
998 # Styles are FooBar_Baz
999 if clazz.name in ["style"]:
1000 for f in clazz.fields:
1001 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1002 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001003
1004
Jeff Sharkey331279b2016-02-29 16:02:02 -07001005def verify_files(clazz):
1006 """Verifies that methods accepting File also accept streams."""
1007
1008 has_file = set()
1009 has_stream = set()
1010
1011 test = []
1012 test.extend(clazz.ctors)
1013 test.extend(clazz.methods)
1014
1015 for m in test:
1016 if "java.io.File" in m.args:
1017 has_file.add(m)
1018 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:
1019 has_stream.add(m.name)
1020
1021 for m in has_file:
1022 if m.name not in has_stream:
1023 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1024
1025
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001026def verify_manager_list(clazz):
1027 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1028
1029 if not clazz.name.endswith("Manager"): return
1030
1031 for m in clazz.methods:
1032 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1033 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1034
1035
Jeff Sharkey26c80902016-12-21 13:41:17 -07001036def verify_abstract_inner(clazz):
1037 """Verifies that abstract inner classes are static."""
1038
1039 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
1040 if " abstract " in clazz.raw and " static " not in clazz.raw:
1041 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1042
1043
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001044def verify_runtime_exceptions(clazz):
1045 """Verifies that runtime exceptions aren't listed in throws."""
1046
1047 banned = [
1048 "java.lang.NullPointerException",
1049 "java.lang.ClassCastException",
1050 "java.lang.IndexOutOfBoundsException",
1051 "java.lang.reflect.UndeclaredThrowableException",
1052 "java.lang.reflect.MalformedParametersException",
1053 "java.lang.reflect.MalformedParameterizedTypeException",
1054 "java.lang.invoke.WrongMethodTypeException",
1055 "java.lang.EnumConstantNotPresentException",
1056 "java.lang.IllegalMonitorStateException",
1057 "java.lang.SecurityException",
1058 "java.lang.UnsupportedOperationException",
1059 "java.lang.annotation.AnnotationTypeMismatchException",
1060 "java.lang.annotation.IncompleteAnnotationException",
1061 "java.lang.TypeNotPresentException",
1062 "java.lang.IllegalStateException",
1063 "java.lang.ArithmeticException",
1064 "java.lang.IllegalArgumentException",
1065 "java.lang.ArrayStoreException",
1066 "java.lang.NegativeArraySizeException",
1067 "java.util.MissingResourceException",
1068 "java.util.EmptyStackException",
1069 "java.util.concurrent.CompletionException",
1070 "java.util.concurrent.RejectedExecutionException",
1071 "java.util.IllformedLocaleException",
1072 "java.util.ConcurrentModificationException",
1073 "java.util.NoSuchElementException",
1074 "java.io.UncheckedIOException",
1075 "java.time.DateTimeException",
1076 "java.security.ProviderException",
1077 "java.nio.BufferUnderflowException",
1078 "java.nio.BufferOverflowException",
1079 ]
1080
1081 test = []
1082 test.extend(clazz.ctors)
1083 test.extend(clazz.methods)
1084
1085 for t in test:
1086 if " throws " not in t.raw: continue
1087 throws = t.raw[t.raw.index(" throws "):]
1088 for b in banned:
1089 if b in throws:
1090 error(clazz, t, None, "Methods must not mention RuntimeException subclasses in throws clauses")
1091
1092
1093def verify_error(clazz):
1094 """Verifies that we always use Exception instead of Error."""
1095 if not clazz.extends: return
1096 if clazz.extends.endswith("Error"):
1097 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1098 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1099 error(clazz, None, None, "Exceptions must be named FooException")
1100
1101
1102def verify_units(clazz):
1103 """Verifies that we use consistent naming for units."""
1104
1105 # If we find K, recommend replacing with V
1106 bad = {
1107 "Ns": "Nanos",
1108 "Ms": "Millis or Micros",
1109 "Sec": "Seconds", "Secs": "Seconds",
1110 "Hr": "Hours", "Hrs": "Hours",
1111 "Mo": "Months", "Mos": "Months",
1112 "Yr": "Years", "Yrs": "Years",
1113 "Byte": "Bytes", "Space": "Bytes",
1114 }
1115
1116 for m in clazz.methods:
1117 if m.typ not in ["short","int","long"]: continue
1118 for k, v in bad.iteritems():
1119 if m.name.endswith(k):
1120 error(clazz, m, None, "Expected method name units to be " + v)
1121 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1122 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1123 if m.name.endswith("Seconds"):
1124 error(clazz, m, None, "Returned time values must be in milliseconds")
1125
1126 for m in clazz.methods:
1127 typ = m.typ
1128 if typ == "void":
1129 if len(m.args) != 1: continue
1130 typ = m.args[0]
1131
1132 if m.name.endswith("Fraction") and typ != "float":
1133 error(clazz, m, None, "Fractions must use floats")
1134 if m.name.endswith("Percentage") and typ != "int":
1135 error(clazz, m, None, "Percentage must use ints")
1136
1137
1138def verify_closable(clazz):
1139 """Verifies that classes are AutoClosable."""
1140 if "implements java.lang.AutoCloseable" in clazz.raw: return
1141 if "implements java.io.Closeable" in clazz.raw: return
1142
1143 for m in clazz.methods:
1144 if len(m.args) > 0: continue
1145 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1146 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1147 return
1148
1149
Jake Wharton9e6738f2017-08-23 11:59:55 -04001150def verify_member_name_not_kotlin_keyword(clazz):
1151 """Prevent method names which are keywords in Kotlin."""
1152
1153 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1154 # This list does not include Java keywords as those are already impossible to use.
1155 keywords = [
1156 'as',
1157 'fun',
1158 'in',
1159 'is',
1160 'object',
1161 'typealias',
1162 'val',
1163 'var',
1164 'when',
1165 ]
1166
1167 for m in clazz.methods:
1168 if m.name in keywords:
1169 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1170 for f in clazz.fields:
1171 if f.name in keywords:
1172 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1173
1174
1175def verify_method_name_not_kotlin_operator(clazz):
1176 """Warn about method names which become operators in Kotlin."""
1177
1178 binary = set()
1179
1180 def unique_binary_op(m, op):
1181 if op in binary:
1182 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1183 binary.add(op)
1184
1185 for m in clazz.methods:
1186 if 'static' in m.split:
1187 continue
1188
1189 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1190 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1191 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1192
1193 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1194 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1195 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1196 # practical way of checking that relationship here.
1197 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1198
1199 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1200 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1201 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1202 unique_binary_op(m, m.name)
1203
1204 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1205 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1206 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1207
1208 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1209 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1210 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1211
1212 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1213 if m.name == 'invoke':
1214 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1215
1216 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1217 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1218 and len(m.args) == 1 \
1219 and m.typ == 'void':
1220 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1221 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1222
1223
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001224def verify_collections_over_arrays(clazz):
1225 """Warn that [] should be Collections."""
1226
1227 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1228 for m in clazz.methods:
1229 if m.typ.endswith("[]") and m.typ not in safe:
1230 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1231 for arg in m.args:
1232 if arg.endswith("[]") and arg not in safe:
1233 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1234
1235
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001236def examine_clazz(clazz):
1237 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001238
1239 notice(clazz)
1240
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001241 if clazz.pkg.name.startswith("java"): return
1242 if clazz.pkg.name.startswith("junit"): return
1243 if clazz.pkg.name.startswith("org.apache"): return
1244 if clazz.pkg.name.startswith("org.xml"): return
1245 if clazz.pkg.name.startswith("org.json"): return
1246 if clazz.pkg.name.startswith("org.w3c"): return
Jeff Sharkey331279b2016-02-29 16:02:02 -07001247 if clazz.pkg.name.startswith("android.icu."): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001248
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001249 verify_constants(clazz)
1250 verify_enums(clazz)
1251 verify_class_names(clazz)
1252 verify_method_names(clazz)
1253 verify_callbacks(clazz)
1254 verify_listeners(clazz)
1255 verify_actions(clazz)
1256 verify_extras(clazz)
1257 verify_equals(clazz)
1258 verify_parcelable(clazz)
1259 verify_protected(clazz)
1260 verify_fields(clazz)
1261 verify_register(clazz)
1262 verify_sync(clazz)
1263 verify_intent_builder(clazz)
1264 verify_helper_classes(clazz)
1265 verify_builder(clazz)
1266 verify_aidl(clazz)
1267 verify_internal(clazz)
1268 verify_layering(clazz)
1269 verify_boolean(clazz)
1270 verify_collections(clazz)
1271 verify_flags(clazz)
1272 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001273 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001274 verify_bitset(clazz)
1275 verify_manager(clazz)
1276 verify_boxed(clazz)
1277 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001278 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001279 verify_callback_handlers(clazz)
1280 verify_context_first(clazz)
1281 verify_listener_last(clazz)
1282 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001283 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001284 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001285 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001286 verify_runtime_exceptions(clazz)
1287 verify_error(clazz)
1288 verify_units(clazz)
1289 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001290 verify_member_name_not_kotlin_keyword(clazz)
1291 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001292 verify_collections_over_arrays(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001293
1294
1295def examine_stream(stream):
1296 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001297 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001298 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001299 noticed = {}
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001300 _parse_stream(stream, examine_clazz)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001301 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001302
1303
1304def examine_api(api):
1305 """Find all style issues in the given parsed API."""
1306 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001307 failures = {}
1308 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001309 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001310 return failures
1311
1312
Jeff Sharkey037458a2014-09-04 15:46:20 -07001313def verify_compat(cur, prev):
1314 """Find any incompatible API changes between two levels."""
1315 global failures
1316
1317 def class_exists(api, test):
1318 return test.fullname in api
1319
1320 def ctor_exists(api, clazz, test):
1321 for m in clazz.ctors:
1322 if m.ident == test.ident: return True
1323 return False
1324
1325 def all_methods(api, clazz):
1326 methods = list(clazz.methods)
1327 if clazz.extends is not None:
1328 methods.extend(all_methods(api, api[clazz.extends]))
1329 return methods
1330
1331 def method_exists(api, clazz, test):
1332 methods = all_methods(api, clazz)
1333 for m in methods:
1334 if m.ident == test.ident: return True
1335 return False
1336
1337 def field_exists(api, clazz, test):
1338 for f in clazz.fields:
1339 if f.ident == test.ident: return True
1340 return False
1341
1342 failures = {}
1343 for key in sorted(prev.keys()):
1344 prev_clazz = prev[key]
1345
1346 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001347 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001348 continue
1349
1350 cur_clazz = cur[key]
1351
1352 for test in prev_clazz.ctors:
1353 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001354 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001355
1356 methods = all_methods(prev, prev_clazz)
1357 for test in methods:
1358 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001359 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001360
1361 for test in prev_clazz.fields:
1362 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001363 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001364
1365 return failures
1366
1367
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001368if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001369 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
1370 patterns. It ignores lint messages from a previous API level, if provided.")
1371 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
1372 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
1373 help="previous.txt")
1374 parser.add_argument("--no-color", action='store_const', const=True,
1375 help="Disable terminal colors")
1376 parser.add_argument("--allow-google", action='store_const', const=True,
1377 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001378 parser.add_argument("--show-noticed", action='store_const', const=True,
1379 help="Show API changes noticed")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001380 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001381
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001382 if args['no_color']:
1383 USE_COLOR = False
1384
1385 if args['allow_google']:
1386 ALLOW_GOOGLE = True
1387
1388 current_file = args['current.txt']
1389 previous_file = args['previous.txt']
1390
1391 with current_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001392 cur_fail, cur_noticed = examine_stream(f)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001393 if not previous_file is None:
1394 with previous_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001395 prev_fail, prev_noticed = examine_stream(f)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001396
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001397 # ignore errors from previous API level
1398 for p in prev_fail:
1399 if p in cur_fail:
1400 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001401
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001402 # ignore classes unchanged from previous API level
1403 for k, v in prev_noticed.iteritems():
1404 if k in cur_noticed and v == cur_noticed[k]:
1405 del cur_noticed[k]
1406
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001407 """
1408 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001409 # look for compatibility issues
1410 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001411
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001412 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1413 for f in sorted(compat_fail):
1414 print compat_fail[f]
1415 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001416 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001417
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001418 if args['show_noticed'] and len(cur_noticed) != 0:
1419 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1420 for f in sorted(cur_noticed.keys()):
1421 print f
1422 print
1423
Jason Monk53b2a732017-11-10 15:43:17 -05001424 if len(cur_fail) != 0:
1425 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1426 for f in sorted(cur_fail):
1427 print cur_fail[f]
1428 print
1429 sys.exit(77)