blob: 847b43bdd5ae2768659dde82cd516852c7b811ec [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():
Adrian Roosb787c182019-01-03 18:54:33 +010054 def __init__(self, clazz, line, raw, blame, sig_format = 1):
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
Adrian Roosb787c182019-01-03 18:54:33 +010060 if sig_format == 2:
61 V2LineParser(raw).parse_into_field(self)
62 elif sig_format == 1:
63 # drop generics for now; may need multiple passes
64 raw = re.sub("<[^<]+?>", "", raw)
65 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -060066
Adrian Roosb787c182019-01-03 18:54:33 +010067 raw = raw.split()
68 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070069
Adrian Roosb787c182019-01-03 18:54:33 +010070 for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
71 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070072
Adrian Roosb787c182019-01-03 18:54:33 +010073 # ignore annotations for now
74 raw = [ r for r in raw if not r.startswith("@") ]
Jeff Sharkey40d67f42018-07-17 13:29:40 -060075
Adrian Roosb787c182019-01-03 18:54:33 +010076 self.typ = raw[0]
77 self.name = raw[1].strip(";")
78 if len(raw) >= 4 and raw[2] == "=":
79 self.value = raw[3].strip(';"')
80 else:
81 self.value = None
82
83 self.ident = "-".join((self.typ, self.name, self.value or ""))
Jeff Sharkey037458a2014-09-04 15:46:20 -070084
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -070085 def __hash__(self):
86 return hash(self.raw)
87
Jeff Sharkey8190f4882014-08-28 12:24:07 -070088 def __repr__(self):
89 return self.raw
90
Jeff Sharkey8190f4882014-08-28 12:24:07 -070091class Method():
Adrian Roosb787c182019-01-03 18:54:33 +010092 def __init__(self, clazz, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070093 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070094 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070095 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070096 self.blame = blame
97
Adrian Roosb787c182019-01-03 18:54:33 +010098 if sig_format == 2:
99 V2LineParser(raw).parse_into_method(self)
100 elif sig_format == 1:
101 # drop generics for now; may need multiple passes
102 raw = re.sub("<[^<]+?>", "", raw)
103 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700104
Adrian Roosb787c182019-01-03 18:54:33 +0100105 # handle each clause differently
106 raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600107
Adrian Roosb787c182019-01-03 18:54:33 +0100108 # parse prefixes
109 raw = re.split("[\s]+", raw_prefix)
110 for r in ["", ";"]:
111 while r in raw: raw.remove(r)
112 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700113
Adrian Roosb787c182019-01-03 18:54:33 +0100114 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator", "synchronized"]:
115 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700116
Adrian Roosb787c182019-01-03 18:54:33 +0100117 self.typ = raw[0]
118 self.name = raw[1]
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600119
Adrian Roosb787c182019-01-03 18:54:33 +0100120 # parse args
121 self.args = []
122 for arg in re.split(",\s*", raw_args):
123 arg = re.split("\s", arg)
124 # ignore annotations for now
125 arg = [ a for a in arg if not a.startswith("@") ]
126 if len(arg[0]) > 0:
127 self.args.append(arg[0])
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600128
Adrian Roosb787c182019-01-03 18:54:33 +0100129 # parse throws
130 self.throws = []
131 for throw in re.split(",\s*", raw_throws):
132 self.throws.append(throw)
133 else:
134 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600135
Adrian Roosb787c182019-01-03 18:54:33 +0100136 self.ident = "-".join((self.typ, self.name, "-".join(self.args)))
137
138 def sig_matches(self, typ, name, args):
139 return typ == self.typ and name == self.name and args == self.args
Jeff Sharkey037458a2014-09-04 15:46:20 -0700140
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700141 def __hash__(self):
142 return hash(self.raw)
143
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700144 def __repr__(self):
145 return self.raw
146
147
148class Class():
Adrian Roosb787c182019-01-03 18:54:33 +0100149 def __init__(self, pkg, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700150 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700151 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700152 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700153 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700154 self.ctors = []
155 self.fields = []
156 self.methods = []
157
Adrian Roosb787c182019-01-03 18:54:33 +0100158 if sig_format == 2:
159 V2LineParser(raw).parse_into_class(self)
160 elif sig_format == 1:
161 # drop generics for now; may need multiple passes
162 raw = re.sub("<[^<]+?>", "", raw)
163 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600164
Adrian Roosb787c182019-01-03 18:54:33 +0100165 raw = raw.split()
166 self.split = list(raw)
167 if "class" in raw:
168 self.fullname = raw[raw.index("class")+1]
169 elif "interface" in raw:
170 self.fullname = raw[raw.index("interface")+1]
171 elif "@interface" in raw:
172 self.fullname = raw[raw.index("@interface")+1]
173 else:
174 raise ValueError("Funky class type %s" % (self.raw))
Jeff Sharkey037458a2014-09-04 15:46:20 -0700175
Adrian Roosb787c182019-01-03 18:54:33 +0100176 if "extends" in raw:
177 self.extends = raw[raw.index("extends")+1]
178 else:
179 self.extends = None
180
181 if "implements" in raw:
182 self.implements = raw[raw.index("implements")+1]
183 else:
184 self.implements = None
Jeff Sharkey037458a2014-09-04 15:46:20 -0700185 else:
Adrian Roosb787c182019-01-03 18:54:33 +0100186 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700187
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700188 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800189 self.fullname_path = self.fullname.split(".")
190
Adrian Roosb787c182019-01-03 18:54:33 +0100191 if self.extends is not None:
192 self.extends_path = self.extends.split(".")
193 else:
194 self.extends_path = []
195
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700196 self.name = self.fullname[self.fullname.rindex(".")+1:]
197
Adrian Roos6eb57b02018-12-13 22:08:29 +0100198 def merge_from(self, other):
199 self.ctors.extend(other.ctors)
200 self.fields.extend(other.fields)
201 self.methods.extend(other.methods)
202
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700203 def __hash__(self):
204 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
205
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700206 def __repr__(self):
207 return self.raw
208
209
210class Package():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700211 def __init__(self, line, raw, blame):
212 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700213 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700214 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700215
216 raw = raw.split()
217 self.name = raw[raw.index("package")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800218 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700219
220 def __repr__(self):
221 return self.raw
222
Adrian Roosb787c182019-01-03 18:54:33 +0100223class V2Tokenizer():
224 DELIMITER = re.compile(r'\s+|[()@<>;,={}/"]|\[\]')
225 STRING_SPECIAL = re.compile(r'["\\]')
226
227 def __init__(self, raw):
228 self.raw = raw
229 self.current = 0
230
231 def __iter__(self):
232 return self
233
234 def next(self):
235 if self.current >= len(self.raw):
236 raise StopIteration
237
238 start = self.current
239 match = V2Tokenizer.DELIMITER.search(self.raw, start)
240 if match:
241 match_start = match.start()
242 if match_start == self.current:
243 end = match.end()
244 else:
245 end = match_start
246 else:
247 end = len(self.raw)
248
249 token = self.raw[start:end]
250 self.current = end
251
252 if token.strip() == "":
253 return self.next()
254
255 if token == "@" and self.raw[start:start+10] == "@interface":
256 return token + self.next()
257
258 if token == '/' and self.raw[start:start+2] == "//":
259 self.current = len(self.raw)
260 raise StopIteration
261
262 if token == '"':
263 return token + self.tokenize_string()
264
265 return token
266
267 def tokenize_string(self):
268 start = self.current
269 end = len(self.raw)
270 while start < end:
271 match = V2Tokenizer.STRING_SPECIAL.search(self.raw, start)
272 if match:
273 if match.group() == '"':
274 end = match.end()
275 break
276 elif match.group() == '\\':
277 # ignore whatever is after the slash
278 start += 2
279 else:
280 raise ValueError("Unexpected match: `%s`" % (match.group()))
281 else:
282 raise ValueError("Unexpected EOF tokenizing string: `%s`" % (self.raw[self.current - 1:],))
283
284 token = self.raw[self.current:end]
285 self.current = end
286 return token
287
288class V2LineParser():
289 MODIFIERS = set("public protected internal private abstract default static final transient volatile synchronized".split())
290 JAVA_LANG_TYPES = set("AbstractMethodError AbstractStringBuilder Appendable ArithmeticException ArrayIndexOutOfBoundsException ArrayStoreException AssertionError AutoCloseable Boolean BootstrapMethodError Byte Character CharSequence Class ClassCastException ClassCircularityError ClassFormatError ClassLoader ClassNotFoundException Cloneable CloneNotSupportedException Comparable Compiler Deprecated Double Enum EnumConstantNotPresentException Error Exception ExceptionInInitializerError Float FunctionalInterface IllegalAccessError IllegalAccessException IllegalArgumentException IllegalMonitorStateException IllegalStateException IllegalThreadStateException IncompatibleClassChangeError IndexOutOfBoundsException InheritableThreadLocal InstantiationError InstantiationException Integer InternalError InterruptedException Iterable LinkageError Long Math NegativeArraySizeException NoClassDefFoundError NoSuchFieldError NoSuchFieldException NoSuchMethodError NoSuchMethodException NullPointerException Number NumberFormatException Object OutOfMemoryError Override Package package-info.java Process ProcessBuilder ProcessEnvironment ProcessImpl Readable ReflectiveOperationException Runnable Runtime RuntimeException RuntimePermission SafeVarargs SecurityException SecurityManager Short StackOverflowError StackTraceElement StrictMath String StringBuffer StringBuilder StringIndexOutOfBoundsException SuppressWarnings System Thread ThreadDeath ThreadGroup ThreadLocal Throwable TypeNotPresentException UNIXProcess UnknownError UnsatisfiedLinkError UnsupportedClassVersionError UnsupportedOperationException VerifyError VirtualMachineError Void".split())
291
292 def __init__(self, raw):
293 self.tokenized = list(V2Tokenizer(raw))
294 self.current = 0
295
296 def parse_into_method(self, method):
297 method.split = []
298 kind = self.parse_one_of("ctor", "method")
299 method.split.append(kind)
300 annotations = self.parse_annotations()
301 method.split.extend(self.parse_modifiers())
302 self.parse_matching_paren("<", ">")
303 if "@Deprecated" in annotations:
304 method.split.append("deprecated")
305 if kind == "ctor":
306 method.typ = "ctor"
307 else:
308 method.typ = self.parse_type()
309 method.split.append(method.typ)
310 method.name = self.parse_name()
311 method.split.append(method.name)
312 self.parse_token("(")
313 method.args = self.parse_args()
314 self.parse_token(")")
315 method.throws = self.parse_throws()
316 if "@interface" in method.clazz.split:
317 self.parse_annotation_default()
318 self.parse_token(";")
319 self.parse_eof()
320
321 def parse_into_class(self, clazz):
322 clazz.split = []
323 annotations = self.parse_annotations()
324 if "@Deprecated" in annotations:
325 clazz.split.append("deprecated")
326 clazz.split.extend(self.parse_modifiers())
327 kind = self.parse_one_of("class", "interface", "@interface", "enum")
328 if kind == "enum":
329 # enums are implicitly final
330 clazz.split.append("final")
331 clazz.split.append(kind)
332 clazz.fullname = self.parse_name()
333 self.parse_matching_paren("<", ">")
334 extends = self.parse_extends()
335 clazz.extends = extends[0] if extends else None
336 implements = self.parse_implements()
337 clazz.implements = implements[0] if implements else None
338 # The checks assume that interfaces are always found in implements, which isn't true for
339 # subinterfaces.
340 if not implements and "interface" in clazz.split:
341 clazz.implements = clazz.extends
342 self.parse_token("{")
343 self.parse_eof()
344
345 def parse_into_field(self, field):
346 kind = self.parse_token("field")
347 field.split = [kind]
348 annotations = self.parse_annotations()
349 if "@Deprecated" in annotations:
350 field.split.append("deprecated")
351 field.split.extend(self.parse_modifiers())
352 field.typ = self.parse_type()
353 field.split.append(field.typ)
354 field.name = self.parse_name()
355 field.split.append(field.name)
356 if self.parse_if("="):
357 field.value = self.parse_value_stripped()
358 else:
359 field.value = None
360
361 self.parse_token(";")
362 self.parse_eof()
363
364 def lookahead(self):
365 return self.tokenized[self.current]
366
367 def parse_one_of(self, *options):
368 found = self.lookahead()
369 if found not in options:
370 raise ValueError("Parsing failed, expected one of `%s` but found `%s` in %s" % (options, found, repr(self.tokenized)))
371 return self.parse_token()
372
373 def parse_token(self, tok = None):
374 found = self.lookahead()
375 if tok is not None and found != tok:
376 raise ValueError("Parsing failed, expected `%s` but found `%s` in %s" % (tok, found, repr(self.tokenized)))
377 self.current += 1
378 return found
379
380 def eof(self):
381 return self.current == len(self.tokenized)
382
383 def parse_eof(self):
384 if not self.eof():
385 raise ValueError("Parsing failed, expected EOF, but %s has not been parsed in %s" % (self.tokenized[self.current:], self.tokenized))
386
387 def parse_if(self, tok):
388 if not self.eof() and self.lookahead() == tok:
389 self.parse_token()
390 return True
391 return False
392
393 def parse_annotations(self):
394 ret = []
395 while self.lookahead() == "@":
396 ret.append(self.parse_annotation())
397 return ret
398
399 def parse_annotation(self):
400 ret = self.parse_token("@") + self.parse_token()
401 self.parse_matching_paren("(", ")")
402 return ret
403
404 def parse_matching_paren(self, open, close):
405 start = self.current
406 if not self.parse_if(open):
407 return
408 length = len(self.tokenized)
409 count = 1
410 while count > 0:
411 if self.current == length:
412 raise ValueError("Unexpected EOF looking for closing paren: `%s`" % (self.tokenized[start:],))
413 t = self.parse_token()
414 if t == open:
415 count += 1
416 elif t == close:
417 count -= 1
418 return self.tokenized[start:self.current]
419
420 def parse_modifiers(self):
421 ret = []
422 while self.lookahead() in V2LineParser.MODIFIERS:
423 ret.append(self.parse_token())
424 return ret
425
426 def parse_type(self):
427 type = self.parse_token()
428 if type in V2LineParser.JAVA_LANG_TYPES:
429 type = "java.lang." + type
430 self.parse_matching_paren("<", ">")
431 while self.parse_if("[]"):
432 type += "[]"
433 return type
434
435 def parse_arg_type(self):
436 type = self.parse_type()
437 if self.parse_if("..."):
438 type += "..."
439 return type
440
441 def parse_name(self):
442 return self.parse_token()
443
444 def parse_args(self):
445 args = []
446 if self.lookahead() == ")":
447 return args
448
449 while True:
450 args.append(self.parse_arg())
451 if self.lookahead() == ")":
452 return args
453 self.parse_token(",")
454
455 def parse_arg(self):
456 self.parse_annotations()
457 return self.parse_arg_type()
458
459 def parse_throws(self):
460 ret = []
461 if self.parse_if("throws"):
462 ret.append(self.parse_type())
463 while self.parse_if(","):
464 ret.append(self.parse_type())
465 return ret
466
467 def parse_extends(self):
468 if self.parse_if("extends"):
469 return self.parse_space_delimited_type_list()
470 return []
471
472 def parse_implements(self):
473 if self.parse_if("implements"):
474 return self.parse_space_delimited_type_list()
475 return []
476
477 def parse_space_delimited_type_list(self, terminals = ["implements", "{"]):
478 types = []
479 while True:
480 types.append(self.parse_type())
481 if self.lookahead() in terminals:
482 return types
483
484 def parse_annotation_default(self):
485 if self.parse_if("default"):
486 self.parse_value()
487
488 def parse_value(self):
489 if self.lookahead() == "{":
490 return " ".join(self.parse_matching_paren("{", "}"))
491 elif self.lookahead() == "(":
492 return " ".join(self.parse_matching_paren("(", ")"))
493 else:
494 return self.parse_token()
495
496 def parse_value_stripped(self):
497 value = self.parse_value()
498 if value[0] == '"':
499 return value[1:-1]
500 return value
501
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700502
Adrian Roos038a0292018-12-19 17:11:21 +0100503def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
504 in_classes_with_base=[]):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700505 api = {}
Adrian Roos038a0292018-12-19 17:11:21 +0100506 in_classes_with_base = _retry_iterator(in_classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +0100507
508 if base_f:
Adrian Roos038a0292018-12-19 17:11:21 +0100509 base_classes = _retry_iterator(_parse_stream_to_generator(base_f))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100510 else:
511 base_classes = []
512
Adrian Roos038a0292018-12-19 17:11:21 +0100513 def handle_class(clazz):
Adrian Roos6eb57b02018-12-13 22:08:29 +0100514 if clazz_cb:
515 clazz_cb(clazz)
516 else: # In callback mode, don't keep track of the full API
517 api[clazz.fullname] = clazz
518
Adrian Roos038a0292018-12-19 17:11:21 +0100519 def handle_missed_classes_with_base(clazz):
520 for c in _yield_until_matching_class(in_classes_with_base, clazz):
521 base_class = _skip_to_matching_class(base_classes, c)
522 if base_class:
523 handle_class(base_class)
524
525 for clazz in _parse_stream_to_generator(f):
526 # Before looking at clazz, let's see if there's some classes that were not present, but
527 # may have an entry in the base stream.
528 handle_missed_classes_with_base(clazz)
529
530 base_class = _skip_to_matching_class(base_classes, clazz)
531 if base_class:
532 clazz.merge_from(base_class)
533 if out_classes_with_base is not None:
534 out_classes_with_base.append(clazz)
535 handle_class(clazz)
536
537 handle_missed_classes_with_base(None)
538
Adrian Roos6eb57b02018-12-13 22:08:29 +0100539 return api
540
541def _parse_stream_to_generator(f):
542 line = 0
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700543 pkg = None
544 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700545 blame = None
Adrian Roosb787c182019-01-03 18:54:33 +0100546 sig_format = 1
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700547
548 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800549 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700550 line += 1
551 raw = raw.rstrip()
552 match = re_blame.match(raw)
553 if match is not None:
554 blame = match.groups()[0:2]
555 raw = match.groups()[2]
556 else:
557 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700558
Adrian Roosb787c182019-01-03 18:54:33 +0100559 if line == 1 and raw == "// Signature format: 2.0":
560 sig_format = 2
561 elif raw.startswith("package"):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700562 pkg = Package(line, raw, blame)
563 elif raw.startswith(" ") and raw.endswith("{"):
Adrian Roosb787c182019-01-03 18:54:33 +0100564 clazz = Class(pkg, line, raw, blame, sig_format=sig_format)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700565 elif raw.startswith(" ctor"):
Adrian Roosb787c182019-01-03 18:54:33 +0100566 clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700567 elif raw.startswith(" method"):
Adrian Roosb787c182019-01-03 18:54:33 +0100568 clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700569 elif raw.startswith(" field"):
Adrian Roosb787c182019-01-03 18:54:33 +0100570 clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100571 elif raw.startswith(" }") and clazz:
Adrian Roos038a0292018-12-19 17:11:21 +0100572 yield clazz
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800573
Adrian Roos5ed42b62018-12-19 17:10:22 +0100574def _retry_iterator(it):
575 """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
576 for e in it:
577 while True:
578 retry = yield e
579 if not retry:
580 break
581 # send() was called, asking us to redeliver clazz on next(). Still need to yield
582 # a dummy value to the send() first though.
583 if (yield "Returning clazz on next()"):
584 raise TypeError("send() must be followed by next(), not send()")
585
Adrian Roos038a0292018-12-19 17:11:21 +0100586def _skip_to_matching_class(classes, needle):
587 """Takes a classes iterator and consumes entries until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700588
Adrian Roos6eb57b02018-12-13 22:08:29 +0100589 This relies on classes being sorted by package and class name."""
590
591 for clazz in classes:
592 if clazz.pkg.name < needle.pkg.name:
593 # We haven't reached the right package yet
594 continue
Adrian Roos5ed42b62018-12-19 17:10:22 +0100595 if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
596 # We're in the right package, but not the right class yet
Adrian Roos6eb57b02018-12-13 22:08:29 +0100597 continue
598 if clazz.fullname == needle.fullname:
599 return clazz
600 # We ran past the right class. Send it back into the generator, then report failure.
601 classes.send(clazz)
602 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700603
Adrian Roos038a0292018-12-19 17:11:21 +0100604def _yield_until_matching_class(classes, needle):
605 """Takes a class iterator and yields entries it until it reaches the class we're looking for.
606
607 This relies on classes being sorted by package and class name."""
608
609 for clazz in classes:
610 if needle is None:
611 yield clazz
612 elif clazz.pkg.name < needle.pkg.name:
613 # We haven't reached the right package yet
614 yield clazz
615 elif clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
616 # We're in the right package, but not the right class yet
617 yield clazz
618 elif clazz.fullname == needle.fullname:
619 # Class found, abort.
620 return
621 else:
622 # We ran past the right class. Send it back into the iterator, then abort.
623 classes.send(clazz)
624 return
625
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700626class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800627 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700628 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700629 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800630 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700631 self.msg = msg
632
633 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800634 self.head = "Error %s" % (rule) if rule else "Error"
635 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 -0700636 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800637 self.head = "Warning %s" % (rule) if rule else "Warning"
638 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 -0700639
640 self.line = clazz.line
641 blame = clazz.blame
642 if detail is not None:
643 dump += "\n in " + repr(detail)
644 self.line = detail.line
645 blame = detail.blame
646 dump += "\n in " + repr(clazz)
647 dump += "\n in " + repr(clazz.pkg)
648 dump += "\n at line " + repr(self.line)
649 if blame is not None:
650 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
651
652 self.dump = dump
653
654 def __repr__(self):
655 return self.dump
656
657
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700658failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700659
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800660def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700661 """Records an API failure to be processed later."""
662 global failures
663
Adrian Roosb787c182019-01-03 18:54:33 +0100664 sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700665 sig = sig.replace(" deprecated ", " ")
666
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800667 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700668
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700669
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800670def warn(clazz, detail, rule, msg):
671 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700672
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800673def error(clazz, detail, rule, msg):
674 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700675
676
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700677noticed = {}
678
679def notice(clazz):
680 global noticed
681
682 noticed[clazz.fullname] = hash(clazz)
683
684
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700685def verify_constants(clazz):
686 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700687 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600688 if clazz.fullname.startswith("android.os.Build"): return
689 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700690
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600691 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700692 for f in clazz.fields:
693 if "static" in f.split and "final" in f.split:
694 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800695 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600696 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700697 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
698 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600699 if f.typ in req and f.value is None:
700 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700701
702
703def verify_enums(clazz):
704 """Enums are bad, mmkay?"""
Adrian Roosb787c182019-01-03 18:54:33 +0100705 if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800706 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700707
708
709def verify_class_names(clazz):
710 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700711 if clazz.fullname.startswith("android.opengl"): return
712 if clazz.fullname.startswith("android.renderscript"): return
713 if re.match("android\.R\.[a-z]+", clazz.fullname): return
714
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700715 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800716 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700717 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800718 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700719 if clazz.name.endswith("Impl"):
720 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700721
722
723def verify_method_names(clazz):
724 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700725 if clazz.fullname.startswith("android.opengl"): return
726 if clazz.fullname.startswith("android.renderscript"): return
727 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700728
729 for m in clazz.methods:
730 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800731 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700732 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800733 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700734
735
736def verify_callbacks(clazz):
737 """Verify Callback classes.
738 All callback classes must be abstract.
739 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700740 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700741
742 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800743 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700744 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800745 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700746
747 if clazz.name.endswith("Callback"):
748 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800749 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700750
751 for m in clazz.methods:
752 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800753 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700754
755
756def verify_listeners(clazz):
757 """Verify Listener classes.
758 All Listener classes must be interface.
759 All methods must follow onFoo() naming style.
760 If only a single method, it must match class name:
761 interface OnFooListener { void onFoo() }"""
762
763 if clazz.name.endswith("Listener"):
Adrian Roosb787c182019-01-03 18:54:33 +0100764 if "abstract" in clazz.split and "class" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800765 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700766
767 for m in clazz.methods:
768 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800769 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700770
771 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
772 m = clazz.methods[0]
773 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800774 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700775
776
777def verify_actions(clazz):
778 """Verify intent actions.
779 All action names must be named ACTION_FOO.
780 All action values must be scoped by package and match name:
781 package android.foo {
782 String ACTION_BAR = "android.foo.action.BAR";
783 }"""
784 for f in clazz.fields:
785 if f.value is None: continue
786 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700787 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600788 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700789
790 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
791 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
792 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800793 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700794 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700795 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700796 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700797 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700798 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700799 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
800 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700801 else:
802 prefix = clazz.pkg.name + ".action"
803 expected = prefix + "." + f.name[7:]
804 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700805 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700806
807
808def verify_extras(clazz):
809 """Verify intent extras.
810 All extra names must be named EXTRA_FOO.
811 All extra values must be scoped by package and match name:
812 package android.foo {
813 String EXTRA_BAR = "android.foo.extra.BAR";
814 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700815 if clazz.fullname == "android.app.Notification": return
816 if clazz.fullname == "android.appwidget.AppWidgetManager": return
817
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700818 for f in clazz.fields:
819 if f.value is None: continue
820 if f.name.startswith("ACTION_"): continue
821
822 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
823 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
824 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800825 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700826 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700827 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700828 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700829 elif clazz.pkg.name == "android.app.admin":
830 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700831 else:
832 prefix = clazz.pkg.name + ".extra"
833 expected = prefix + "." + f.name[6:]
834 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700835 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700836
837
838def verify_equals(clazz):
839 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700840 eq = False
841 hc = False
842 for m in clazz.methods:
Adrian Roosb787c182019-01-03 18:54:33 +0100843 if "static" in m.split: continue
844 if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
845 if m.sig_matches("int", "hashCode", []): hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700846 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800847 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700848
849
850def verify_parcelable(clazz):
851 """Verify that Parcelable objects aren't hiding required bits."""
Adrian Roosb787c182019-01-03 18:54:33 +0100852 if clazz.implements == "android.os.Parcelable":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700853 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
854 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
855 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
856
857 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800858 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700859
Adrian Roosb787c182019-01-03 18:54:33 +0100860 if "final" not in clazz.split:
Jeff Sharkey331279b2016-02-29 16:02:02 -0700861 error(clazz, None, "FW8", "Parcelable classes must be final")
862
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700863 for c in clazz.ctors:
864 if c.args == ["android.os.Parcel"]:
865 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
866
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700867
868def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800869 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700870 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600871 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700872 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800873 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700874 for f in clazz.fields:
875 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800876 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700877
878
879def verify_fields(clazz):
880 """Verify that all exposed fields are final.
881 Exposed fields must follow myName style.
882 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700883
884 IGNORE_BARE_FIELDS = [
885 "android.app.ActivityManager.RecentTaskInfo",
886 "android.app.Notification",
887 "android.content.pm.ActivityInfo",
888 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600889 "android.content.pm.ComponentInfo",
890 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700891 "android.content.pm.FeatureGroupInfo",
892 "android.content.pm.InstrumentationInfo",
893 "android.content.pm.PackageInfo",
894 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600895 "android.content.res.Configuration",
896 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700897 "android.os.Message",
898 "android.system.StructPollfd",
899 ]
900
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700901 for f in clazz.fields:
902 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700903 if clazz.fullname in IGNORE_BARE_FIELDS:
904 pass
905 elif clazz.fullname.endswith("LayoutParams"):
906 pass
907 elif clazz.fullname.startswith("android.util.Mutable"):
908 pass
909 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800910 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700911
912 if not "static" in f.split:
913 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800914 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700915
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700916 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800917 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700918
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700919 if re.match("[A-Z_]+", f.name):
920 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800921 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700922
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700923
924def verify_register(clazz):
925 """Verify parity of registration methods.
926 Callback objects use register/unregister methods.
927 Listener objects use add/remove methods."""
928 methods = [ m.name for m in clazz.methods ]
929 for m in clazz.methods:
930 if "Callback" in m.raw:
931 if m.name.startswith("register"):
932 other = "unregister" + m.name[8:]
933 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800934 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700935 if m.name.startswith("unregister"):
936 other = "register" + m.name[10:]
937 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800938 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700939
940 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800941 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700942
943 if "Listener" in m.raw:
944 if m.name.startswith("add"):
945 other = "remove" + m.name[3:]
946 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800947 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700948 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
949 other = "add" + m.name[6:]
950 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800951 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700952
953 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800954 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700955
956
957def verify_sync(clazz):
958 """Verify synchronized methods aren't exposed."""
959 for m in clazz.methods:
960 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800961 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700962
963
964def verify_intent_builder(clazz):
965 """Verify that Intent builders are createFooIntent() style."""
966 if clazz.name == "Intent": return
967
968 for m in clazz.methods:
969 if m.typ == "android.content.Intent":
970 if m.name.startswith("create") and m.name.endswith("Intent"):
971 pass
972 else:
Adam Powell539ea122015-04-10 13:01:37 -0700973 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700974
975
976def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700977 """Verify that helper classes are named consistently with what they extend.
978 All developer extendable methods should be named onFoo()."""
979 test_methods = False
Adrian Roosb787c182019-01-03 18:54:33 +0100980 if clazz.extends == "android.app.Service":
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700981 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700982 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800983 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700984
985 found = False
986 for f in clazz.fields:
987 if f.name == "SERVICE_INTERFACE":
988 found = True
989 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700990 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700991
Adrian Roosb787c182019-01-03 18:54:33 +0100992 if clazz.extends == "android.content.ContentProvider":
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700993 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700994 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800995 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700996
997 found = False
998 for f in clazz.fields:
999 if f.name == "PROVIDER_INTERFACE":
1000 found = True
1001 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001002 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001003
Adrian Roosb787c182019-01-03 18:54:33 +01001004 if clazz.extends == "android.content.BroadcastReceiver":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001005 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001006 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001007 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001008
Adrian Roosb787c182019-01-03 18:54:33 +01001009 if clazz.extends == "android.app.Activity":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001010 test_methods = True
1011 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001012 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001013
1014 if test_methods:
1015 for m in clazz.methods:
1016 if "final" in m.split: continue
1017 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001018 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001019 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001020 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001021 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001022
1023
1024def verify_builder(clazz):
1025 """Verify builder classes.
1026 Methods should return the builder to enable chaining."""
Adrian Roosb787c182019-01-03 18:54:33 +01001027 if clazz.extends: return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001028 if not clazz.name.endswith("Builder"): return
1029
1030 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001031 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001032
1033 has_build = False
1034 for m in clazz.methods:
1035 if m.name == "build":
1036 has_build = True
1037 continue
1038
1039 if m.name.startswith("get"): continue
1040 if m.name.startswith("clear"): continue
1041
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001042 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001043 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001044
1045 if m.name.startswith("set"):
1046 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001047 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001048
1049 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001050 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001051
1052
1053def verify_aidl(clazz):
1054 """Catch people exposing raw AIDL."""
Adrian Roosb787c182019-01-03 18:54:33 +01001055 if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001056 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001057
1058
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001059def verify_internal(clazz):
1060 """Catch people exposing internal classes."""
1061 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001062 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001063
1064
1065def verify_layering(clazz):
1066 """Catch package layering violations.
1067 For example, something in android.os depending on android.app."""
1068 ranking = [
1069 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
1070 "android.app",
1071 "android.widget",
1072 "android.view",
1073 "android.animation",
1074 "android.provider",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001075 ["android.content","android.graphics.drawable"],
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001076 "android.database",
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001077 "android.text",
Siyamed Sinir0a2e15d2018-09-13 16:06:59 -07001078 "android.graphics",
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001079 "android.os",
1080 "android.util"
1081 ]
1082
1083 def rank(p):
1084 for i in range(len(ranking)):
1085 if isinstance(ranking[i], list):
1086 for j in ranking[i]:
1087 if p.startswith(j): return i
1088 else:
1089 if p.startswith(ranking[i]): return i
1090
1091 cr = rank(clazz.pkg.name)
1092 if cr is None: return
1093
1094 for f in clazz.fields:
1095 ir = rank(f.typ)
1096 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001097 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001098
1099 for m in clazz.methods:
1100 ir = rank(m.typ)
1101 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001102 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001103 for arg in m.args:
1104 ir = rank(arg)
1105 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001106 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001107
1108
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001109def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001110 """Verifies that boolean accessors are named correctly.
1111 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001112
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001113 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
1114 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001115
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001116 gets = [ m for m in clazz.methods if is_get(m) ]
1117 sets = [ m for m in clazz.methods if is_set(m) ]
1118
1119 def error_if_exists(methods, trigger, expected, actual):
1120 for m in methods:
1121 if m.name == actual:
1122 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001123
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001124 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001125 if is_get(m):
1126 if re.match("is[A-Z]", m.name):
1127 target = m.name[2:]
1128 expected = "setIs" + target
1129 error_if_exists(sets, m.name, expected, "setHas" + target)
1130 elif re.match("has[A-Z]", m.name):
1131 target = m.name[3:]
1132 expected = "setHas" + target
1133 error_if_exists(sets, m.name, expected, "setIs" + target)
1134 error_if_exists(sets, m.name, expected, "set" + target)
1135 elif re.match("get[A-Z]", m.name):
1136 target = m.name[3:]
1137 expected = "set" + target
1138 error_if_exists(sets, m.name, expected, "setIs" + target)
1139 error_if_exists(sets, m.name, expected, "setHas" + target)
1140
1141 if is_set(m):
1142 if re.match("set[A-Z]", m.name):
1143 target = m.name[3:]
1144 expected = "get" + target
1145 error_if_exists(sets, m.name, expected, "is" + target)
1146 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001147
1148
1149def verify_collections(clazz):
1150 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001151 if clazz.fullname == "android.os.Bundle": return
1152
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001153 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
1154 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
1155 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001156 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001157 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001158 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001159 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001160 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001161
1162
1163def verify_flags(clazz):
1164 """Verifies that flags are non-overlapping."""
1165 known = collections.defaultdict(int)
1166 for f in clazz.fields:
1167 if "FLAG_" in f.name:
1168 try:
1169 val = int(f.value)
1170 except:
1171 continue
1172
1173 scope = f.name[0:f.name.index("FLAG_")]
1174 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001175 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001176 known[scope] |= val
1177
1178
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001179def verify_exception(clazz):
1180 """Verifies that methods don't throw generic exceptions."""
1181 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001182 for t in m.throws:
1183 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
1184 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001185
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001186 if t in ["android.os.RemoteException"]:
1187 if clazz.name == "android.content.ContentProviderClient": continue
1188 if clazz.name == "android.os.Binder": continue
1189 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -07001190
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001191 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
1192
1193 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
1194 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -07001195
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001196
1197def verify_google(clazz):
1198 """Verifies that APIs never reference Google."""
1199
1200 if re.search("google", clazz.raw, re.IGNORECASE):
1201 error(clazz, None, None, "Must never reference Google")
1202
1203 test = []
1204 test.extend(clazz.ctors)
1205 test.extend(clazz.fields)
1206 test.extend(clazz.methods)
1207
1208 for t in test:
1209 if re.search("google", t.raw, re.IGNORECASE):
1210 error(clazz, t, None, "Must never reference Google")
1211
1212
1213def verify_bitset(clazz):
1214 """Verifies that we avoid using heavy BitSet."""
1215
1216 for f in clazz.fields:
1217 if f.typ == "java.util.BitSet":
1218 error(clazz, f, None, "Field type must not be heavy BitSet")
1219
1220 for m in clazz.methods:
1221 if m.typ == "java.util.BitSet":
1222 error(clazz, m, None, "Return type must not be heavy BitSet")
1223 for arg in m.args:
1224 if arg == "java.util.BitSet":
1225 error(clazz, m, None, "Argument type must not be heavy BitSet")
1226
1227
1228def verify_manager(clazz):
1229 """Verifies that FooManager is only obtained from Context."""
1230
1231 if not clazz.name.endswith("Manager"): return
1232
1233 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001234 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001235
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001236 for m in clazz.methods:
1237 if m.typ == clazz.fullname:
1238 error(clazz, m, None, "Managers must always be obtained from Context")
1239
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001240
1241def verify_boxed(clazz):
1242 """Verifies that methods avoid boxed primitives."""
1243
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001244 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 -08001245
1246 for c in clazz.ctors:
1247 for arg in c.args:
1248 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001249 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001250
1251 for f in clazz.fields:
1252 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001253 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001254
1255 for m in clazz.methods:
1256 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001257 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001258 for arg in m.args:
1259 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001260 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001261
1262
1263def verify_static_utils(clazz):
1264 """Verifies that helper classes can't be constructed."""
1265 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001266 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001267
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001268 # Only care about classes with default constructors
1269 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
1270 test = []
1271 test.extend(clazz.fields)
1272 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001273
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001274 if len(test) == 0: return
1275 for t in test:
1276 if "static" not in t.split:
1277 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001278
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001279 error(clazz, None, None, "Fully-static utility classes must not have constructor")
1280
1281
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001282def verify_overload_args(clazz):
1283 """Verifies that method overloads add new arguments at the end."""
1284 if clazz.fullname.startswith("android.opengl"): return
1285
1286 overloads = collections.defaultdict(list)
1287 for m in clazz.methods:
1288 if "deprecated" in m.split: continue
1289 overloads[m.name].append(m)
1290
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001291 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001292 if len(methods) <= 1: continue
1293
1294 # Look for arguments common across all overloads
1295 def cluster(args):
1296 count = collections.defaultdict(int)
1297 res = set()
1298 for i in range(len(args)):
1299 a = args[i]
1300 res.add("%s#%d" % (a, count[a]))
1301 count[a] += 1
1302 return res
1303
1304 common_args = cluster(methods[0].args)
1305 for m in methods:
1306 common_args = common_args & cluster(m.args)
1307
1308 if len(common_args) == 0: continue
1309
1310 # Require that all common arguments are present at start of signature
1311 locked_sig = None
1312 for m in methods:
1313 sig = m.args[0:len(common_args)]
1314 if not common_args.issubset(cluster(sig)):
1315 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1316 elif not locked_sig:
1317 locked_sig = sig
1318 elif locked_sig != sig:
1319 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1320
1321
1322def verify_callback_handlers(clazz):
1323 """Verifies that methods adding listener/callback have overload
1324 for specifying delivery thread."""
1325
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001326 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001327 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001328 "animation",
1329 "view",
1330 "graphics",
1331 "transition",
1332 "widget",
1333 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001334 ]
1335 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001336 if s in clazz.pkg.name_path: return
1337 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001338
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001339 # Ignore UI classes which assume main thread
1340 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1341 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1342 if s in clazz.fullname: return
1343 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1344 for s in ["Loader"]:
1345 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001346
1347 found = {}
1348 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001349 examine = clazz.ctors + clazz.methods
1350 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001351 if m.name.startswith("unregister"): continue
1352 if m.name.startswith("remove"): continue
1353 if re.match("on[A-Z]+", m.name): continue
1354
1355 by_name[m.name].append(m)
1356
1357 for a in m.args:
1358 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1359 found[m.name] = m
1360
1361 for f in found.values():
1362 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001363 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001364 for m in by_name[f.name]:
1365 if "android.os.Handler" in m.args:
1366 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001367 if "java.util.concurrent.Executor" in m.args:
1368 takes_exec = True
1369 if not takes_exec:
1370 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001371
1372
1373def verify_context_first(clazz):
1374 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001375 examine = clazz.ctors + clazz.methods
1376 for m in examine:
1377 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001378 if "android.content.Context" in m.args[1:]:
1379 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001380 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1381 if "android.content.ContentResolver" in m.args[1:]:
1382 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001383
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001384
1385def verify_listener_last(clazz):
1386 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1387 examine = clazz.ctors + clazz.methods
1388 for m in examine:
1389 if "Listener" in m.name or "Callback" in m.name: continue
1390 found = False
1391 for a in m.args:
1392 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1393 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001394 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001395 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1396
1397
1398def verify_resource_names(clazz):
1399 """Verifies that resource names have consistent case."""
1400 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1401
1402 # Resources defined by files are foo_bar_baz
1403 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1404 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001405 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1406 if f.name.startswith("config_"):
1407 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1408
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001409 if re.match("[a-z1-9_]+$", f.name): continue
1410 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1411
1412 # Resources defined inside files are fooBarBaz
1413 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1414 for f in clazz.fields:
1415 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1416 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1417 if re.match("state_[a-z_]*$", f.name): continue
1418
1419 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1420 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1421
1422 # Styles are FooBar_Baz
1423 if clazz.name in ["style"]:
1424 for f in clazz.fields:
1425 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1426 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001427
1428
Jeff Sharkey331279b2016-02-29 16:02:02 -07001429def verify_files(clazz):
1430 """Verifies that methods accepting File also accept streams."""
1431
1432 has_file = set()
1433 has_stream = set()
1434
1435 test = []
1436 test.extend(clazz.ctors)
1437 test.extend(clazz.methods)
1438
1439 for m in test:
1440 if "java.io.File" in m.args:
1441 has_file.add(m)
1442 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:
1443 has_stream.add(m.name)
1444
1445 for m in has_file:
1446 if m.name not in has_stream:
1447 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1448
1449
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001450def verify_manager_list(clazz):
1451 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1452
1453 if not clazz.name.endswith("Manager"): return
1454
1455 for m in clazz.methods:
1456 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1457 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1458
1459
Jeff Sharkey26c80902016-12-21 13:41:17 -07001460def verify_abstract_inner(clazz):
1461 """Verifies that abstract inner classes are static."""
1462
1463 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
Adrian Roosb787c182019-01-03 18:54:33 +01001464 if "abstract" in clazz.split and "static" not in clazz.split:
Jeff Sharkey26c80902016-12-21 13:41:17 -07001465 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1466
1467
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001468def verify_runtime_exceptions(clazz):
1469 """Verifies that runtime exceptions aren't listed in throws."""
1470
1471 banned = [
1472 "java.lang.NullPointerException",
1473 "java.lang.ClassCastException",
1474 "java.lang.IndexOutOfBoundsException",
1475 "java.lang.reflect.UndeclaredThrowableException",
1476 "java.lang.reflect.MalformedParametersException",
1477 "java.lang.reflect.MalformedParameterizedTypeException",
1478 "java.lang.invoke.WrongMethodTypeException",
1479 "java.lang.EnumConstantNotPresentException",
1480 "java.lang.IllegalMonitorStateException",
1481 "java.lang.SecurityException",
1482 "java.lang.UnsupportedOperationException",
1483 "java.lang.annotation.AnnotationTypeMismatchException",
1484 "java.lang.annotation.IncompleteAnnotationException",
1485 "java.lang.TypeNotPresentException",
1486 "java.lang.IllegalStateException",
1487 "java.lang.ArithmeticException",
1488 "java.lang.IllegalArgumentException",
1489 "java.lang.ArrayStoreException",
1490 "java.lang.NegativeArraySizeException",
1491 "java.util.MissingResourceException",
1492 "java.util.EmptyStackException",
1493 "java.util.concurrent.CompletionException",
1494 "java.util.concurrent.RejectedExecutionException",
1495 "java.util.IllformedLocaleException",
1496 "java.util.ConcurrentModificationException",
1497 "java.util.NoSuchElementException",
1498 "java.io.UncheckedIOException",
1499 "java.time.DateTimeException",
1500 "java.security.ProviderException",
1501 "java.nio.BufferUnderflowException",
1502 "java.nio.BufferOverflowException",
1503 ]
1504
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001505 examine = clazz.ctors + clazz.methods
1506 for m in examine:
1507 for t in m.throws:
1508 if t in banned:
1509 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001510
1511
1512def verify_error(clazz):
1513 """Verifies that we always use Exception instead of Error."""
1514 if not clazz.extends: return
1515 if clazz.extends.endswith("Error"):
1516 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1517 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1518 error(clazz, None, None, "Exceptions must be named FooException")
1519
1520
1521def verify_units(clazz):
1522 """Verifies that we use consistent naming for units."""
1523
1524 # If we find K, recommend replacing with V
1525 bad = {
1526 "Ns": "Nanos",
1527 "Ms": "Millis or Micros",
1528 "Sec": "Seconds", "Secs": "Seconds",
1529 "Hr": "Hours", "Hrs": "Hours",
1530 "Mo": "Months", "Mos": "Months",
1531 "Yr": "Years", "Yrs": "Years",
1532 "Byte": "Bytes", "Space": "Bytes",
1533 }
1534
1535 for m in clazz.methods:
1536 if m.typ not in ["short","int","long"]: continue
1537 for k, v in bad.iteritems():
1538 if m.name.endswith(k):
1539 error(clazz, m, None, "Expected method name units to be " + v)
1540 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1541 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1542 if m.name.endswith("Seconds"):
1543 error(clazz, m, None, "Returned time values must be in milliseconds")
1544
1545 for m in clazz.methods:
1546 typ = m.typ
1547 if typ == "void":
1548 if len(m.args) != 1: continue
1549 typ = m.args[0]
1550
1551 if m.name.endswith("Fraction") and typ != "float":
1552 error(clazz, m, None, "Fractions must use floats")
1553 if m.name.endswith("Percentage") and typ != "int":
1554 error(clazz, m, None, "Percentage must use ints")
1555
1556
1557def verify_closable(clazz):
1558 """Verifies that classes are AutoClosable."""
Adrian Roosb787c182019-01-03 18:54:33 +01001559 if clazz.implements == "java.lang.AutoCloseable": return
1560 if clazz.implements == "java.io.Closeable": return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001561
1562 for m in clazz.methods:
1563 if len(m.args) > 0: continue
1564 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1565 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1566 return
1567
1568
Jake Wharton9e6738f2017-08-23 11:59:55 -04001569def verify_member_name_not_kotlin_keyword(clazz):
1570 """Prevent method names which are keywords in Kotlin."""
1571
1572 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1573 # This list does not include Java keywords as those are already impossible to use.
1574 keywords = [
1575 'as',
1576 'fun',
1577 'in',
1578 'is',
1579 'object',
1580 'typealias',
1581 'val',
1582 'var',
1583 'when',
1584 ]
1585
1586 for m in clazz.methods:
1587 if m.name in keywords:
1588 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1589 for f in clazz.fields:
1590 if f.name in keywords:
1591 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1592
1593
1594def verify_method_name_not_kotlin_operator(clazz):
1595 """Warn about method names which become operators in Kotlin."""
1596
1597 binary = set()
1598
1599 def unique_binary_op(m, op):
1600 if op in binary:
1601 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1602 binary.add(op)
1603
1604 for m in clazz.methods:
1605 if 'static' in m.split:
1606 continue
1607
1608 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1609 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1610 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1611
1612 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1613 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1614 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1615 # practical way of checking that relationship here.
1616 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1617
1618 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1619 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1620 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1621 unique_binary_op(m, m.name)
1622
1623 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1624 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1625 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1626
1627 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1628 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1629 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1630
1631 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1632 if m.name == 'invoke':
1633 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1634
1635 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1636 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1637 and len(m.args) == 1 \
1638 and m.typ == 'void':
1639 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1640 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1641
1642
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001643def verify_collections_over_arrays(clazz):
1644 """Warn that [] should be Collections."""
1645
Adrian Roosb787c182019-01-03 18:54:33 +01001646 if "@interface" in clazz.split:
1647 return
1648
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001649 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1650 for m in clazz.methods:
1651 if m.typ.endswith("[]") and m.typ not in safe:
1652 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1653 for arg in m.args:
1654 if arg.endswith("[]") and arg not in safe:
1655 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1656
1657
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001658def verify_user_handle(clazz):
1659 """Methods taking UserHandle should be ForUser or AsUser."""
1660 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1661 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1662 if clazz.fullname == "android.content.pm.LauncherApps": return
1663 if clazz.fullname == "android.os.UserHandle": return
1664 if clazz.fullname == "android.os.UserManager": return
1665
1666 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001667 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001668
1669 has_arg = "android.os.UserHandle" in m.args
1670 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1671
1672 if clazz.fullname.endswith("Manager") and has_arg:
1673 warn(clazz, m, None, "When a method overload is needed to target a specific "
1674 "UserHandle, callers should be directed to use "
1675 "Context.createPackageContextAsUser() and re-obtain the relevant "
1676 "Manager, and no new API should be added")
1677 elif has_arg and not has_name:
1678 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1679 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001680
1681
1682def verify_params(clazz):
1683 """Parameter classes should be 'Params'."""
1684 if clazz.name.endswith("Params"): return
1685 if clazz.fullname == "android.app.ActivityOptions": return
1686 if clazz.fullname == "android.app.BroadcastOptions": return
1687 if clazz.fullname == "android.os.Bundle": return
1688 if clazz.fullname == "android.os.BaseBundle": return
1689 if clazz.fullname == "android.os.PersistableBundle": return
1690
1691 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1692 for b in bad:
1693 if clazz.name.endswith(b):
1694 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1695
1696
1697def verify_services(clazz):
1698 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1699 if clazz.fullname != "android.content.Context": return
1700
1701 for f in clazz.fields:
1702 if f.typ != "java.lang.String": continue
1703 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1704 if found:
1705 expected = found.group(1).lower()
1706 if f.value != expected:
1707 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1708
1709
1710def verify_tense(clazz):
1711 """Verify tenses of method names."""
1712 if clazz.fullname.startswith("android.opengl"): return
1713
1714 for m in clazz.methods:
1715 if m.name.endswith("Enable"):
1716 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1717
1718
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001719def verify_icu(clazz):
1720 """Verifies that richer ICU replacements are used."""
1721 better = {
1722 "java.util.TimeZone": "android.icu.util.TimeZone",
1723 "java.util.Calendar": "android.icu.util.Calendar",
1724 "java.util.Locale": "android.icu.util.ULocale",
1725 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1726 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1727 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1728 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1729 "java.lang.Character": "android.icu.lang.UCharacter",
1730 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1731 "java.text.Collator": "android.icu.text.Collator",
1732 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1733 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1734 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1735 "java.text.DateFormat": "android.icu.text.DateFormat",
1736 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1737 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1738 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1739 }
1740
1741 for m in clazz.ctors + clazz.methods:
1742 types = []
1743 types.extend(m.typ)
1744 types.extend(m.args)
1745 for arg in types:
1746 if arg in better:
1747 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1748
1749
1750def verify_clone(clazz):
1751 """Verify that clone() isn't implemented; see EJ page 61."""
1752 for m in clazz.methods:
1753 if m.name == "clone":
1754 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1755
1756
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001757def verify_pfd(clazz):
1758 """Verify that android APIs use PFD over FD."""
1759 examine = clazz.ctors + clazz.methods
1760 for m in examine:
1761 if m.typ == "java.io.FileDescriptor":
1762 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1763 if m.typ == "int":
1764 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1765 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1766 for arg in m.args:
1767 if arg == "java.io.FileDescriptor":
1768 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1769
1770 for f in clazz.fields:
1771 if f.typ == "java.io.FileDescriptor":
1772 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1773
1774
1775def verify_numbers(clazz):
1776 """Discourage small numbers types like short and byte."""
1777
1778 discouraged = ["short","byte"]
1779
1780 for c in clazz.ctors:
1781 for arg in c.args:
1782 if arg in discouraged:
1783 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1784
1785 for f in clazz.fields:
1786 if f.typ in discouraged:
1787 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1788
1789 for m in clazz.methods:
1790 if m.typ in discouraged:
1791 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1792 for arg in m.args:
1793 if arg in discouraged:
1794 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1795
1796
1797def verify_singleton(clazz):
1798 """Catch singleton objects with constructors."""
1799
1800 singleton = False
1801 for m in clazz.methods:
1802 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1803 singleton = True
1804
1805 if singleton:
1806 for c in clazz.ctors:
1807 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1808
1809
1810
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001811def is_interesting(clazz):
1812 """Test if given class is interesting from an Android PoV."""
1813
1814 if clazz.pkg.name.startswith("java"): return False
1815 if clazz.pkg.name.startswith("junit"): return False
1816 if clazz.pkg.name.startswith("org.apache"): return False
1817 if clazz.pkg.name.startswith("org.xml"): return False
1818 if clazz.pkg.name.startswith("org.json"): return False
1819 if clazz.pkg.name.startswith("org.w3c"): return False
1820 if clazz.pkg.name.startswith("android.icu."): return False
1821 return True
1822
1823
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001824def examine_clazz(clazz):
1825 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001826
1827 notice(clazz)
1828
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001829 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001830
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001831 verify_constants(clazz)
1832 verify_enums(clazz)
1833 verify_class_names(clazz)
1834 verify_method_names(clazz)
1835 verify_callbacks(clazz)
1836 verify_listeners(clazz)
1837 verify_actions(clazz)
1838 verify_extras(clazz)
1839 verify_equals(clazz)
1840 verify_parcelable(clazz)
1841 verify_protected(clazz)
1842 verify_fields(clazz)
1843 verify_register(clazz)
1844 verify_sync(clazz)
1845 verify_intent_builder(clazz)
1846 verify_helper_classes(clazz)
1847 verify_builder(clazz)
1848 verify_aidl(clazz)
1849 verify_internal(clazz)
1850 verify_layering(clazz)
1851 verify_boolean(clazz)
1852 verify_collections(clazz)
1853 verify_flags(clazz)
1854 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001855 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001856 verify_bitset(clazz)
1857 verify_manager(clazz)
1858 verify_boxed(clazz)
1859 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001860 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001861 verify_callback_handlers(clazz)
1862 verify_context_first(clazz)
1863 verify_listener_last(clazz)
1864 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001865 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001866 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001867 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001868 verify_runtime_exceptions(clazz)
1869 verify_error(clazz)
1870 verify_units(clazz)
1871 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001872 verify_member_name_not_kotlin_keyword(clazz)
1873 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001874 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001875 verify_user_handle(clazz)
1876 verify_params(clazz)
1877 verify_services(clazz)
1878 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001879 verify_icu(clazz)
1880 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001881 verify_pfd(clazz)
1882 verify_numbers(clazz)
1883 verify_singleton(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001884
1885
Adrian Roos038a0292018-12-19 17:11:21 +01001886def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001887 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001888 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001889 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001890 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01001891 _parse_stream(stream, examine_clazz, base_f=base_stream,
1892 in_classes_with_base=in_classes_with_base,
1893 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001894 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001895
1896
1897def examine_api(api):
1898 """Find all style issues in the given parsed API."""
1899 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001900 failures = {}
1901 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001902 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001903 return failures
1904
1905
Jeff Sharkey037458a2014-09-04 15:46:20 -07001906def verify_compat(cur, prev):
1907 """Find any incompatible API changes between two levels."""
1908 global failures
1909
1910 def class_exists(api, test):
1911 return test.fullname in api
1912
1913 def ctor_exists(api, clazz, test):
1914 for m in clazz.ctors:
1915 if m.ident == test.ident: return True
1916 return False
1917
1918 def all_methods(api, clazz):
1919 methods = list(clazz.methods)
1920 if clazz.extends is not None:
1921 methods.extend(all_methods(api, api[clazz.extends]))
1922 return methods
1923
1924 def method_exists(api, clazz, test):
1925 methods = all_methods(api, clazz)
1926 for m in methods:
1927 if m.ident == test.ident: return True
1928 return False
1929
1930 def field_exists(api, clazz, test):
1931 for f in clazz.fields:
1932 if f.ident == test.ident: return True
1933 return False
1934
1935 failures = {}
1936 for key in sorted(prev.keys()):
1937 prev_clazz = prev[key]
1938
1939 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001940 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001941 continue
1942
1943 cur_clazz = cur[key]
1944
1945 for test in prev_clazz.ctors:
1946 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001947 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001948
1949 methods = all_methods(prev, prev_clazz)
1950 for test in methods:
1951 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001952 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001953
1954 for test in prev_clazz.fields:
1955 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001956 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001957
1958 return failures
1959
1960
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001961def show_deprecations_at_birth(cur, prev):
1962 """Show API deprecations at birth."""
1963 global failures
1964
1965 # Remove all existing things so we're left with new
1966 for prev_clazz in prev.values():
1967 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001968 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001969
1970 sigs = { i.ident: i for i in prev_clazz.ctors }
1971 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
1972 sigs = { i.ident: i for i in prev_clazz.methods }
1973 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
1974 sigs = { i.ident: i for i in prev_clazz.fields }
1975 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
1976
1977 # Forget about class entirely when nothing new
1978 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
1979 del cur[prev_clazz.fullname]
1980
1981 for clazz in cur.values():
Adrian Roosb787c182019-01-03 18:54:33 +01001982 if "deprecated" in clazz.split and not clazz.fullname in prev:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001983 error(clazz, None, None, "Found API deprecation at birth")
1984
1985 for i in clazz.ctors + clazz.methods + clazz.fields:
Adrian Roosb787c182019-01-03 18:54:33 +01001986 if "deprecated" in i.split:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001987 error(clazz, i, None, "Found API deprecation at birth")
1988
1989 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
1990 format(reset=True)))
1991 for f in sorted(failures):
1992 print failures[f]
1993 print
1994
1995
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001996def show_stats(cur, prev):
1997 """Show API stats."""
1998
1999 stats = collections.defaultdict(int)
2000 for cur_clazz in cur.values():
2001 if not is_interesting(cur_clazz): continue
2002
2003 if cur_clazz.fullname not in prev:
2004 stats['new_classes'] += 1
2005 stats['new_ctors'] += len(cur_clazz.ctors)
2006 stats['new_methods'] += len(cur_clazz.methods)
2007 stats['new_fields'] += len(cur_clazz.fields)
2008 else:
2009 prev_clazz = prev[cur_clazz.fullname]
2010
2011 sigs = { i.ident: i for i in prev_clazz.ctors }
2012 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
2013 sigs = { i.ident: i for i in prev_clazz.methods }
2014 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
2015 sigs = { i.ident: i for i in prev_clazz.fields }
2016 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
2017
2018 if ctors + methods + fields > 0:
2019 stats['extend_classes'] += 1
2020 stats['extend_ctors'] += ctors
2021 stats['extend_methods'] += methods
2022 stats['extend_fields'] += fields
2023
2024 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
2025 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
2026
2027
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002028if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002029 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
2030 patterns. It ignores lint messages from a previous API level, if provided.")
2031 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
2032 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
2033 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01002034 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
2035 help="The base current.txt to use when examining system-current.txt or"
2036 " test-current.txt")
2037 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
2038 help="The base previous.txt to use when examining system-previous.txt or"
2039 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002040 parser.add_argument("--no-color", action='store_const', const=True,
2041 help="Disable terminal colors")
2042 parser.add_argument("--allow-google", action='store_const', const=True,
2043 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002044 parser.add_argument("--show-noticed", action='store_const', const=True,
2045 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002046 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
2047 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002048 parser.add_argument("--show-stats", action='store_const', const=True,
2049 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002050 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002051
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002052 if args['no_color']:
2053 USE_COLOR = False
2054
2055 if args['allow_google']:
2056 ALLOW_GOOGLE = True
2057
2058 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002059 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002060 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002061 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002062
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002063 if args['show_deprecations_at_birth']:
2064 with current_file as f:
2065 cur = _parse_stream(f)
2066 with previous_file as f:
2067 prev = _parse_stream(f)
2068 show_deprecations_at_birth(cur, prev)
2069 sys.exit()
2070
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002071 if args['show_stats']:
2072 with current_file as f:
2073 cur = _parse_stream(f)
2074 with previous_file as f:
2075 prev = _parse_stream(f)
2076 show_stats(cur, prev)
2077 sys.exit()
2078
Adrian Roos038a0292018-12-19 17:11:21 +01002079 classes_with_base = []
2080
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002081 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002082 if base_current_file:
2083 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002084 cur_fail, cur_noticed = examine_stream(f, base_f,
2085 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002086 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002087 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
2088
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002089 if not previous_file is None:
2090 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002091 if base_previous_file:
2092 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002093 prev_fail, prev_noticed = examine_stream(f, base_f,
2094 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002095 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002096 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002097
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002098 # ignore errors from previous API level
2099 for p in prev_fail:
2100 if p in cur_fail:
2101 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002102
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002103 # ignore classes unchanged from previous API level
2104 for k, v in prev_noticed.iteritems():
2105 if k in cur_noticed and v == cur_noticed[k]:
2106 del cur_noticed[k]
2107
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002108 """
2109 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002110 # look for compatibility issues
2111 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002112
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002113 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2114 for f in sorted(compat_fail):
2115 print compat_fail[f]
2116 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002117 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002118
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002119 if args['show_noticed'] and len(cur_noticed) != 0:
2120 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2121 for f in sorted(cur_noticed.keys()):
2122 print f
2123 print
2124
Jason Monk53b2a732017-11-10 15:43:17 -05002125 if len(cur_fail) != 0:
2126 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2127 for f in sorted(cur_fail):
2128 print cur_fail[f]
2129 print
2130 sys.exit(77)