blob: 441c1209a742629f17be11f3cb18e749cb858d59 [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
Adrian Roos1f1b6a82019-01-05 20:09:38 +010029import re, sys, collections, traceback, argparse, itertools
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 Roose5eeae72019-01-04 20:10:06 +0100223class V2Tokenizer(object):
224 __slots__ = ["raw"]
225
Adrian Roosb787c182019-01-03 18:54:33 +0100226 DELIMITER = re.compile(r'\s+|[()@<>;,={}/"]|\[\]')
227 STRING_SPECIAL = re.compile(r'["\\]')
228
229 def __init__(self, raw):
230 self.raw = raw
Adrian Roosb787c182019-01-03 18:54:33 +0100231
Adrian Roose5eeae72019-01-04 20:10:06 +0100232 def tokenize(self):
233 tokens = []
234 current = 0
235 raw = self.raw
236 length = len(raw)
Adrian Roosb787c182019-01-03 18:54:33 +0100237
Adrian Roose5eeae72019-01-04 20:10:06 +0100238 while current < length:
239 while current < length:
240 start = current
241 match = V2Tokenizer.DELIMITER.search(raw, start)
242 if match is not None:
243 match_start = match.start()
244 if match_start == current:
245 end = match.end()
246 else:
247 end = match_start
248 else:
249 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100250
Adrian Roose5eeae72019-01-04 20:10:06 +0100251 token = raw[start:end]
252 current = end
Adrian Roosb787c182019-01-03 18:54:33 +0100253
Adrian Roose5eeae72019-01-04 20:10:06 +0100254 if token == "" or token[0] == " ":
255 continue
256 else:
257 break
Adrian Roosb787c182019-01-03 18:54:33 +0100258
Adrian Roose5eeae72019-01-04 20:10:06 +0100259 if token == "@":
260 if raw[start:start+11] == "@interface ":
261 current = start + 11
262 tokens.append("@interface")
263 continue
264 elif token == '/':
265 if raw[start:start+2] == "//":
266 current = length
267 continue
268 elif token == '"':
269 current, string_token = self.tokenize_string(raw, length, current)
270 tokens.append(token + string_token)
271 continue
Adrian Roosb787c182019-01-03 18:54:33 +0100272
Adrian Roose5eeae72019-01-04 20:10:06 +0100273 tokens.append(token)
Adrian Roosb787c182019-01-03 18:54:33 +0100274
Adrian Roose5eeae72019-01-04 20:10:06 +0100275 return tokens
Adrian Roosb787c182019-01-03 18:54:33 +0100276
Adrian Roose5eeae72019-01-04 20:10:06 +0100277 def tokenize_string(self, raw, length, current):
278 start = current
279 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100280 while start < end:
Adrian Roose5eeae72019-01-04 20:10:06 +0100281 match = V2Tokenizer.STRING_SPECIAL.search(raw, start)
Adrian Roosb787c182019-01-03 18:54:33 +0100282 if match:
283 if match.group() == '"':
284 end = match.end()
285 break
286 elif match.group() == '\\':
287 # ignore whatever is after the slash
288 start += 2
289 else:
290 raise ValueError("Unexpected match: `%s`" % (match.group()))
291 else:
Adrian Roose5eeae72019-01-04 20:10:06 +0100292 raise ValueError("Unexpected EOF tokenizing string: `%s`" % (raw[current - 1:],))
Adrian Roosb787c182019-01-03 18:54:33 +0100293
Adrian Roose5eeae72019-01-04 20:10:06 +0100294 token = raw[current:end]
295 return end, token
Adrian Roosb787c182019-01-03 18:54:33 +0100296
Adrian Roose5eeae72019-01-04 20:10:06 +0100297class V2LineParser(object):
298 __slots__ = ["tokenized", "current", "len"]
299
Adrian Roosb787c182019-01-03 18:54:33 +0100300 MODIFIERS = set("public protected internal private abstract default static final transient volatile synchronized".split())
301 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())
302
303 def __init__(self, raw):
Adrian Roose5eeae72019-01-04 20:10:06 +0100304 self.tokenized = V2Tokenizer(raw).tokenize()
Adrian Roosb787c182019-01-03 18:54:33 +0100305 self.current = 0
Adrian Roose5eeae72019-01-04 20:10:06 +0100306 self.len = len(self.tokenized)
Adrian Roosb787c182019-01-03 18:54:33 +0100307
308 def parse_into_method(self, method):
309 method.split = []
310 kind = self.parse_one_of("ctor", "method")
311 method.split.append(kind)
312 annotations = self.parse_annotations()
313 method.split.extend(self.parse_modifiers())
314 self.parse_matching_paren("<", ">")
315 if "@Deprecated" in annotations:
316 method.split.append("deprecated")
317 if kind == "ctor":
318 method.typ = "ctor"
319 else:
320 method.typ = self.parse_type()
321 method.split.append(method.typ)
322 method.name = self.parse_name()
323 method.split.append(method.name)
324 self.parse_token("(")
325 method.args = self.parse_args()
326 self.parse_token(")")
327 method.throws = self.parse_throws()
328 if "@interface" in method.clazz.split:
329 self.parse_annotation_default()
330 self.parse_token(";")
331 self.parse_eof()
332
333 def parse_into_class(self, clazz):
334 clazz.split = []
335 annotations = self.parse_annotations()
336 if "@Deprecated" in annotations:
337 clazz.split.append("deprecated")
338 clazz.split.extend(self.parse_modifiers())
339 kind = self.parse_one_of("class", "interface", "@interface", "enum")
340 if kind == "enum":
341 # enums are implicitly final
342 clazz.split.append("final")
343 clazz.split.append(kind)
344 clazz.fullname = self.parse_name()
345 self.parse_matching_paren("<", ">")
346 extends = self.parse_extends()
347 clazz.extends = extends[0] if extends else None
348 implements = self.parse_implements()
349 clazz.implements = implements[0] if implements else None
350 # The checks assume that interfaces are always found in implements, which isn't true for
351 # subinterfaces.
352 if not implements and "interface" in clazz.split:
353 clazz.implements = clazz.extends
354 self.parse_token("{")
355 self.parse_eof()
356
357 def parse_into_field(self, field):
358 kind = self.parse_token("field")
359 field.split = [kind]
360 annotations = self.parse_annotations()
361 if "@Deprecated" in annotations:
362 field.split.append("deprecated")
363 field.split.extend(self.parse_modifiers())
364 field.typ = self.parse_type()
365 field.split.append(field.typ)
366 field.name = self.parse_name()
367 field.split.append(field.name)
368 if self.parse_if("="):
369 field.value = self.parse_value_stripped()
370 else:
371 field.value = None
372
373 self.parse_token(";")
374 self.parse_eof()
375
376 def lookahead(self):
377 return self.tokenized[self.current]
378
379 def parse_one_of(self, *options):
380 found = self.lookahead()
381 if found not in options:
382 raise ValueError("Parsing failed, expected one of `%s` but found `%s` in %s" % (options, found, repr(self.tokenized)))
383 return self.parse_token()
384
385 def parse_token(self, tok = None):
386 found = self.lookahead()
387 if tok is not None and found != tok:
388 raise ValueError("Parsing failed, expected `%s` but found `%s` in %s" % (tok, found, repr(self.tokenized)))
389 self.current += 1
390 return found
391
392 def eof(self):
Adrian Roose5eeae72019-01-04 20:10:06 +0100393 return self.current == self.len
Adrian Roosb787c182019-01-03 18:54:33 +0100394
395 def parse_eof(self):
396 if not self.eof():
397 raise ValueError("Parsing failed, expected EOF, but %s has not been parsed in %s" % (self.tokenized[self.current:], self.tokenized))
398
399 def parse_if(self, tok):
400 if not self.eof() and self.lookahead() == tok:
401 self.parse_token()
402 return True
403 return False
404
405 def parse_annotations(self):
406 ret = []
407 while self.lookahead() == "@":
408 ret.append(self.parse_annotation())
409 return ret
410
411 def parse_annotation(self):
412 ret = self.parse_token("@") + self.parse_token()
413 self.parse_matching_paren("(", ")")
414 return ret
415
416 def parse_matching_paren(self, open, close):
417 start = self.current
418 if not self.parse_if(open):
419 return
420 length = len(self.tokenized)
421 count = 1
422 while count > 0:
423 if self.current == length:
424 raise ValueError("Unexpected EOF looking for closing paren: `%s`" % (self.tokenized[start:],))
425 t = self.parse_token()
426 if t == open:
427 count += 1
428 elif t == close:
429 count -= 1
430 return self.tokenized[start:self.current]
431
432 def parse_modifiers(self):
433 ret = []
434 while self.lookahead() in V2LineParser.MODIFIERS:
435 ret.append(self.parse_token())
436 return ret
437
438 def parse_type(self):
439 type = self.parse_token()
440 if type in V2LineParser.JAVA_LANG_TYPES:
441 type = "java.lang." + type
442 self.parse_matching_paren("<", ">")
443 while self.parse_if("[]"):
444 type += "[]"
445 return type
446
447 def parse_arg_type(self):
448 type = self.parse_type()
449 if self.parse_if("..."):
450 type += "..."
451 return type
452
453 def parse_name(self):
454 return self.parse_token()
455
456 def parse_args(self):
457 args = []
458 if self.lookahead() == ")":
459 return args
460
461 while True:
462 args.append(self.parse_arg())
463 if self.lookahead() == ")":
464 return args
465 self.parse_token(",")
466
467 def parse_arg(self):
468 self.parse_annotations()
469 return self.parse_arg_type()
470
471 def parse_throws(self):
472 ret = []
473 if self.parse_if("throws"):
474 ret.append(self.parse_type())
475 while self.parse_if(","):
476 ret.append(self.parse_type())
477 return ret
478
479 def parse_extends(self):
480 if self.parse_if("extends"):
481 return self.parse_space_delimited_type_list()
482 return []
483
484 def parse_implements(self):
485 if self.parse_if("implements"):
486 return self.parse_space_delimited_type_list()
487 return []
488
489 def parse_space_delimited_type_list(self, terminals = ["implements", "{"]):
490 types = []
491 while True:
492 types.append(self.parse_type())
493 if self.lookahead() in terminals:
494 return types
495
496 def parse_annotation_default(self):
497 if self.parse_if("default"):
498 self.parse_value()
499
500 def parse_value(self):
501 if self.lookahead() == "{":
502 return " ".join(self.parse_matching_paren("{", "}"))
503 elif self.lookahead() == "(":
504 return " ".join(self.parse_matching_paren("(", ")"))
505 else:
506 return self.parse_token()
507
508 def parse_value_stripped(self):
509 value = self.parse_value()
510 if value[0] == '"':
511 return value[1:-1]
512 return value
513
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700514
Adrian Roos038a0292018-12-19 17:11:21 +0100515def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
516 in_classes_with_base=[]):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700517 api = {}
Adrian Roos038a0292018-12-19 17:11:21 +0100518 in_classes_with_base = _retry_iterator(in_classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +0100519
520 if base_f:
Adrian Roos038a0292018-12-19 17:11:21 +0100521 base_classes = _retry_iterator(_parse_stream_to_generator(base_f))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100522 else:
523 base_classes = []
524
Adrian Roos038a0292018-12-19 17:11:21 +0100525 def handle_class(clazz):
Adrian Roos6eb57b02018-12-13 22:08:29 +0100526 if clazz_cb:
527 clazz_cb(clazz)
528 else: # In callback mode, don't keep track of the full API
529 api[clazz.fullname] = clazz
530
Adrian Roos038a0292018-12-19 17:11:21 +0100531 def handle_missed_classes_with_base(clazz):
532 for c in _yield_until_matching_class(in_classes_with_base, clazz):
533 base_class = _skip_to_matching_class(base_classes, c)
534 if base_class:
535 handle_class(base_class)
536
537 for clazz in _parse_stream_to_generator(f):
538 # Before looking at clazz, let's see if there's some classes that were not present, but
539 # may have an entry in the base stream.
540 handle_missed_classes_with_base(clazz)
541
542 base_class = _skip_to_matching_class(base_classes, clazz)
543 if base_class:
544 clazz.merge_from(base_class)
545 if out_classes_with_base is not None:
546 out_classes_with_base.append(clazz)
547 handle_class(clazz)
548
549 handle_missed_classes_with_base(None)
550
Adrian Roos6eb57b02018-12-13 22:08:29 +0100551 return api
552
553def _parse_stream_to_generator(f):
554 line = 0
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700555 pkg = None
556 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700557 blame = None
Adrian Roosb787c182019-01-03 18:54:33 +0100558 sig_format = 1
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700559
560 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800561 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700562 line += 1
563 raw = raw.rstrip()
564 match = re_blame.match(raw)
565 if match is not None:
566 blame = match.groups()[0:2]
567 raw = match.groups()[2]
568 else:
569 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700570
Adrian Roosb787c182019-01-03 18:54:33 +0100571 if line == 1 and raw == "// Signature format: 2.0":
572 sig_format = 2
573 elif raw.startswith("package"):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700574 pkg = Package(line, raw, blame)
575 elif raw.startswith(" ") and raw.endswith("{"):
Adrian Roosb787c182019-01-03 18:54:33 +0100576 clazz = Class(pkg, line, raw, blame, sig_format=sig_format)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700577 elif raw.startswith(" ctor"):
Adrian Roosb787c182019-01-03 18:54:33 +0100578 clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700579 elif raw.startswith(" method"):
Adrian Roosb787c182019-01-03 18:54:33 +0100580 clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700581 elif raw.startswith(" field"):
Adrian Roosb787c182019-01-03 18:54:33 +0100582 clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100583 elif raw.startswith(" }") and clazz:
Adrian Roos038a0292018-12-19 17:11:21 +0100584 yield clazz
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800585
Adrian Roos5ed42b62018-12-19 17:10:22 +0100586def _retry_iterator(it):
587 """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
588 for e in it:
589 while True:
590 retry = yield e
591 if not retry:
592 break
593 # send() was called, asking us to redeliver clazz on next(). Still need to yield
594 # a dummy value to the send() first though.
595 if (yield "Returning clazz on next()"):
596 raise TypeError("send() must be followed by next(), not send()")
597
Adrian Roos038a0292018-12-19 17:11:21 +0100598def _skip_to_matching_class(classes, needle):
599 """Takes a classes iterator and consumes entries until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700600
Adrian Roos6eb57b02018-12-13 22:08:29 +0100601 This relies on classes being sorted by package and class name."""
602
603 for clazz in classes:
604 if clazz.pkg.name < needle.pkg.name:
605 # We haven't reached the right package yet
606 continue
Adrian Roos5ed42b62018-12-19 17:10:22 +0100607 if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
608 # We're in the right package, but not the right class yet
Adrian Roos6eb57b02018-12-13 22:08:29 +0100609 continue
610 if clazz.fullname == needle.fullname:
611 return clazz
612 # We ran past the right class. Send it back into the generator, then report failure.
613 classes.send(clazz)
614 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700615
Adrian Roos038a0292018-12-19 17:11:21 +0100616def _yield_until_matching_class(classes, needle):
617 """Takes a class iterator and yields entries it until it reaches the class we're looking for.
618
619 This relies on classes being sorted by package and class name."""
620
621 for clazz in classes:
622 if needle is None:
623 yield clazz
624 elif clazz.pkg.name < needle.pkg.name:
625 # We haven't reached the right package yet
626 yield clazz
627 elif clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
628 # We're in the right package, but not the right class yet
629 yield clazz
630 elif clazz.fullname == needle.fullname:
631 # Class found, abort.
632 return
633 else:
634 # We ran past the right class. Send it back into the iterator, then abort.
635 classes.send(clazz)
636 return
637
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700638class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800639 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700640 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700641 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800642 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700643 self.msg = msg
644
645 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800646 self.head = "Error %s" % (rule) if rule else "Error"
647 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 -0700648 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800649 self.head = "Warning %s" % (rule) if rule else "Warning"
650 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 -0700651
652 self.line = clazz.line
653 blame = clazz.blame
654 if detail is not None:
655 dump += "\n in " + repr(detail)
656 self.line = detail.line
657 blame = detail.blame
658 dump += "\n in " + repr(clazz)
659 dump += "\n in " + repr(clazz.pkg)
660 dump += "\n at line " + repr(self.line)
661 if blame is not None:
662 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
663
664 self.dump = dump
665
666 def __repr__(self):
667 return self.dump
668
669
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700670failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700671
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800672def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700673 """Records an API failure to be processed later."""
674 global failures
675
Adrian Roosb787c182019-01-03 18:54:33 +0100676 sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700677 sig = sig.replace(" deprecated ", " ")
678
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800679 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700680
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700681
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800682def warn(clazz, detail, rule, msg):
683 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700684
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800685def error(clazz, detail, rule, msg):
686 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700687
688
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700689noticed = {}
690
691def notice(clazz):
692 global noticed
693
694 noticed[clazz.fullname] = hash(clazz)
695
696
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700697def verify_constants(clazz):
698 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700699 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600700 if clazz.fullname.startswith("android.os.Build"): return
701 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700702
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600703 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700704 for f in clazz.fields:
705 if "static" in f.split and "final" in f.split:
706 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800707 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600708 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700709 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
710 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600711 if f.typ in req and f.value is None:
712 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700713
714
715def verify_enums(clazz):
716 """Enums are bad, mmkay?"""
Adrian Roosb787c182019-01-03 18:54:33 +0100717 if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800718 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700719
720
721def verify_class_names(clazz):
722 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700723 if clazz.fullname.startswith("android.opengl"): return
724 if clazz.fullname.startswith("android.renderscript"): return
725 if re.match("android\.R\.[a-z]+", clazz.fullname): return
726
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700727 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800728 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700729 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800730 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700731 if clazz.name.endswith("Impl"):
732 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700733
734
735def verify_method_names(clazz):
736 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700737 if clazz.fullname.startswith("android.opengl"): return
738 if clazz.fullname.startswith("android.renderscript"): return
739 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700740
741 for m in clazz.methods:
742 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800743 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700744 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800745 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700746
747
748def verify_callbacks(clazz):
749 """Verify Callback classes.
750 All callback classes must be abstract.
751 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700752 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700753
754 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800755 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700756 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800757 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700758
759 if clazz.name.endswith("Callback"):
760 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800761 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700762
763 for m in clazz.methods:
764 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800765 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700766
767
768def verify_listeners(clazz):
769 """Verify Listener classes.
770 All Listener classes must be interface.
771 All methods must follow onFoo() naming style.
772 If only a single method, it must match class name:
773 interface OnFooListener { void onFoo() }"""
774
775 if clazz.name.endswith("Listener"):
Adrian Roosb787c182019-01-03 18:54:33 +0100776 if "abstract" in clazz.split and "class" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800777 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700778
779 for m in clazz.methods:
780 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800781 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700782
783 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
784 m = clazz.methods[0]
785 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800786 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700787
788
789def verify_actions(clazz):
790 """Verify intent actions.
791 All action names must be named ACTION_FOO.
792 All action values must be scoped by package and match name:
793 package android.foo {
794 String ACTION_BAR = "android.foo.action.BAR";
795 }"""
796 for f in clazz.fields:
797 if f.value is None: continue
798 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700799 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600800 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700801
802 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
803 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
804 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800805 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700806 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700807 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700808 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700809 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700810 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700811 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
812 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700813 else:
814 prefix = clazz.pkg.name + ".action"
815 expected = prefix + "." + f.name[7:]
816 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700817 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700818
819
820def verify_extras(clazz):
821 """Verify intent extras.
822 All extra names must be named EXTRA_FOO.
823 All extra values must be scoped by package and match name:
824 package android.foo {
825 String EXTRA_BAR = "android.foo.extra.BAR";
826 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700827 if clazz.fullname == "android.app.Notification": return
828 if clazz.fullname == "android.appwidget.AppWidgetManager": return
829
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700830 for f in clazz.fields:
831 if f.value is None: continue
832 if f.name.startswith("ACTION_"): continue
833
834 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
835 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
836 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800837 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700838 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700839 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700840 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700841 elif clazz.pkg.name == "android.app.admin":
842 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700843 else:
844 prefix = clazz.pkg.name + ".extra"
845 expected = prefix + "." + f.name[6:]
846 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700847 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700848
849
850def verify_equals(clazz):
851 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700852 eq = False
853 hc = False
854 for m in clazz.methods:
Adrian Roosb787c182019-01-03 18:54:33 +0100855 if "static" in m.split: continue
856 if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
857 if m.sig_matches("int", "hashCode", []): hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700858 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800859 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700860
861
862def verify_parcelable(clazz):
863 """Verify that Parcelable objects aren't hiding required bits."""
Adrian Roosb787c182019-01-03 18:54:33 +0100864 if clazz.implements == "android.os.Parcelable":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700865 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
866 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
867 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
868
869 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800870 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700871
Adrian Roosb787c182019-01-03 18:54:33 +0100872 if "final" not in clazz.split:
Jeff Sharkey331279b2016-02-29 16:02:02 -0700873 error(clazz, None, "FW8", "Parcelable classes must be final")
874
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700875 for c in clazz.ctors:
876 if c.args == ["android.os.Parcel"]:
877 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
878
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700879
880def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800881 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700882 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600883 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700884 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800885 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700886 for f in clazz.fields:
887 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800888 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700889
890
891def verify_fields(clazz):
892 """Verify that all exposed fields are final.
893 Exposed fields must follow myName style.
894 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700895
896 IGNORE_BARE_FIELDS = [
897 "android.app.ActivityManager.RecentTaskInfo",
898 "android.app.Notification",
899 "android.content.pm.ActivityInfo",
900 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600901 "android.content.pm.ComponentInfo",
902 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700903 "android.content.pm.FeatureGroupInfo",
904 "android.content.pm.InstrumentationInfo",
905 "android.content.pm.PackageInfo",
906 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600907 "android.content.res.Configuration",
908 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700909 "android.os.Message",
910 "android.system.StructPollfd",
911 ]
912
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700913 for f in clazz.fields:
914 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700915 if clazz.fullname in IGNORE_BARE_FIELDS:
916 pass
917 elif clazz.fullname.endswith("LayoutParams"):
918 pass
919 elif clazz.fullname.startswith("android.util.Mutable"):
920 pass
921 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800922 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700923
924 if not "static" in f.split:
925 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800926 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700927
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700928 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800929 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700930
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700931 if re.match("[A-Z_]+", f.name):
932 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800933 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700934
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700935
936def verify_register(clazz):
937 """Verify parity of registration methods.
938 Callback objects use register/unregister methods.
939 Listener objects use add/remove methods."""
940 methods = [ m.name for m in clazz.methods ]
941 for m in clazz.methods:
942 if "Callback" in m.raw:
943 if m.name.startswith("register"):
944 other = "unregister" + m.name[8:]
945 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800946 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700947 if m.name.startswith("unregister"):
948 other = "register" + m.name[10:]
949 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800950 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700951
952 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800953 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700954
955 if "Listener" in m.raw:
956 if m.name.startswith("add"):
957 other = "remove" + m.name[3:]
958 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800959 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700960 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
961 other = "add" + m.name[6:]
962 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800963 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700964
965 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800966 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700967
968
969def verify_sync(clazz):
970 """Verify synchronized methods aren't exposed."""
971 for m in clazz.methods:
972 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800973 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700974
975
976def verify_intent_builder(clazz):
977 """Verify that Intent builders are createFooIntent() style."""
978 if clazz.name == "Intent": return
979
980 for m in clazz.methods:
981 if m.typ == "android.content.Intent":
982 if m.name.startswith("create") and m.name.endswith("Intent"):
983 pass
984 else:
Adam Powell539ea122015-04-10 13:01:37 -0700985 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700986
987
988def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700989 """Verify that helper classes are named consistently with what they extend.
990 All developer extendable methods should be named onFoo()."""
991 test_methods = False
Adrian Roosb787c182019-01-03 18:54:33 +0100992 if clazz.extends == "android.app.Service":
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700993 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700994 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800995 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700996
997 found = False
998 for f in clazz.fields:
999 if f.name == "SERVICE_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.ContentProvider":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001005 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001006 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001007 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001008
1009 found = False
1010 for f in clazz.fields:
1011 if f.name == "PROVIDER_INTERFACE":
1012 found = True
1013 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001014 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001015
Adrian Roosb787c182019-01-03 18:54:33 +01001016 if clazz.extends == "android.content.BroadcastReceiver":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001017 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001018 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001019 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001020
Adrian Roosb787c182019-01-03 18:54:33 +01001021 if clazz.extends == "android.app.Activity":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001022 test_methods = True
1023 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001024 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001025
1026 if test_methods:
1027 for m in clazz.methods:
1028 if "final" in m.split: continue
1029 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001030 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001031 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001032 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001033 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001034
1035
1036def verify_builder(clazz):
1037 """Verify builder classes.
1038 Methods should return the builder to enable chaining."""
Adrian Roosb787c182019-01-03 18:54:33 +01001039 if clazz.extends: return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001040 if not clazz.name.endswith("Builder"): return
1041
1042 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001043 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001044
1045 has_build = False
1046 for m in clazz.methods:
1047 if m.name == "build":
1048 has_build = True
1049 continue
1050
1051 if m.name.startswith("get"): continue
1052 if m.name.startswith("clear"): continue
1053
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001054 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001055 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001056
1057 if m.name.startswith("set"):
1058 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001059 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001060
1061 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001062 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001063
1064
1065def verify_aidl(clazz):
1066 """Catch people exposing raw AIDL."""
Adrian Roosb787c182019-01-03 18:54:33 +01001067 if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001068 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001069
1070
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001071def verify_internal(clazz):
1072 """Catch people exposing internal classes."""
1073 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001074 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001075
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001076def layering_build_ranking(ranking_list):
1077 r = {}
1078 for rank, ps in enumerate(ranking_list):
1079 if not isinstance(ps, list):
1080 ps = [ps]
1081 for p in ps:
1082 rs = r
1083 for n in p.split('.'):
1084 if n not in rs:
1085 rs[n] = {}
1086 rs = rs[n]
1087 rs['-rank'] = rank
1088 return r
1089
1090LAYERING_PACKAGE_RANKING = layering_build_ranking([
1091 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
1092 "android.app",
1093 "android.widget",
1094 "android.view",
1095 "android.animation",
1096 "android.provider",
1097 ["android.content","android.graphics.drawable"],
1098 "android.database",
1099 "android.text",
1100 "android.graphics",
1101 "android.os",
1102 "android.util"
1103])
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001104
1105def verify_layering(clazz):
1106 """Catch package layering violations.
1107 For example, something in android.os depending on android.app."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001108
1109 def rank(p):
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001110 r = None
1111 l = LAYERING_PACKAGE_RANKING
1112 for n in p.split('.'):
1113 if n in l:
1114 l = l[n]
1115 if '-rank' in l:
1116 r = l['-rank']
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001117 else:
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001118 break
1119 return r
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001120
1121 cr = rank(clazz.pkg.name)
1122 if cr is None: return
1123
1124 for f in clazz.fields:
1125 ir = rank(f.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001126 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001127 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001128
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001129 for m in itertools.chain(clazz.methods, clazz.ctors):
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001130 ir = rank(m.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001131 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001132 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001133 for arg in m.args:
1134 ir = rank(arg)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001135 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001136 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001137
1138
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001139def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001140 """Verifies that boolean accessors are named correctly.
1141 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001142
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001143 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
1144 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001145
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001146 gets = [ m for m in clazz.methods if is_get(m) ]
1147 sets = [ m for m in clazz.methods if is_set(m) ]
1148
1149 def error_if_exists(methods, trigger, expected, actual):
1150 for m in methods:
1151 if m.name == actual:
1152 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001153
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001154 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001155 if is_get(m):
1156 if re.match("is[A-Z]", m.name):
1157 target = m.name[2:]
1158 expected = "setIs" + target
1159 error_if_exists(sets, m.name, expected, "setHas" + target)
1160 elif re.match("has[A-Z]", m.name):
1161 target = m.name[3:]
1162 expected = "setHas" + target
1163 error_if_exists(sets, m.name, expected, "setIs" + target)
1164 error_if_exists(sets, m.name, expected, "set" + target)
1165 elif re.match("get[A-Z]", m.name):
1166 target = m.name[3:]
1167 expected = "set" + target
1168 error_if_exists(sets, m.name, expected, "setIs" + target)
1169 error_if_exists(sets, m.name, expected, "setHas" + target)
1170
1171 if is_set(m):
1172 if re.match("set[A-Z]", m.name):
1173 target = m.name[3:]
1174 expected = "get" + target
1175 error_if_exists(sets, m.name, expected, "is" + target)
1176 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001177
1178
1179def verify_collections(clazz):
1180 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001181 if clazz.fullname == "android.os.Bundle": return
1182
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001183 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
1184 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
1185 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001186 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001187 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001188 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001189 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001190 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001191
1192
1193def verify_flags(clazz):
1194 """Verifies that flags are non-overlapping."""
1195 known = collections.defaultdict(int)
1196 for f in clazz.fields:
1197 if "FLAG_" in f.name:
1198 try:
1199 val = int(f.value)
1200 except:
1201 continue
1202
1203 scope = f.name[0:f.name.index("FLAG_")]
1204 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001205 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001206 known[scope] |= val
1207
1208
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001209def verify_exception(clazz):
1210 """Verifies that methods don't throw generic exceptions."""
1211 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001212 for t in m.throws:
1213 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
1214 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001215
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001216 if t in ["android.os.RemoteException"]:
1217 if clazz.name == "android.content.ContentProviderClient": continue
1218 if clazz.name == "android.os.Binder": continue
1219 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -07001220
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001221 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
1222
1223 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
1224 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -07001225
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001226GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001227
1228def verify_google(clazz):
1229 """Verifies that APIs never reference Google."""
1230
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001231 if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001232 error(clazz, None, None, "Must never reference Google")
1233
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001234 for test in clazz.ctors, clazz.fields, clazz.methods:
1235 for t in test:
1236 if GOOGLE_IGNORECASE.search(t.raw) is not None:
1237 error(clazz, t, None, "Must never reference Google")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001238
1239
1240def verify_bitset(clazz):
1241 """Verifies that we avoid using heavy BitSet."""
1242
1243 for f in clazz.fields:
1244 if f.typ == "java.util.BitSet":
1245 error(clazz, f, None, "Field type must not be heavy BitSet")
1246
1247 for m in clazz.methods:
1248 if m.typ == "java.util.BitSet":
1249 error(clazz, m, None, "Return type must not be heavy BitSet")
1250 for arg in m.args:
1251 if arg == "java.util.BitSet":
1252 error(clazz, m, None, "Argument type must not be heavy BitSet")
1253
1254
1255def verify_manager(clazz):
1256 """Verifies that FooManager is only obtained from Context."""
1257
1258 if not clazz.name.endswith("Manager"): return
1259
1260 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001261 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001262
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001263 for m in clazz.methods:
1264 if m.typ == clazz.fullname:
1265 error(clazz, m, None, "Managers must always be obtained from Context")
1266
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001267
1268def verify_boxed(clazz):
1269 """Verifies that methods avoid boxed primitives."""
1270
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001271 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 -08001272
1273 for c in clazz.ctors:
1274 for arg in c.args:
1275 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001276 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001277
1278 for f in clazz.fields:
1279 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001280 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001281
1282 for m in clazz.methods:
1283 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001284 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001285 for arg in m.args:
1286 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001287 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001288
1289
1290def verify_static_utils(clazz):
1291 """Verifies that helper classes can't be constructed."""
1292 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001293 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001294
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001295 # Only care about classes with default constructors
1296 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
1297 test = []
1298 test.extend(clazz.fields)
1299 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001300
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001301 if len(test) == 0: return
1302 for t in test:
1303 if "static" not in t.split:
1304 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001305
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001306 error(clazz, None, None, "Fully-static utility classes must not have constructor")
1307
1308
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001309def verify_overload_args(clazz):
1310 """Verifies that method overloads add new arguments at the end."""
1311 if clazz.fullname.startswith("android.opengl"): return
1312
1313 overloads = collections.defaultdict(list)
1314 for m in clazz.methods:
1315 if "deprecated" in m.split: continue
1316 overloads[m.name].append(m)
1317
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001318 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001319 if len(methods) <= 1: continue
1320
1321 # Look for arguments common across all overloads
1322 def cluster(args):
1323 count = collections.defaultdict(int)
1324 res = set()
1325 for i in range(len(args)):
1326 a = args[i]
1327 res.add("%s#%d" % (a, count[a]))
1328 count[a] += 1
1329 return res
1330
1331 common_args = cluster(methods[0].args)
1332 for m in methods:
1333 common_args = common_args & cluster(m.args)
1334
1335 if len(common_args) == 0: continue
1336
1337 # Require that all common arguments are present at start of signature
1338 locked_sig = None
1339 for m in methods:
1340 sig = m.args[0:len(common_args)]
1341 if not common_args.issubset(cluster(sig)):
1342 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1343 elif not locked_sig:
1344 locked_sig = sig
1345 elif locked_sig != sig:
1346 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1347
1348
1349def verify_callback_handlers(clazz):
1350 """Verifies that methods adding listener/callback have overload
1351 for specifying delivery thread."""
1352
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001353 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001354 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001355 "animation",
1356 "view",
1357 "graphics",
1358 "transition",
1359 "widget",
1360 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001361 ]
1362 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001363 if s in clazz.pkg.name_path: return
1364 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001365
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001366 # Ignore UI classes which assume main thread
1367 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1368 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1369 if s in clazz.fullname: return
1370 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1371 for s in ["Loader"]:
1372 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001373
1374 found = {}
1375 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001376 examine = clazz.ctors + clazz.methods
1377 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001378 if m.name.startswith("unregister"): continue
1379 if m.name.startswith("remove"): continue
1380 if re.match("on[A-Z]+", m.name): continue
1381
1382 by_name[m.name].append(m)
1383
1384 for a in m.args:
1385 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1386 found[m.name] = m
1387
1388 for f in found.values():
1389 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001390 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001391 for m in by_name[f.name]:
1392 if "android.os.Handler" in m.args:
1393 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001394 if "java.util.concurrent.Executor" in m.args:
1395 takes_exec = True
1396 if not takes_exec:
1397 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001398
1399
1400def verify_context_first(clazz):
1401 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001402 examine = clazz.ctors + clazz.methods
1403 for m in examine:
1404 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001405 if "android.content.Context" in m.args[1:]:
1406 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001407 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1408 if "android.content.ContentResolver" in m.args[1:]:
1409 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001410
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001411
1412def verify_listener_last(clazz):
1413 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1414 examine = clazz.ctors + clazz.methods
1415 for m in examine:
1416 if "Listener" in m.name or "Callback" in m.name: continue
1417 found = False
1418 for a in m.args:
1419 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1420 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001421 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001422 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1423
1424
1425def verify_resource_names(clazz):
1426 """Verifies that resource names have consistent case."""
1427 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1428
1429 # Resources defined by files are foo_bar_baz
1430 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1431 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001432 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1433 if f.name.startswith("config_"):
1434 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1435
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001436 if re.match("[a-z1-9_]+$", f.name): continue
1437 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1438
1439 # Resources defined inside files are fooBarBaz
1440 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1441 for f in clazz.fields:
1442 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1443 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1444 if re.match("state_[a-z_]*$", f.name): continue
1445
1446 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1447 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1448
1449 # Styles are FooBar_Baz
1450 if clazz.name in ["style"]:
1451 for f in clazz.fields:
1452 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1453 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001454
1455
Jeff Sharkey331279b2016-02-29 16:02:02 -07001456def verify_files(clazz):
1457 """Verifies that methods accepting File also accept streams."""
1458
1459 has_file = set()
1460 has_stream = set()
1461
1462 test = []
1463 test.extend(clazz.ctors)
1464 test.extend(clazz.methods)
1465
1466 for m in test:
1467 if "java.io.File" in m.args:
1468 has_file.add(m)
1469 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:
1470 has_stream.add(m.name)
1471
1472 for m in has_file:
1473 if m.name not in has_stream:
1474 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1475
1476
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001477def verify_manager_list(clazz):
1478 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1479
1480 if not clazz.name.endswith("Manager"): return
1481
1482 for m in clazz.methods:
1483 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1484 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1485
1486
Jeff Sharkey26c80902016-12-21 13:41:17 -07001487def verify_abstract_inner(clazz):
1488 """Verifies that abstract inner classes are static."""
1489
1490 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
Adrian Roosb787c182019-01-03 18:54:33 +01001491 if "abstract" in clazz.split and "static" not in clazz.split:
Jeff Sharkey26c80902016-12-21 13:41:17 -07001492 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1493
1494
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001495def verify_runtime_exceptions(clazz):
1496 """Verifies that runtime exceptions aren't listed in throws."""
1497
1498 banned = [
1499 "java.lang.NullPointerException",
1500 "java.lang.ClassCastException",
1501 "java.lang.IndexOutOfBoundsException",
1502 "java.lang.reflect.UndeclaredThrowableException",
1503 "java.lang.reflect.MalformedParametersException",
1504 "java.lang.reflect.MalformedParameterizedTypeException",
1505 "java.lang.invoke.WrongMethodTypeException",
1506 "java.lang.EnumConstantNotPresentException",
1507 "java.lang.IllegalMonitorStateException",
1508 "java.lang.SecurityException",
1509 "java.lang.UnsupportedOperationException",
1510 "java.lang.annotation.AnnotationTypeMismatchException",
1511 "java.lang.annotation.IncompleteAnnotationException",
1512 "java.lang.TypeNotPresentException",
1513 "java.lang.IllegalStateException",
1514 "java.lang.ArithmeticException",
1515 "java.lang.IllegalArgumentException",
1516 "java.lang.ArrayStoreException",
1517 "java.lang.NegativeArraySizeException",
1518 "java.util.MissingResourceException",
1519 "java.util.EmptyStackException",
1520 "java.util.concurrent.CompletionException",
1521 "java.util.concurrent.RejectedExecutionException",
1522 "java.util.IllformedLocaleException",
1523 "java.util.ConcurrentModificationException",
1524 "java.util.NoSuchElementException",
1525 "java.io.UncheckedIOException",
1526 "java.time.DateTimeException",
1527 "java.security.ProviderException",
1528 "java.nio.BufferUnderflowException",
1529 "java.nio.BufferOverflowException",
1530 ]
1531
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001532 examine = clazz.ctors + clazz.methods
1533 for m in examine:
1534 for t in m.throws:
1535 if t in banned:
1536 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001537
1538
1539def verify_error(clazz):
1540 """Verifies that we always use Exception instead of Error."""
1541 if not clazz.extends: return
1542 if clazz.extends.endswith("Error"):
1543 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1544 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1545 error(clazz, None, None, "Exceptions must be named FooException")
1546
1547
1548def verify_units(clazz):
1549 """Verifies that we use consistent naming for units."""
1550
1551 # If we find K, recommend replacing with V
1552 bad = {
1553 "Ns": "Nanos",
1554 "Ms": "Millis or Micros",
1555 "Sec": "Seconds", "Secs": "Seconds",
1556 "Hr": "Hours", "Hrs": "Hours",
1557 "Mo": "Months", "Mos": "Months",
1558 "Yr": "Years", "Yrs": "Years",
1559 "Byte": "Bytes", "Space": "Bytes",
1560 }
1561
1562 for m in clazz.methods:
1563 if m.typ not in ["short","int","long"]: continue
1564 for k, v in bad.iteritems():
1565 if m.name.endswith(k):
1566 error(clazz, m, None, "Expected method name units to be " + v)
1567 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1568 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1569 if m.name.endswith("Seconds"):
1570 error(clazz, m, None, "Returned time values must be in milliseconds")
1571
1572 for m in clazz.methods:
1573 typ = m.typ
1574 if typ == "void":
1575 if len(m.args) != 1: continue
1576 typ = m.args[0]
1577
1578 if m.name.endswith("Fraction") and typ != "float":
1579 error(clazz, m, None, "Fractions must use floats")
1580 if m.name.endswith("Percentage") and typ != "int":
1581 error(clazz, m, None, "Percentage must use ints")
1582
1583
1584def verify_closable(clazz):
1585 """Verifies that classes are AutoClosable."""
Adrian Roosb787c182019-01-03 18:54:33 +01001586 if clazz.implements == "java.lang.AutoCloseable": return
1587 if clazz.implements == "java.io.Closeable": return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001588
1589 for m in clazz.methods:
1590 if len(m.args) > 0: continue
1591 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1592 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1593 return
1594
1595
Jake Wharton9e6738f2017-08-23 11:59:55 -04001596def verify_member_name_not_kotlin_keyword(clazz):
1597 """Prevent method names which are keywords in Kotlin."""
1598
1599 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1600 # This list does not include Java keywords as those are already impossible to use.
1601 keywords = [
1602 'as',
1603 'fun',
1604 'in',
1605 'is',
1606 'object',
1607 'typealias',
1608 'val',
1609 'var',
1610 'when',
1611 ]
1612
1613 for m in clazz.methods:
1614 if m.name in keywords:
1615 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1616 for f in clazz.fields:
1617 if f.name in keywords:
1618 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1619
1620
1621def verify_method_name_not_kotlin_operator(clazz):
1622 """Warn about method names which become operators in Kotlin."""
1623
1624 binary = set()
1625
1626 def unique_binary_op(m, op):
1627 if op in binary:
1628 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1629 binary.add(op)
1630
1631 for m in clazz.methods:
1632 if 'static' in m.split:
1633 continue
1634
1635 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1636 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1637 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1638
1639 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1640 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1641 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1642 # practical way of checking that relationship here.
1643 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1644
1645 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1646 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1647 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1648 unique_binary_op(m, m.name)
1649
1650 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1651 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1652 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1653
1654 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1655 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1656 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1657
1658 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1659 if m.name == 'invoke':
1660 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1661
1662 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1663 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1664 and len(m.args) == 1 \
1665 and m.typ == 'void':
1666 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1667 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1668
1669
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001670def verify_collections_over_arrays(clazz):
1671 """Warn that [] should be Collections."""
1672
Adrian Roosb787c182019-01-03 18:54:33 +01001673 if "@interface" in clazz.split:
1674 return
1675
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001676 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1677 for m in clazz.methods:
1678 if m.typ.endswith("[]") and m.typ not in safe:
1679 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1680 for arg in m.args:
1681 if arg.endswith("[]") and arg not in safe:
1682 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1683
1684
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001685def verify_user_handle(clazz):
1686 """Methods taking UserHandle should be ForUser or AsUser."""
1687 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1688 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1689 if clazz.fullname == "android.content.pm.LauncherApps": return
1690 if clazz.fullname == "android.os.UserHandle": return
1691 if clazz.fullname == "android.os.UserManager": return
1692
1693 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001694 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001695
1696 has_arg = "android.os.UserHandle" in m.args
1697 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1698
1699 if clazz.fullname.endswith("Manager") and has_arg:
1700 warn(clazz, m, None, "When a method overload is needed to target a specific "
1701 "UserHandle, callers should be directed to use "
1702 "Context.createPackageContextAsUser() and re-obtain the relevant "
1703 "Manager, and no new API should be added")
1704 elif has_arg and not has_name:
1705 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1706 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001707
1708
1709def verify_params(clazz):
1710 """Parameter classes should be 'Params'."""
1711 if clazz.name.endswith("Params"): return
1712 if clazz.fullname == "android.app.ActivityOptions": return
1713 if clazz.fullname == "android.app.BroadcastOptions": return
1714 if clazz.fullname == "android.os.Bundle": return
1715 if clazz.fullname == "android.os.BaseBundle": return
1716 if clazz.fullname == "android.os.PersistableBundle": return
1717
1718 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1719 for b in bad:
1720 if clazz.name.endswith(b):
1721 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1722
1723
1724def verify_services(clazz):
1725 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1726 if clazz.fullname != "android.content.Context": return
1727
1728 for f in clazz.fields:
1729 if f.typ != "java.lang.String": continue
1730 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1731 if found:
1732 expected = found.group(1).lower()
1733 if f.value != expected:
1734 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1735
1736
1737def verify_tense(clazz):
1738 """Verify tenses of method names."""
1739 if clazz.fullname.startswith("android.opengl"): return
1740
1741 for m in clazz.methods:
1742 if m.name.endswith("Enable"):
1743 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1744
1745
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001746def verify_icu(clazz):
1747 """Verifies that richer ICU replacements are used."""
1748 better = {
1749 "java.util.TimeZone": "android.icu.util.TimeZone",
1750 "java.util.Calendar": "android.icu.util.Calendar",
1751 "java.util.Locale": "android.icu.util.ULocale",
1752 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1753 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1754 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1755 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1756 "java.lang.Character": "android.icu.lang.UCharacter",
1757 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1758 "java.text.Collator": "android.icu.text.Collator",
1759 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1760 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1761 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1762 "java.text.DateFormat": "android.icu.text.DateFormat",
1763 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1764 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1765 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1766 }
1767
1768 for m in clazz.ctors + clazz.methods:
1769 types = []
1770 types.extend(m.typ)
1771 types.extend(m.args)
1772 for arg in types:
1773 if arg in better:
1774 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1775
1776
1777def verify_clone(clazz):
1778 """Verify that clone() isn't implemented; see EJ page 61."""
1779 for m in clazz.methods:
1780 if m.name == "clone":
1781 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1782
1783
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001784def verify_pfd(clazz):
1785 """Verify that android APIs use PFD over FD."""
1786 examine = clazz.ctors + clazz.methods
1787 for m in examine:
1788 if m.typ == "java.io.FileDescriptor":
1789 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1790 if m.typ == "int":
1791 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1792 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1793 for arg in m.args:
1794 if arg == "java.io.FileDescriptor":
1795 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1796
1797 for f in clazz.fields:
1798 if f.typ == "java.io.FileDescriptor":
1799 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1800
1801
1802def verify_numbers(clazz):
1803 """Discourage small numbers types like short and byte."""
1804
1805 discouraged = ["short","byte"]
1806
1807 for c in clazz.ctors:
1808 for arg in c.args:
1809 if arg in discouraged:
1810 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1811
1812 for f in clazz.fields:
1813 if f.typ in discouraged:
1814 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1815
1816 for m in clazz.methods:
1817 if m.typ in discouraged:
1818 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1819 for arg in m.args:
1820 if arg in discouraged:
1821 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1822
1823
1824def verify_singleton(clazz):
1825 """Catch singleton objects with constructors."""
1826
1827 singleton = False
1828 for m in clazz.methods:
1829 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1830 singleton = True
1831
1832 if singleton:
1833 for c in clazz.ctors:
1834 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1835
1836
1837
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001838def is_interesting(clazz):
1839 """Test if given class is interesting from an Android PoV."""
1840
1841 if clazz.pkg.name.startswith("java"): return False
1842 if clazz.pkg.name.startswith("junit"): return False
1843 if clazz.pkg.name.startswith("org.apache"): return False
1844 if clazz.pkg.name.startswith("org.xml"): return False
1845 if clazz.pkg.name.startswith("org.json"): return False
1846 if clazz.pkg.name.startswith("org.w3c"): return False
1847 if clazz.pkg.name.startswith("android.icu."): return False
1848 return True
1849
1850
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001851def examine_clazz(clazz):
1852 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001853
1854 notice(clazz)
1855
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001856 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001857
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001858 verify_constants(clazz)
1859 verify_enums(clazz)
1860 verify_class_names(clazz)
1861 verify_method_names(clazz)
1862 verify_callbacks(clazz)
1863 verify_listeners(clazz)
1864 verify_actions(clazz)
1865 verify_extras(clazz)
1866 verify_equals(clazz)
1867 verify_parcelable(clazz)
1868 verify_protected(clazz)
1869 verify_fields(clazz)
1870 verify_register(clazz)
1871 verify_sync(clazz)
1872 verify_intent_builder(clazz)
1873 verify_helper_classes(clazz)
1874 verify_builder(clazz)
1875 verify_aidl(clazz)
1876 verify_internal(clazz)
1877 verify_layering(clazz)
1878 verify_boolean(clazz)
1879 verify_collections(clazz)
1880 verify_flags(clazz)
1881 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001882 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001883 verify_bitset(clazz)
1884 verify_manager(clazz)
1885 verify_boxed(clazz)
1886 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001887 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001888 verify_callback_handlers(clazz)
1889 verify_context_first(clazz)
1890 verify_listener_last(clazz)
1891 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001892 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001893 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001894 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001895 verify_runtime_exceptions(clazz)
1896 verify_error(clazz)
1897 verify_units(clazz)
1898 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001899 verify_member_name_not_kotlin_keyword(clazz)
1900 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001901 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001902 verify_user_handle(clazz)
1903 verify_params(clazz)
1904 verify_services(clazz)
1905 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001906 verify_icu(clazz)
1907 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001908 verify_pfd(clazz)
1909 verify_numbers(clazz)
1910 verify_singleton(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001911
1912
Adrian Roos038a0292018-12-19 17:11:21 +01001913def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001914 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001915 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001916 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001917 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01001918 _parse_stream(stream, examine_clazz, base_f=base_stream,
1919 in_classes_with_base=in_classes_with_base,
1920 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001921 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001922
1923
1924def examine_api(api):
1925 """Find all style issues in the given parsed API."""
1926 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001927 failures = {}
1928 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001929 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001930 return failures
1931
1932
Jeff Sharkey037458a2014-09-04 15:46:20 -07001933def verify_compat(cur, prev):
1934 """Find any incompatible API changes between two levels."""
1935 global failures
1936
1937 def class_exists(api, test):
1938 return test.fullname in api
1939
1940 def ctor_exists(api, clazz, test):
1941 for m in clazz.ctors:
1942 if m.ident == test.ident: return True
1943 return False
1944
1945 def all_methods(api, clazz):
1946 methods = list(clazz.methods)
1947 if clazz.extends is not None:
1948 methods.extend(all_methods(api, api[clazz.extends]))
1949 return methods
1950
1951 def method_exists(api, clazz, test):
1952 methods = all_methods(api, clazz)
1953 for m in methods:
1954 if m.ident == test.ident: return True
1955 return False
1956
1957 def field_exists(api, clazz, test):
1958 for f in clazz.fields:
1959 if f.ident == test.ident: return True
1960 return False
1961
1962 failures = {}
1963 for key in sorted(prev.keys()):
1964 prev_clazz = prev[key]
1965
1966 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001967 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001968 continue
1969
1970 cur_clazz = cur[key]
1971
1972 for test in prev_clazz.ctors:
1973 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001974 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001975
1976 methods = all_methods(prev, prev_clazz)
1977 for test in methods:
1978 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001979 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001980
1981 for test in prev_clazz.fields:
1982 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001983 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001984
1985 return failures
1986
1987
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001988def show_deprecations_at_birth(cur, prev):
1989 """Show API deprecations at birth."""
1990 global failures
1991
1992 # Remove all existing things so we're left with new
1993 for prev_clazz in prev.values():
1994 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001995 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001996
1997 sigs = { i.ident: i for i in prev_clazz.ctors }
1998 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
1999 sigs = { i.ident: i for i in prev_clazz.methods }
2000 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
2001 sigs = { i.ident: i for i in prev_clazz.fields }
2002 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
2003
2004 # Forget about class entirely when nothing new
2005 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
2006 del cur[prev_clazz.fullname]
2007
2008 for clazz in cur.values():
Adrian Roosb787c182019-01-03 18:54:33 +01002009 if "deprecated" in clazz.split and not clazz.fullname in prev:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002010 error(clazz, None, None, "Found API deprecation at birth")
2011
2012 for i in clazz.ctors + clazz.methods + clazz.fields:
Adrian Roosb787c182019-01-03 18:54:33 +01002013 if "deprecated" in i.split:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002014 error(clazz, i, None, "Found API deprecation at birth")
2015
2016 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
2017 format(reset=True)))
2018 for f in sorted(failures):
2019 print failures[f]
2020 print
2021
2022
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002023def show_stats(cur, prev):
2024 """Show API stats."""
2025
2026 stats = collections.defaultdict(int)
2027 for cur_clazz in cur.values():
2028 if not is_interesting(cur_clazz): continue
2029
2030 if cur_clazz.fullname not in prev:
2031 stats['new_classes'] += 1
2032 stats['new_ctors'] += len(cur_clazz.ctors)
2033 stats['new_methods'] += len(cur_clazz.methods)
2034 stats['new_fields'] += len(cur_clazz.fields)
2035 else:
2036 prev_clazz = prev[cur_clazz.fullname]
2037
2038 sigs = { i.ident: i for i in prev_clazz.ctors }
2039 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
2040 sigs = { i.ident: i for i in prev_clazz.methods }
2041 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
2042 sigs = { i.ident: i for i in prev_clazz.fields }
2043 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
2044
2045 if ctors + methods + fields > 0:
2046 stats['extend_classes'] += 1
2047 stats['extend_ctors'] += ctors
2048 stats['extend_methods'] += methods
2049 stats['extend_fields'] += fields
2050
2051 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
2052 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
2053
2054
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002055if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002056 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
2057 patterns. It ignores lint messages from a previous API level, if provided.")
2058 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
2059 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
2060 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01002061 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
2062 help="The base current.txt to use when examining system-current.txt or"
2063 " test-current.txt")
2064 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
2065 help="The base previous.txt to use when examining system-previous.txt or"
2066 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002067 parser.add_argument("--no-color", action='store_const', const=True,
2068 help="Disable terminal colors")
2069 parser.add_argument("--allow-google", action='store_const', const=True,
2070 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002071 parser.add_argument("--show-noticed", action='store_const', const=True,
2072 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002073 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
2074 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002075 parser.add_argument("--show-stats", action='store_const', const=True,
2076 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002077 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002078
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002079 if args['no_color']:
2080 USE_COLOR = False
2081
2082 if args['allow_google']:
2083 ALLOW_GOOGLE = True
2084
2085 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002086 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002087 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002088 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002089
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002090 if args['show_deprecations_at_birth']:
2091 with current_file as f:
2092 cur = _parse_stream(f)
2093 with previous_file as f:
2094 prev = _parse_stream(f)
2095 show_deprecations_at_birth(cur, prev)
2096 sys.exit()
2097
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002098 if args['show_stats']:
2099 with current_file as f:
2100 cur = _parse_stream(f)
2101 with previous_file as f:
2102 prev = _parse_stream(f)
2103 show_stats(cur, prev)
2104 sys.exit()
2105
Adrian Roos038a0292018-12-19 17:11:21 +01002106 classes_with_base = []
2107
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002108 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002109 if base_current_file:
2110 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002111 cur_fail, cur_noticed = examine_stream(f, base_f,
2112 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002113 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002114 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
2115
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002116 if not previous_file is None:
2117 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002118 if base_previous_file:
2119 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002120 prev_fail, prev_noticed = examine_stream(f, base_f,
2121 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002122 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002123 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002124
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002125 # ignore errors from previous API level
2126 for p in prev_fail:
2127 if p in cur_fail:
2128 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002129
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002130 # ignore classes unchanged from previous API level
2131 for k, v in prev_noticed.iteritems():
2132 if k in cur_noticed and v == cur_noticed[k]:
2133 del cur_noticed[k]
2134
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002135 """
2136 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002137 # look for compatibility issues
2138 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002139
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002140 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2141 for f in sorted(compat_fail):
2142 print compat_fail[f]
2143 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002144 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002145
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002146 if args['show_noticed'] and len(cur_noticed) != 0:
2147 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2148 for f in sorted(cur_noticed.keys()):
2149 print f
2150 print
2151
Jason Monk53b2a732017-11-10 15:43:17 -05002152 if len(cur_fail) != 0:
2153 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2154 for f in sorted(cur_fail):
2155 print cur_fail[f]
2156 print
2157 sys.exit(77)