blob: 4c02d94542d0dd478d6df2306a232b5e313d132c [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():
Adrian Roosb1faa0b2019-02-26 11:54:40 +0100211 NAME = re.compile("package(?: .*)? ([A-Za-z.]+)")
212
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700213 def __init__(self, line, raw, blame):
214 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700215 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700216 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700217
Adrian Roosb1faa0b2019-02-26 11:54:40 +0100218 self.name = Package.NAME.match(raw).group(1)
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800219 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700220
221 def __repr__(self):
222 return self.raw
223
Adrian Roose5eeae72019-01-04 20:10:06 +0100224class V2Tokenizer(object):
225 __slots__ = ["raw"]
226
Adrian Rooscf82e042019-01-29 15:01:28 +0100227 SIGNATURE_PREFIX = "// Signature format: "
Adrian Roos5cdfb692019-01-05 22:04:55 +0100228 DELIMITER = re.compile(r'\s+|[()@<>;,={}/"!?]|\[\]|\.\.\.')
Adrian Roosb787c182019-01-03 18:54:33 +0100229 STRING_SPECIAL = re.compile(r'["\\]')
230
231 def __init__(self, raw):
232 self.raw = raw
Adrian Roosb787c182019-01-03 18:54:33 +0100233
Adrian Roose5eeae72019-01-04 20:10:06 +0100234 def tokenize(self):
235 tokens = []
236 current = 0
237 raw = self.raw
238 length = len(raw)
Adrian Roosb787c182019-01-03 18:54:33 +0100239
Adrian Roose5eeae72019-01-04 20:10:06 +0100240 while current < length:
241 while current < length:
242 start = current
243 match = V2Tokenizer.DELIMITER.search(raw, start)
244 if match is not None:
245 match_start = match.start()
246 if match_start == current:
247 end = match.end()
248 else:
249 end = match_start
250 else:
251 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100252
Adrian Roose5eeae72019-01-04 20:10:06 +0100253 token = raw[start:end]
254 current = end
Adrian Roosb787c182019-01-03 18:54:33 +0100255
Adrian Roose5eeae72019-01-04 20:10:06 +0100256 if token == "" or token[0] == " ":
257 continue
258 else:
259 break
Adrian Roosb787c182019-01-03 18:54:33 +0100260
Adrian Roose5eeae72019-01-04 20:10:06 +0100261 if token == "@":
262 if raw[start:start+11] == "@interface ":
263 current = start + 11
264 tokens.append("@interface")
265 continue
266 elif token == '/':
267 if raw[start:start+2] == "//":
268 current = length
269 continue
270 elif token == '"':
271 current, string_token = self.tokenize_string(raw, length, current)
272 tokens.append(token + string_token)
273 continue
Adrian Roosb787c182019-01-03 18:54:33 +0100274
Adrian Roose5eeae72019-01-04 20:10:06 +0100275 tokens.append(token)
Adrian Roosb787c182019-01-03 18:54:33 +0100276
Adrian Roose5eeae72019-01-04 20:10:06 +0100277 return tokens
Adrian Roosb787c182019-01-03 18:54:33 +0100278
Adrian Roose5eeae72019-01-04 20:10:06 +0100279 def tokenize_string(self, raw, length, current):
280 start = current
281 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100282 while start < end:
Adrian Roose5eeae72019-01-04 20:10:06 +0100283 match = V2Tokenizer.STRING_SPECIAL.search(raw, start)
Adrian Roosb787c182019-01-03 18:54:33 +0100284 if match:
285 if match.group() == '"':
286 end = match.end()
287 break
288 elif match.group() == '\\':
289 # ignore whatever is after the slash
290 start += 2
291 else:
292 raise ValueError("Unexpected match: `%s`" % (match.group()))
293 else:
Adrian Roose5eeae72019-01-04 20:10:06 +0100294 raise ValueError("Unexpected EOF tokenizing string: `%s`" % (raw[current - 1:],))
Adrian Roosb787c182019-01-03 18:54:33 +0100295
Adrian Roose5eeae72019-01-04 20:10:06 +0100296 token = raw[current:end]
297 return end, token
Adrian Roosb787c182019-01-03 18:54:33 +0100298
Adrian Roose5eeae72019-01-04 20:10:06 +0100299class V2LineParser(object):
300 __slots__ = ["tokenized", "current", "len"]
301
Adrian Roos258c5722019-01-21 15:43:15 +0100302 FIELD_KINDS = ("field", "property", "enum_constant")
Adrian Roosd1e38922019-01-14 15:44:15 +0100303 MODIFIERS = set("public protected internal private abstract default static final transient volatile synchronized native operator sealed strictfp infix inline suspend vararg".split())
Adrian Roosb787c182019-01-03 18:54:33 +0100304 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())
305
306 def __init__(self, raw):
Adrian Roose5eeae72019-01-04 20:10:06 +0100307 self.tokenized = V2Tokenizer(raw).tokenize()
Adrian Roosb787c182019-01-03 18:54:33 +0100308 self.current = 0
Adrian Roose5eeae72019-01-04 20:10:06 +0100309 self.len = len(self.tokenized)
Adrian Roosb787c182019-01-03 18:54:33 +0100310
311 def parse_into_method(self, method):
312 method.split = []
313 kind = self.parse_one_of("ctor", "method")
314 method.split.append(kind)
315 annotations = self.parse_annotations()
316 method.split.extend(self.parse_modifiers())
317 self.parse_matching_paren("<", ">")
318 if "@Deprecated" in annotations:
319 method.split.append("deprecated")
320 if kind == "ctor":
321 method.typ = "ctor"
322 else:
323 method.typ = self.parse_type()
324 method.split.append(method.typ)
325 method.name = self.parse_name()
326 method.split.append(method.name)
327 self.parse_token("(")
328 method.args = self.parse_args()
329 self.parse_token(")")
330 method.throws = self.parse_throws()
331 if "@interface" in method.clazz.split:
332 self.parse_annotation_default()
333 self.parse_token(";")
334 self.parse_eof()
335
336 def parse_into_class(self, clazz):
337 clazz.split = []
338 annotations = self.parse_annotations()
339 if "@Deprecated" in annotations:
340 clazz.split.append("deprecated")
341 clazz.split.extend(self.parse_modifiers())
342 kind = self.parse_one_of("class", "interface", "@interface", "enum")
343 if kind == "enum":
344 # enums are implicitly final
345 clazz.split.append("final")
346 clazz.split.append(kind)
347 clazz.fullname = self.parse_name()
348 self.parse_matching_paren("<", ">")
349 extends = self.parse_extends()
350 clazz.extends = extends[0] if extends else None
351 implements = self.parse_implements()
352 clazz.implements = implements[0] if implements else None
353 # The checks assume that interfaces are always found in implements, which isn't true for
354 # subinterfaces.
355 if not implements and "interface" in clazz.split:
356 clazz.implements = clazz.extends
357 self.parse_token("{")
358 self.parse_eof()
359
360 def parse_into_field(self, field):
Adrian Roos258c5722019-01-21 15:43:15 +0100361 kind = self.parse_one_of(*V2LineParser.FIELD_KINDS)
Adrian Roosb787c182019-01-03 18:54:33 +0100362 field.split = [kind]
363 annotations = self.parse_annotations()
364 if "@Deprecated" in annotations:
365 field.split.append("deprecated")
366 field.split.extend(self.parse_modifiers())
367 field.typ = self.parse_type()
368 field.split.append(field.typ)
369 field.name = self.parse_name()
370 field.split.append(field.name)
371 if self.parse_if("="):
372 field.value = self.parse_value_stripped()
373 else:
374 field.value = None
375
376 self.parse_token(";")
377 self.parse_eof()
378
379 def lookahead(self):
380 return self.tokenized[self.current]
381
382 def parse_one_of(self, *options):
383 found = self.lookahead()
384 if found not in options:
385 raise ValueError("Parsing failed, expected one of `%s` but found `%s` in %s" % (options, found, repr(self.tokenized)))
386 return self.parse_token()
387
388 def parse_token(self, tok = None):
389 found = self.lookahead()
390 if tok is not None and found != tok:
391 raise ValueError("Parsing failed, expected `%s` but found `%s` in %s" % (tok, found, repr(self.tokenized)))
392 self.current += 1
393 return found
394
395 def eof(self):
Adrian Roose5eeae72019-01-04 20:10:06 +0100396 return self.current == self.len
Adrian Roosb787c182019-01-03 18:54:33 +0100397
398 def parse_eof(self):
399 if not self.eof():
400 raise ValueError("Parsing failed, expected EOF, but %s has not been parsed in %s" % (self.tokenized[self.current:], self.tokenized))
401
402 def parse_if(self, tok):
403 if not self.eof() and self.lookahead() == tok:
404 self.parse_token()
405 return True
406 return False
407
408 def parse_annotations(self):
409 ret = []
410 while self.lookahead() == "@":
411 ret.append(self.parse_annotation())
412 return ret
413
414 def parse_annotation(self):
415 ret = self.parse_token("@") + self.parse_token()
416 self.parse_matching_paren("(", ")")
417 return ret
418
419 def parse_matching_paren(self, open, close):
420 start = self.current
421 if not self.parse_if(open):
422 return
423 length = len(self.tokenized)
424 count = 1
425 while count > 0:
426 if self.current == length:
427 raise ValueError("Unexpected EOF looking for closing paren: `%s`" % (self.tokenized[start:],))
428 t = self.parse_token()
429 if t == open:
430 count += 1
431 elif t == close:
432 count -= 1
433 return self.tokenized[start:self.current]
434
435 def parse_modifiers(self):
436 ret = []
437 while self.lookahead() in V2LineParser.MODIFIERS:
438 ret.append(self.parse_token())
439 return ret
440
Adrian Roos5cdfb692019-01-05 22:04:55 +0100441 def parse_kotlin_nullability(self):
442 t = self.lookahead()
443 if t == "?" or t == "!":
444 return self.parse_token()
445 return None
446
Adrian Roosb787c182019-01-03 18:54:33 +0100447 def parse_type(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100448 self.parse_annotations()
Adrian Roosb787c182019-01-03 18:54:33 +0100449 type = self.parse_token()
Adrian Roosd1e38922019-01-14 15:44:15 +0100450 if type[-1] == '.':
451 self.parse_annotations()
452 type += self.parse_token()
Adrian Roosb787c182019-01-03 18:54:33 +0100453 if type in V2LineParser.JAVA_LANG_TYPES:
454 type = "java.lang." + type
455 self.parse_matching_paren("<", ">")
Adrian Roos5cdfb692019-01-05 22:04:55 +0100456 while True:
457 t = self.lookahead()
Adrian Roosd1e38922019-01-14 15:44:15 +0100458 if t == "@":
459 self.parse_annotation()
460 elif t == "[]":
Adrian Roos5cdfb692019-01-05 22:04:55 +0100461 type += self.parse_token()
462 elif self.parse_kotlin_nullability() is not None:
463 pass # discard nullability for now
464 else:
465 break
Adrian Roosb787c182019-01-03 18:54:33 +0100466 return type
467
468 def parse_arg_type(self):
469 type = self.parse_type()
470 if self.parse_if("..."):
471 type += "..."
Adrian Roos5cdfb692019-01-05 22:04:55 +0100472 self.parse_kotlin_nullability() # discard nullability for now
Adrian Roosb787c182019-01-03 18:54:33 +0100473 return type
474
475 def parse_name(self):
476 return self.parse_token()
477
478 def parse_args(self):
479 args = []
480 if self.lookahead() == ")":
481 return args
482
483 while True:
484 args.append(self.parse_arg())
485 if self.lookahead() == ")":
486 return args
487 self.parse_token(",")
488
489 def parse_arg(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100490 self.parse_if("vararg") # kotlin vararg
Adrian Roosb787c182019-01-03 18:54:33 +0100491 self.parse_annotations()
Adrian Roos5cdfb692019-01-05 22:04:55 +0100492 type = self.parse_arg_type()
493 l = self.lookahead()
494 if l != "," and l != ")":
Adrian Roosd1e38922019-01-14 15:44:15 +0100495 if self.lookahead() != '=':
496 self.parse_token() # kotlin argument name
Adrian Roos5cdfb692019-01-05 22:04:55 +0100497 if self.parse_if('='): # kotlin default value
Adrian Roosd1e38922019-01-14 15:44:15 +0100498 self.parse_expression()
Adrian Roos5cdfb692019-01-05 22:04:55 +0100499 return type
Adrian Roosb787c182019-01-03 18:54:33 +0100500
Adrian Roosd1e38922019-01-14 15:44:15 +0100501 def parse_expression(self):
502 while not self.lookahead() in [')', ',', ';']:
503 (self.parse_matching_paren('(', ')') or
504 self.parse_matching_paren('{', '}') or
505 self.parse_token())
506
Adrian Roosb787c182019-01-03 18:54:33 +0100507 def parse_throws(self):
508 ret = []
509 if self.parse_if("throws"):
510 ret.append(self.parse_type())
511 while self.parse_if(","):
512 ret.append(self.parse_type())
513 return ret
514
515 def parse_extends(self):
516 if self.parse_if("extends"):
517 return self.parse_space_delimited_type_list()
518 return []
519
520 def parse_implements(self):
521 if self.parse_if("implements"):
522 return self.parse_space_delimited_type_list()
523 return []
524
525 def parse_space_delimited_type_list(self, terminals = ["implements", "{"]):
526 types = []
527 while True:
528 types.append(self.parse_type())
529 if self.lookahead() in terminals:
530 return types
531
532 def parse_annotation_default(self):
533 if self.parse_if("default"):
Adrian Roosd1e38922019-01-14 15:44:15 +0100534 self.parse_expression()
Adrian Roosb787c182019-01-03 18:54:33 +0100535
536 def parse_value(self):
537 if self.lookahead() == "{":
538 return " ".join(self.parse_matching_paren("{", "}"))
539 elif self.lookahead() == "(":
540 return " ".join(self.parse_matching_paren("(", ")"))
541 else:
542 return self.parse_token()
543
544 def parse_value_stripped(self):
545 value = self.parse_value()
546 if value[0] == '"':
547 return value[1:-1]
548 return value
549
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700550
Adrian Roos038a0292018-12-19 17:11:21 +0100551def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
552 in_classes_with_base=[]):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700553 api = {}
Adrian Roos038a0292018-12-19 17:11:21 +0100554 in_classes_with_base = _retry_iterator(in_classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +0100555
556 if base_f:
Adrian Roos038a0292018-12-19 17:11:21 +0100557 base_classes = _retry_iterator(_parse_stream_to_generator(base_f))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100558 else:
559 base_classes = []
560
Adrian Roos038a0292018-12-19 17:11:21 +0100561 def handle_class(clazz):
Adrian Roos6eb57b02018-12-13 22:08:29 +0100562 if clazz_cb:
563 clazz_cb(clazz)
564 else: # In callback mode, don't keep track of the full API
565 api[clazz.fullname] = clazz
566
Adrian Roos038a0292018-12-19 17:11:21 +0100567 def handle_missed_classes_with_base(clazz):
568 for c in _yield_until_matching_class(in_classes_with_base, clazz):
569 base_class = _skip_to_matching_class(base_classes, c)
570 if base_class:
571 handle_class(base_class)
572
573 for clazz in _parse_stream_to_generator(f):
574 # Before looking at clazz, let's see if there's some classes that were not present, but
575 # may have an entry in the base stream.
576 handle_missed_classes_with_base(clazz)
577
578 base_class = _skip_to_matching_class(base_classes, clazz)
579 if base_class:
580 clazz.merge_from(base_class)
581 if out_classes_with_base is not None:
582 out_classes_with_base.append(clazz)
583 handle_class(clazz)
584
585 handle_missed_classes_with_base(None)
586
Adrian Roos6eb57b02018-12-13 22:08:29 +0100587 return api
588
589def _parse_stream_to_generator(f):
590 line = 0
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700591 pkg = None
592 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700593 blame = None
Adrian Roosb787c182019-01-03 18:54:33 +0100594 sig_format = 1
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700595
596 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Adrian Roos258c5722019-01-21 15:43:15 +0100597
598 field_prefixes = map(lambda kind: " %s" % (kind,), V2LineParser.FIELD_KINDS)
599 def startsWithFieldPrefix(raw):
600 for prefix in field_prefixes:
601 if raw.startswith(prefix):
602 return True
603 return False
604
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800605 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700606 line += 1
607 raw = raw.rstrip()
608 match = re_blame.match(raw)
609 if match is not None:
610 blame = match.groups()[0:2]
611 raw = match.groups()[2]
612 else:
613 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700614
Adrian Rooscf82e042019-01-29 15:01:28 +0100615 if line == 1 and raw.startswith("// Signature format: "):
616 sig_format_string = raw[len(V2Tokenizer.SIGNATURE_PREFIX):]
617 if sig_format_string in ["2.0", "3.0"]:
618 sig_format = 2
619 else:
620 raise ValueError("Unknown format: %s" % (sig_format_string,))
Adrian Roosb787c182019-01-03 18:54:33 +0100621 elif raw.startswith("package"):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700622 pkg = Package(line, raw, blame)
623 elif raw.startswith(" ") and raw.endswith("{"):
Adrian Roosb787c182019-01-03 18:54:33 +0100624 clazz = Class(pkg, line, raw, blame, sig_format=sig_format)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700625 elif raw.startswith(" ctor"):
Adrian Roosb787c182019-01-03 18:54:33 +0100626 clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700627 elif raw.startswith(" method"):
Adrian Roosb787c182019-01-03 18:54:33 +0100628 clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos258c5722019-01-21 15:43:15 +0100629 elif startsWithFieldPrefix(raw):
Adrian Roosb787c182019-01-03 18:54:33 +0100630 clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100631 elif raw.startswith(" }") and clazz:
Adrian Roos038a0292018-12-19 17:11:21 +0100632 yield clazz
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800633
Adrian Roos5ed42b62018-12-19 17:10:22 +0100634def _retry_iterator(it):
635 """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
636 for e in it:
637 while True:
638 retry = yield e
639 if not retry:
640 break
641 # send() was called, asking us to redeliver clazz on next(). Still need to yield
642 # a dummy value to the send() first though.
643 if (yield "Returning clazz on next()"):
644 raise TypeError("send() must be followed by next(), not send()")
645
Adrian Roos038a0292018-12-19 17:11:21 +0100646def _skip_to_matching_class(classes, needle):
647 """Takes a classes iterator and consumes entries until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700648
Adrian Roos6eb57b02018-12-13 22:08:29 +0100649 This relies on classes being sorted by package and class name."""
650
651 for clazz in classes:
652 if clazz.pkg.name < needle.pkg.name:
653 # We haven't reached the right package yet
654 continue
Adrian Roos5ed42b62018-12-19 17:10:22 +0100655 if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
656 # We're in the right package, but not the right class yet
Adrian Roos6eb57b02018-12-13 22:08:29 +0100657 continue
658 if clazz.fullname == needle.fullname:
659 return clazz
660 # We ran past the right class. Send it back into the generator, then report failure.
661 classes.send(clazz)
662 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700663
Adrian Roos038a0292018-12-19 17:11:21 +0100664def _yield_until_matching_class(classes, needle):
665 """Takes a class iterator and yields entries it until it reaches the class we're looking for.
666
667 This relies on classes being sorted by package and class name."""
668
669 for clazz in classes:
670 if needle is None:
671 yield clazz
672 elif clazz.pkg.name < needle.pkg.name:
673 # We haven't reached the right package yet
674 yield clazz
675 elif clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
676 # We're in the right package, but not the right class yet
677 yield clazz
678 elif clazz.fullname == needle.fullname:
679 # Class found, abort.
680 return
681 else:
682 # We ran past the right class. Send it back into the iterator, then abort.
683 classes.send(clazz)
684 return
685
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700686class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800687 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700688 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700689 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800690 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700691 self.msg = msg
692
693 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800694 self.head = "Error %s" % (rule) if rule else "Error"
695 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 -0700696 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800697 self.head = "Warning %s" % (rule) if rule else "Warning"
698 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 -0700699
700 self.line = clazz.line
701 blame = clazz.blame
702 if detail is not None:
703 dump += "\n in " + repr(detail)
704 self.line = detail.line
705 blame = detail.blame
706 dump += "\n in " + repr(clazz)
707 dump += "\n in " + repr(clazz.pkg)
708 dump += "\n at line " + repr(self.line)
709 if blame is not None:
710 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
711
712 self.dump = dump
713
714 def __repr__(self):
715 return self.dump
716
717
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700718failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700719
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800720def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700721 """Records an API failure to be processed later."""
722 global failures
723
Adrian Roosb787c182019-01-03 18:54:33 +0100724 sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700725 sig = sig.replace(" deprecated ", " ")
726
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800727 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700728
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700729
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800730def warn(clazz, detail, rule, msg):
731 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700732
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800733def error(clazz, detail, rule, msg):
734 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700735
736
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700737noticed = {}
738
739def notice(clazz):
740 global noticed
741
742 noticed[clazz.fullname] = hash(clazz)
743
744
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700745def verify_constants(clazz):
746 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700747 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600748 if clazz.fullname.startswith("android.os.Build"): return
749 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700750
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600751 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700752 for f in clazz.fields:
753 if "static" in f.split and "final" in f.split:
754 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800755 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600756 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700757 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
758 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600759 if f.typ in req and f.value is None:
760 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700761
762
763def verify_enums(clazz):
764 """Enums are bad, mmkay?"""
Adrian Roosb787c182019-01-03 18:54:33 +0100765 if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800766 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700767
768
769def verify_class_names(clazz):
770 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700771 if clazz.fullname.startswith("android.opengl"): return
772 if clazz.fullname.startswith("android.renderscript"): return
773 if re.match("android\.R\.[a-z]+", clazz.fullname): return
774
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700775 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800776 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700777 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800778 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700779 if clazz.name.endswith("Impl"):
780 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700781
782
783def verify_method_names(clazz):
784 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700785 if clazz.fullname.startswith("android.opengl"): return
786 if clazz.fullname.startswith("android.renderscript"): return
787 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700788
789 for m in clazz.methods:
790 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800791 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700792 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800793 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700794
795
796def verify_callbacks(clazz):
797 """Verify Callback classes.
798 All callback classes must be abstract.
799 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700800 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700801
802 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800803 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700804 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800805 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700806
807 if clazz.name.endswith("Callback"):
808 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800809 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700810
811 for m in clazz.methods:
812 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800813 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700814
815
816def verify_listeners(clazz):
817 """Verify Listener classes.
818 All Listener classes must be interface.
819 All methods must follow onFoo() naming style.
820 If only a single method, it must match class name:
821 interface OnFooListener { void onFoo() }"""
822
823 if clazz.name.endswith("Listener"):
Adrian Roosb787c182019-01-03 18:54:33 +0100824 if "abstract" in clazz.split and "class" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800825 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700826
827 for m in clazz.methods:
828 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800829 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700830
831 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
832 m = clazz.methods[0]
833 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800834 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700835
836
837def verify_actions(clazz):
838 """Verify intent actions.
839 All action names must be named ACTION_FOO.
840 All action values must be scoped by package and match name:
841 package android.foo {
842 String ACTION_BAR = "android.foo.action.BAR";
843 }"""
844 for f in clazz.fields:
845 if f.value is None: continue
846 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700847 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600848 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700849
850 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
851 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
852 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800853 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700854 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700855 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700856 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700857 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700858 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700859 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
860 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700861 else:
862 prefix = clazz.pkg.name + ".action"
863 expected = prefix + "." + f.name[7:]
864 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700865 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700866
867
868def verify_extras(clazz):
869 """Verify intent extras.
870 All extra names must be named EXTRA_FOO.
871 All extra values must be scoped by package and match name:
872 package android.foo {
873 String EXTRA_BAR = "android.foo.extra.BAR";
874 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700875 if clazz.fullname == "android.app.Notification": return
876 if clazz.fullname == "android.appwidget.AppWidgetManager": return
877
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700878 for f in clazz.fields:
879 if f.value is None: continue
880 if f.name.startswith("ACTION_"): continue
881
882 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
883 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
884 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800885 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700886 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700887 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700888 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700889 elif clazz.pkg.name == "android.app.admin":
890 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700891 else:
892 prefix = clazz.pkg.name + ".extra"
893 expected = prefix + "." + f.name[6:]
894 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700895 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700896
897
898def verify_equals(clazz):
899 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700900 eq = False
901 hc = False
902 for m in clazz.methods:
Adrian Roosb787c182019-01-03 18:54:33 +0100903 if "static" in m.split: continue
904 if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
905 if m.sig_matches("int", "hashCode", []): hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700906 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800907 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700908
909
910def verify_parcelable(clazz):
911 """Verify that Parcelable objects aren't hiding required bits."""
Adrian Roosb787c182019-01-03 18:54:33 +0100912 if clazz.implements == "android.os.Parcelable":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700913 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
914 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
915 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
916
917 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800918 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700919
Adrian Roosb787c182019-01-03 18:54:33 +0100920 if "final" not in clazz.split:
Jeff Sharkey331279b2016-02-29 16:02:02 -0700921 error(clazz, None, "FW8", "Parcelable classes must be final")
922
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700923 for c in clazz.ctors:
924 if c.args == ["android.os.Parcel"]:
925 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
926
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700927
928def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800929 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700930 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600931 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700932 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800933 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700934 for f in clazz.fields:
935 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800936 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700937
938
939def verify_fields(clazz):
940 """Verify that all exposed fields are final.
941 Exposed fields must follow myName style.
942 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700943
944 IGNORE_BARE_FIELDS = [
945 "android.app.ActivityManager.RecentTaskInfo",
946 "android.app.Notification",
947 "android.content.pm.ActivityInfo",
948 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600949 "android.content.pm.ComponentInfo",
950 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700951 "android.content.pm.FeatureGroupInfo",
952 "android.content.pm.InstrumentationInfo",
953 "android.content.pm.PackageInfo",
954 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600955 "android.content.res.Configuration",
956 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700957 "android.os.Message",
958 "android.system.StructPollfd",
959 ]
960
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700961 for f in clazz.fields:
962 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700963 if clazz.fullname in IGNORE_BARE_FIELDS:
964 pass
965 elif clazz.fullname.endswith("LayoutParams"):
966 pass
967 elif clazz.fullname.startswith("android.util.Mutable"):
968 pass
969 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800970 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700971
Adrian Roosd1e38922019-01-14 15:44:15 +0100972 if "static" not in f.split and "property" not in f.split:
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700973 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800974 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700975
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700976 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800977 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700978
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700979 if re.match("[A-Z_]+", f.name):
980 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800981 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700982
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700983
984def verify_register(clazz):
985 """Verify parity of registration methods.
986 Callback objects use register/unregister methods.
987 Listener objects use add/remove methods."""
988 methods = [ m.name for m in clazz.methods ]
989 for m in clazz.methods:
990 if "Callback" in m.raw:
991 if m.name.startswith("register"):
992 other = "unregister" + m.name[8:]
993 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800994 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700995 if m.name.startswith("unregister"):
996 other = "register" + m.name[10:]
997 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800998 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700999
1000 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001001 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001002
1003 if "Listener" in m.raw:
1004 if m.name.startswith("add"):
1005 other = "remove" + m.name[3:]
1006 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001007 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001008 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
1009 other = "add" + m.name[6:]
1010 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001011 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001012
1013 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001014 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001015
1016
1017def verify_sync(clazz):
1018 """Verify synchronized methods aren't exposed."""
1019 for m in clazz.methods:
1020 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001021 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001022
1023
1024def verify_intent_builder(clazz):
1025 """Verify that Intent builders are createFooIntent() style."""
1026 if clazz.name == "Intent": return
1027
1028 for m in clazz.methods:
1029 if m.typ == "android.content.Intent":
1030 if m.name.startswith("create") and m.name.endswith("Intent"):
1031 pass
1032 else:
Adam Powell539ea122015-04-10 13:01:37 -07001033 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001034
1035
1036def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001037 """Verify that helper classes are named consistently with what they extend.
1038 All developer extendable methods should be named onFoo()."""
1039 test_methods = False
Adrian Roosb787c182019-01-03 18:54:33 +01001040 if clazz.extends == "android.app.Service":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001041 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001042 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001043 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001044
1045 found = False
1046 for f in clazz.fields:
1047 if f.name == "SERVICE_INTERFACE":
1048 found = True
1049 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001050 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001051
Adrian Roosb787c182019-01-03 18:54:33 +01001052 if clazz.extends == "android.content.ContentProvider":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001053 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001054 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001055 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001056
1057 found = False
1058 for f in clazz.fields:
1059 if f.name == "PROVIDER_INTERFACE":
1060 found = True
1061 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001062 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001063
Adrian Roosb787c182019-01-03 18:54:33 +01001064 if clazz.extends == "android.content.BroadcastReceiver":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001065 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001066 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001067 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001068
Adrian Roosb787c182019-01-03 18:54:33 +01001069 if clazz.extends == "android.app.Activity":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001070 test_methods = True
1071 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001072 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001073
1074 if test_methods:
1075 for m in clazz.methods:
1076 if "final" in m.split: continue
1077 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001078 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001079 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001080 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001081 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001082
1083
1084def verify_builder(clazz):
1085 """Verify builder classes.
1086 Methods should return the builder to enable chaining."""
Adrian Roosb787c182019-01-03 18:54:33 +01001087 if clazz.extends: return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001088 if not clazz.name.endswith("Builder"): return
1089
1090 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001091 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001092
1093 has_build = False
1094 for m in clazz.methods:
1095 if m.name == "build":
1096 has_build = True
1097 continue
1098
1099 if m.name.startswith("get"): continue
1100 if m.name.startswith("clear"): continue
1101
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001102 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001103 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001104
1105 if m.name.startswith("set"):
1106 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001107 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001108
1109 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001110 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001111
1112
1113def verify_aidl(clazz):
1114 """Catch people exposing raw AIDL."""
Adrian Roosb787c182019-01-03 18:54:33 +01001115 if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001116 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001117
1118
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001119def verify_internal(clazz):
1120 """Catch people exposing internal classes."""
1121 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001122 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001123
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001124def layering_build_ranking(ranking_list):
1125 r = {}
1126 for rank, ps in enumerate(ranking_list):
1127 if not isinstance(ps, list):
1128 ps = [ps]
1129 for p in ps:
1130 rs = r
1131 for n in p.split('.'):
1132 if n not in rs:
1133 rs[n] = {}
1134 rs = rs[n]
1135 rs['-rank'] = rank
1136 return r
1137
1138LAYERING_PACKAGE_RANKING = layering_build_ranking([
1139 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
1140 "android.app",
1141 "android.widget",
1142 "android.view",
1143 "android.animation",
1144 "android.provider",
1145 ["android.content","android.graphics.drawable"],
1146 "android.database",
1147 "android.text",
1148 "android.graphics",
1149 "android.os",
1150 "android.util"
1151])
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001152
1153def verify_layering(clazz):
1154 """Catch package layering violations.
1155 For example, something in android.os depending on android.app."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001156
1157 def rank(p):
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001158 r = None
1159 l = LAYERING_PACKAGE_RANKING
1160 for n in p.split('.'):
1161 if n in l:
1162 l = l[n]
1163 if '-rank' in l:
1164 r = l['-rank']
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001165 else:
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001166 break
1167 return r
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001168
1169 cr = rank(clazz.pkg.name)
1170 if cr is None: return
1171
1172 for f in clazz.fields:
1173 ir = rank(f.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001174 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001175 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001176
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001177 for m in itertools.chain(clazz.methods, clazz.ctors):
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001178 ir = rank(m.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001179 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001180 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001181 for arg in m.args:
1182 ir = rank(arg)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001183 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001184 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001185
1186
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001187def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001188 """Verifies that boolean accessors are named correctly.
1189 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001190
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001191 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
1192 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001193
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001194 gets = [ m for m in clazz.methods if is_get(m) ]
1195 sets = [ m for m in clazz.methods if is_set(m) ]
1196
1197 def error_if_exists(methods, trigger, expected, actual):
1198 for m in methods:
1199 if m.name == actual:
1200 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001201
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001202 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001203 if is_get(m):
1204 if re.match("is[A-Z]", m.name):
1205 target = m.name[2:]
1206 expected = "setIs" + target
1207 error_if_exists(sets, m.name, expected, "setHas" + target)
1208 elif re.match("has[A-Z]", m.name):
1209 target = m.name[3:]
1210 expected = "setHas" + target
1211 error_if_exists(sets, m.name, expected, "setIs" + target)
1212 error_if_exists(sets, m.name, expected, "set" + target)
1213 elif re.match("get[A-Z]", m.name):
1214 target = m.name[3:]
1215 expected = "set" + target
1216 error_if_exists(sets, m.name, expected, "setIs" + target)
1217 error_if_exists(sets, m.name, expected, "setHas" + target)
1218
1219 if is_set(m):
1220 if re.match("set[A-Z]", m.name):
1221 target = m.name[3:]
1222 expected = "get" + target
1223 error_if_exists(sets, m.name, expected, "is" + target)
1224 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001225
1226
1227def verify_collections(clazz):
1228 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001229 if clazz.fullname == "android.os.Bundle": return
1230
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001231 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
1232 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
1233 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001234 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001235 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001236 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001237 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001238 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001239
1240
1241def verify_flags(clazz):
1242 """Verifies that flags are non-overlapping."""
1243 known = collections.defaultdict(int)
1244 for f in clazz.fields:
1245 if "FLAG_" in f.name:
1246 try:
1247 val = int(f.value)
1248 except:
1249 continue
1250
1251 scope = f.name[0:f.name.index("FLAG_")]
1252 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001253 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001254 known[scope] |= val
1255
1256
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001257def verify_exception(clazz):
1258 """Verifies that methods don't throw generic exceptions."""
1259 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001260 for t in m.throws:
1261 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
1262 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001263
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001264 if t in ["android.os.RemoteException"]:
1265 if clazz.name == "android.content.ContentProviderClient": continue
1266 if clazz.name == "android.os.Binder": continue
1267 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -07001268
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001269 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
1270
1271 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
1272 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -07001273
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001274GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001275
1276def verify_google(clazz):
1277 """Verifies that APIs never reference Google."""
1278
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001279 if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001280 error(clazz, None, None, "Must never reference Google")
1281
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001282 for test in clazz.ctors, clazz.fields, clazz.methods:
1283 for t in test:
1284 if GOOGLE_IGNORECASE.search(t.raw) is not None:
1285 error(clazz, t, None, "Must never reference Google")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001286
1287
1288def verify_bitset(clazz):
1289 """Verifies that we avoid using heavy BitSet."""
1290
1291 for f in clazz.fields:
1292 if f.typ == "java.util.BitSet":
1293 error(clazz, f, None, "Field type must not be heavy BitSet")
1294
1295 for m in clazz.methods:
1296 if m.typ == "java.util.BitSet":
1297 error(clazz, m, None, "Return type must not be heavy BitSet")
1298 for arg in m.args:
1299 if arg == "java.util.BitSet":
1300 error(clazz, m, None, "Argument type must not be heavy BitSet")
1301
1302
1303def verify_manager(clazz):
1304 """Verifies that FooManager is only obtained from Context."""
1305
1306 if not clazz.name.endswith("Manager"): return
1307
1308 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001309 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001310
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001311 for m in clazz.methods:
1312 if m.typ == clazz.fullname:
1313 error(clazz, m, None, "Managers must always be obtained from Context")
1314
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001315
1316def verify_boxed(clazz):
1317 """Verifies that methods avoid boxed primitives."""
1318
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001319 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 -08001320
1321 for c in clazz.ctors:
1322 for arg in c.args:
1323 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001324 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001325
1326 for f in clazz.fields:
1327 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001328 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001329
1330 for m in clazz.methods:
1331 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001332 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001333 for arg in m.args:
1334 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001335 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001336
1337
1338def verify_static_utils(clazz):
1339 """Verifies that helper classes can't be constructed."""
1340 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001341 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001342
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001343 # Only care about classes with default constructors
1344 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
1345 test = []
1346 test.extend(clazz.fields)
1347 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001348
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001349 if len(test) == 0: return
1350 for t in test:
1351 if "static" not in t.split:
1352 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001353
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001354 error(clazz, None, None, "Fully-static utility classes must not have constructor")
1355
1356
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001357def verify_overload_args(clazz):
1358 """Verifies that method overloads add new arguments at the end."""
1359 if clazz.fullname.startswith("android.opengl"): return
1360
1361 overloads = collections.defaultdict(list)
1362 for m in clazz.methods:
1363 if "deprecated" in m.split: continue
1364 overloads[m.name].append(m)
1365
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001366 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001367 if len(methods) <= 1: continue
1368
1369 # Look for arguments common across all overloads
1370 def cluster(args):
1371 count = collections.defaultdict(int)
1372 res = set()
1373 for i in range(len(args)):
1374 a = args[i]
1375 res.add("%s#%d" % (a, count[a]))
1376 count[a] += 1
1377 return res
1378
1379 common_args = cluster(methods[0].args)
1380 for m in methods:
1381 common_args = common_args & cluster(m.args)
1382
1383 if len(common_args) == 0: continue
1384
1385 # Require that all common arguments are present at start of signature
1386 locked_sig = None
1387 for m in methods:
1388 sig = m.args[0:len(common_args)]
1389 if not common_args.issubset(cluster(sig)):
1390 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1391 elif not locked_sig:
1392 locked_sig = sig
1393 elif locked_sig != sig:
1394 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1395
1396
1397def verify_callback_handlers(clazz):
1398 """Verifies that methods adding listener/callback have overload
1399 for specifying delivery thread."""
1400
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001401 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001402 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001403 "animation",
1404 "view",
1405 "graphics",
1406 "transition",
1407 "widget",
1408 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001409 ]
1410 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001411 if s in clazz.pkg.name_path: return
1412 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001413
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001414 # Ignore UI classes which assume main thread
1415 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1416 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1417 if s in clazz.fullname: return
1418 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1419 for s in ["Loader"]:
1420 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001421
1422 found = {}
1423 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001424 examine = clazz.ctors + clazz.methods
1425 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001426 if m.name.startswith("unregister"): continue
1427 if m.name.startswith("remove"): continue
1428 if re.match("on[A-Z]+", m.name): continue
1429
1430 by_name[m.name].append(m)
1431
1432 for a in m.args:
1433 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1434 found[m.name] = m
1435
1436 for f in found.values():
1437 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001438 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001439 for m in by_name[f.name]:
1440 if "android.os.Handler" in m.args:
1441 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001442 if "java.util.concurrent.Executor" in m.args:
1443 takes_exec = True
1444 if not takes_exec:
1445 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001446
1447
1448def verify_context_first(clazz):
1449 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001450 examine = clazz.ctors + clazz.methods
1451 for m in examine:
1452 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001453 if "android.content.Context" in m.args[1:]:
1454 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001455 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1456 if "android.content.ContentResolver" in m.args[1:]:
1457 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001458
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001459
1460def verify_listener_last(clazz):
1461 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1462 examine = clazz.ctors + clazz.methods
1463 for m in examine:
1464 if "Listener" in m.name or "Callback" in m.name: continue
1465 found = False
1466 for a in m.args:
1467 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1468 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001469 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001470 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1471
1472
1473def verify_resource_names(clazz):
1474 """Verifies that resource names have consistent case."""
1475 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1476
1477 # Resources defined by files are foo_bar_baz
1478 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1479 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001480 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1481 if f.name.startswith("config_"):
1482 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1483
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001484 if re.match("[a-z1-9_]+$", f.name): continue
1485 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1486
1487 # Resources defined inside files are fooBarBaz
1488 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1489 for f in clazz.fields:
1490 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1491 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1492 if re.match("state_[a-z_]*$", f.name): continue
1493
1494 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1495 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1496
1497 # Styles are FooBar_Baz
1498 if clazz.name in ["style"]:
1499 for f in clazz.fields:
1500 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1501 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001502
1503
Jeff Sharkey331279b2016-02-29 16:02:02 -07001504def verify_files(clazz):
1505 """Verifies that methods accepting File also accept streams."""
1506
1507 has_file = set()
1508 has_stream = set()
1509
1510 test = []
1511 test.extend(clazz.ctors)
1512 test.extend(clazz.methods)
1513
1514 for m in test:
1515 if "java.io.File" in m.args:
1516 has_file.add(m)
1517 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:
1518 has_stream.add(m.name)
1519
1520 for m in has_file:
1521 if m.name not in has_stream:
1522 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1523
1524
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001525def verify_manager_list(clazz):
1526 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1527
1528 if not clazz.name.endswith("Manager"): return
1529
1530 for m in clazz.methods:
1531 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1532 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1533
1534
Jeff Sharkey26c80902016-12-21 13:41:17 -07001535def verify_abstract_inner(clazz):
1536 """Verifies that abstract inner classes are static."""
1537
1538 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
Adrian Roosb787c182019-01-03 18:54:33 +01001539 if "abstract" in clazz.split and "static" not in clazz.split:
Jeff Sharkey26c80902016-12-21 13:41:17 -07001540 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1541
1542
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001543def verify_runtime_exceptions(clazz):
1544 """Verifies that runtime exceptions aren't listed in throws."""
1545
1546 banned = [
1547 "java.lang.NullPointerException",
1548 "java.lang.ClassCastException",
1549 "java.lang.IndexOutOfBoundsException",
1550 "java.lang.reflect.UndeclaredThrowableException",
1551 "java.lang.reflect.MalformedParametersException",
1552 "java.lang.reflect.MalformedParameterizedTypeException",
1553 "java.lang.invoke.WrongMethodTypeException",
1554 "java.lang.EnumConstantNotPresentException",
1555 "java.lang.IllegalMonitorStateException",
1556 "java.lang.SecurityException",
1557 "java.lang.UnsupportedOperationException",
1558 "java.lang.annotation.AnnotationTypeMismatchException",
1559 "java.lang.annotation.IncompleteAnnotationException",
1560 "java.lang.TypeNotPresentException",
1561 "java.lang.IllegalStateException",
1562 "java.lang.ArithmeticException",
1563 "java.lang.IllegalArgumentException",
1564 "java.lang.ArrayStoreException",
1565 "java.lang.NegativeArraySizeException",
1566 "java.util.MissingResourceException",
1567 "java.util.EmptyStackException",
1568 "java.util.concurrent.CompletionException",
1569 "java.util.concurrent.RejectedExecutionException",
1570 "java.util.IllformedLocaleException",
1571 "java.util.ConcurrentModificationException",
1572 "java.util.NoSuchElementException",
1573 "java.io.UncheckedIOException",
1574 "java.time.DateTimeException",
1575 "java.security.ProviderException",
1576 "java.nio.BufferUnderflowException",
1577 "java.nio.BufferOverflowException",
1578 ]
1579
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001580 examine = clazz.ctors + clazz.methods
1581 for m in examine:
1582 for t in m.throws:
1583 if t in banned:
1584 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001585
1586
1587def verify_error(clazz):
1588 """Verifies that we always use Exception instead of Error."""
1589 if not clazz.extends: return
1590 if clazz.extends.endswith("Error"):
1591 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1592 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1593 error(clazz, None, None, "Exceptions must be named FooException")
1594
1595
1596def verify_units(clazz):
1597 """Verifies that we use consistent naming for units."""
1598
1599 # If we find K, recommend replacing with V
1600 bad = {
1601 "Ns": "Nanos",
1602 "Ms": "Millis or Micros",
1603 "Sec": "Seconds", "Secs": "Seconds",
1604 "Hr": "Hours", "Hrs": "Hours",
1605 "Mo": "Months", "Mos": "Months",
1606 "Yr": "Years", "Yrs": "Years",
1607 "Byte": "Bytes", "Space": "Bytes",
1608 }
1609
1610 for m in clazz.methods:
1611 if m.typ not in ["short","int","long"]: continue
1612 for k, v in bad.iteritems():
1613 if m.name.endswith(k):
1614 error(clazz, m, None, "Expected method name units to be " + v)
1615 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1616 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1617 if m.name.endswith("Seconds"):
1618 error(clazz, m, None, "Returned time values must be in milliseconds")
1619
1620 for m in clazz.methods:
1621 typ = m.typ
1622 if typ == "void":
1623 if len(m.args) != 1: continue
1624 typ = m.args[0]
1625
1626 if m.name.endswith("Fraction") and typ != "float":
1627 error(clazz, m, None, "Fractions must use floats")
1628 if m.name.endswith("Percentage") and typ != "int":
1629 error(clazz, m, None, "Percentage must use ints")
1630
1631
1632def verify_closable(clazz):
1633 """Verifies that classes are AutoClosable."""
Adrian Roosb787c182019-01-03 18:54:33 +01001634 if clazz.implements == "java.lang.AutoCloseable": return
1635 if clazz.implements == "java.io.Closeable": return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001636
1637 for m in clazz.methods:
1638 if len(m.args) > 0: continue
1639 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1640 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1641 return
1642
1643
Jake Wharton9e6738f2017-08-23 11:59:55 -04001644def verify_member_name_not_kotlin_keyword(clazz):
1645 """Prevent method names which are keywords in Kotlin."""
1646
1647 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1648 # This list does not include Java keywords as those are already impossible to use.
1649 keywords = [
1650 'as',
1651 'fun',
1652 'in',
1653 'is',
1654 'object',
1655 'typealias',
1656 'val',
1657 'var',
1658 'when',
1659 ]
1660
1661 for m in clazz.methods:
1662 if m.name in keywords:
1663 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1664 for f in clazz.fields:
1665 if f.name in keywords:
1666 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1667
1668
1669def verify_method_name_not_kotlin_operator(clazz):
1670 """Warn about method names which become operators in Kotlin."""
1671
1672 binary = set()
1673
1674 def unique_binary_op(m, op):
1675 if op in binary:
1676 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1677 binary.add(op)
1678
1679 for m in clazz.methods:
Adrian Roosd1e38922019-01-14 15:44:15 +01001680 if 'static' in m.split or 'operator' in m.split:
Jake Wharton9e6738f2017-08-23 11:59:55 -04001681 continue
1682
1683 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1684 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1685 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1686
1687 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1688 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1689 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1690 # practical way of checking that relationship here.
1691 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1692
1693 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1694 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1695 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1696 unique_binary_op(m, m.name)
1697
1698 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1699 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1700 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1701
1702 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1703 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1704 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1705
1706 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1707 if m.name == 'invoke':
1708 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1709
1710 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1711 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1712 and len(m.args) == 1 \
1713 and m.typ == 'void':
1714 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1715 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1716
1717
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001718def verify_collections_over_arrays(clazz):
1719 """Warn that [] should be Collections."""
1720
Adrian Roosb787c182019-01-03 18:54:33 +01001721 if "@interface" in clazz.split:
1722 return
1723
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001724 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1725 for m in clazz.methods:
1726 if m.typ.endswith("[]") and m.typ not in safe:
1727 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1728 for arg in m.args:
1729 if arg.endswith("[]") and arg not in safe:
1730 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1731
1732
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001733def verify_user_handle(clazz):
1734 """Methods taking UserHandle should be ForUser or AsUser."""
1735 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1736 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1737 if clazz.fullname == "android.content.pm.LauncherApps": return
1738 if clazz.fullname == "android.os.UserHandle": return
1739 if clazz.fullname == "android.os.UserManager": return
1740
1741 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001742 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001743
1744 has_arg = "android.os.UserHandle" in m.args
1745 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1746
1747 if clazz.fullname.endswith("Manager") and has_arg:
1748 warn(clazz, m, None, "When a method overload is needed to target a specific "
1749 "UserHandle, callers should be directed to use "
1750 "Context.createPackageContextAsUser() and re-obtain the relevant "
1751 "Manager, and no new API should be added")
1752 elif has_arg and not has_name:
1753 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1754 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001755
1756
1757def verify_params(clazz):
1758 """Parameter classes should be 'Params'."""
1759 if clazz.name.endswith("Params"): return
1760 if clazz.fullname == "android.app.ActivityOptions": return
1761 if clazz.fullname == "android.app.BroadcastOptions": return
1762 if clazz.fullname == "android.os.Bundle": return
1763 if clazz.fullname == "android.os.BaseBundle": return
1764 if clazz.fullname == "android.os.PersistableBundle": return
1765
1766 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1767 for b in bad:
1768 if clazz.name.endswith(b):
1769 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1770
1771
1772def verify_services(clazz):
1773 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1774 if clazz.fullname != "android.content.Context": return
1775
1776 for f in clazz.fields:
1777 if f.typ != "java.lang.String": continue
1778 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1779 if found:
1780 expected = found.group(1).lower()
1781 if f.value != expected:
1782 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1783
1784
1785def verify_tense(clazz):
1786 """Verify tenses of method names."""
1787 if clazz.fullname.startswith("android.opengl"): return
1788
1789 for m in clazz.methods:
1790 if m.name.endswith("Enable"):
1791 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1792
1793
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001794def verify_icu(clazz):
1795 """Verifies that richer ICU replacements are used."""
1796 better = {
1797 "java.util.TimeZone": "android.icu.util.TimeZone",
1798 "java.util.Calendar": "android.icu.util.Calendar",
1799 "java.util.Locale": "android.icu.util.ULocale",
1800 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1801 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1802 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1803 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1804 "java.lang.Character": "android.icu.lang.UCharacter",
1805 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1806 "java.text.Collator": "android.icu.text.Collator",
1807 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1808 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1809 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1810 "java.text.DateFormat": "android.icu.text.DateFormat",
1811 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1812 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1813 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1814 }
1815
1816 for m in clazz.ctors + clazz.methods:
1817 types = []
1818 types.extend(m.typ)
1819 types.extend(m.args)
1820 for arg in types:
1821 if arg in better:
1822 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1823
1824
1825def verify_clone(clazz):
1826 """Verify that clone() isn't implemented; see EJ page 61."""
1827 for m in clazz.methods:
1828 if m.name == "clone":
1829 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1830
1831
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001832def verify_pfd(clazz):
1833 """Verify that android APIs use PFD over FD."""
1834 examine = clazz.ctors + clazz.methods
1835 for m in examine:
1836 if m.typ == "java.io.FileDescriptor":
1837 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1838 if m.typ == "int":
1839 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1840 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1841 for arg in m.args:
1842 if arg == "java.io.FileDescriptor":
1843 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1844
1845 for f in clazz.fields:
1846 if f.typ == "java.io.FileDescriptor":
1847 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1848
1849
1850def verify_numbers(clazz):
1851 """Discourage small numbers types like short and byte."""
1852
1853 discouraged = ["short","byte"]
1854
1855 for c in clazz.ctors:
1856 for arg in c.args:
1857 if arg in discouraged:
1858 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1859
1860 for f in clazz.fields:
1861 if f.typ in discouraged:
1862 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1863
1864 for m in clazz.methods:
1865 if m.typ in discouraged:
1866 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1867 for arg in m.args:
1868 if arg in discouraged:
1869 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1870
1871
1872def verify_singleton(clazz):
1873 """Catch singleton objects with constructors."""
1874
1875 singleton = False
1876 for m in clazz.methods:
1877 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1878 singleton = True
1879
1880 if singleton:
1881 for c in clazz.ctors:
1882 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1883
1884
1885
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001886def is_interesting(clazz):
1887 """Test if given class is interesting from an Android PoV."""
1888
1889 if clazz.pkg.name.startswith("java"): return False
1890 if clazz.pkg.name.startswith("junit"): return False
1891 if clazz.pkg.name.startswith("org.apache"): return False
1892 if clazz.pkg.name.startswith("org.xml"): return False
1893 if clazz.pkg.name.startswith("org.json"): return False
1894 if clazz.pkg.name.startswith("org.w3c"): return False
1895 if clazz.pkg.name.startswith("android.icu."): return False
1896 return True
1897
1898
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001899def examine_clazz(clazz):
1900 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001901
1902 notice(clazz)
1903
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001904 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001905
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001906 verify_constants(clazz)
1907 verify_enums(clazz)
1908 verify_class_names(clazz)
1909 verify_method_names(clazz)
1910 verify_callbacks(clazz)
1911 verify_listeners(clazz)
1912 verify_actions(clazz)
1913 verify_extras(clazz)
1914 verify_equals(clazz)
1915 verify_parcelable(clazz)
1916 verify_protected(clazz)
1917 verify_fields(clazz)
1918 verify_register(clazz)
1919 verify_sync(clazz)
1920 verify_intent_builder(clazz)
1921 verify_helper_classes(clazz)
1922 verify_builder(clazz)
1923 verify_aidl(clazz)
1924 verify_internal(clazz)
1925 verify_layering(clazz)
1926 verify_boolean(clazz)
1927 verify_collections(clazz)
1928 verify_flags(clazz)
1929 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001930 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001931 verify_bitset(clazz)
1932 verify_manager(clazz)
1933 verify_boxed(clazz)
1934 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001935 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001936 verify_callback_handlers(clazz)
1937 verify_context_first(clazz)
1938 verify_listener_last(clazz)
1939 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001940 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001941 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001942 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001943 verify_runtime_exceptions(clazz)
1944 verify_error(clazz)
1945 verify_units(clazz)
1946 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001947 verify_member_name_not_kotlin_keyword(clazz)
1948 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001949 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001950 verify_user_handle(clazz)
1951 verify_params(clazz)
1952 verify_services(clazz)
1953 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001954 verify_icu(clazz)
1955 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001956 verify_pfd(clazz)
1957 verify_numbers(clazz)
1958 verify_singleton(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001959
1960
Adrian Roos038a0292018-12-19 17:11:21 +01001961def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001962 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001963 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001964 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001965 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01001966 _parse_stream(stream, examine_clazz, base_f=base_stream,
1967 in_classes_with_base=in_classes_with_base,
1968 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001969 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001970
1971
1972def examine_api(api):
1973 """Find all style issues in the given parsed API."""
1974 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001975 failures = {}
1976 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001977 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001978 return failures
1979
1980
Jeff Sharkey037458a2014-09-04 15:46:20 -07001981def verify_compat(cur, prev):
1982 """Find any incompatible API changes between two levels."""
1983 global failures
1984
1985 def class_exists(api, test):
1986 return test.fullname in api
1987
1988 def ctor_exists(api, clazz, test):
1989 for m in clazz.ctors:
1990 if m.ident == test.ident: return True
1991 return False
1992
1993 def all_methods(api, clazz):
1994 methods = list(clazz.methods)
1995 if clazz.extends is not None:
1996 methods.extend(all_methods(api, api[clazz.extends]))
1997 return methods
1998
1999 def method_exists(api, clazz, test):
2000 methods = all_methods(api, clazz)
2001 for m in methods:
2002 if m.ident == test.ident: return True
2003 return False
2004
2005 def field_exists(api, clazz, test):
2006 for f in clazz.fields:
2007 if f.ident == test.ident: return True
2008 return False
2009
2010 failures = {}
2011 for key in sorted(prev.keys()):
2012 prev_clazz = prev[key]
2013
2014 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002015 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002016 continue
2017
2018 cur_clazz = cur[key]
2019
2020 for test in prev_clazz.ctors:
2021 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002022 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002023
2024 methods = all_methods(prev, prev_clazz)
2025 for test in methods:
2026 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002027 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002028
2029 for test in prev_clazz.fields:
2030 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002031 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002032
2033 return failures
2034
2035
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002036def show_deprecations_at_birth(cur, prev):
2037 """Show API deprecations at birth."""
2038 global failures
2039
2040 # Remove all existing things so we're left with new
2041 for prev_clazz in prev.values():
2042 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002043 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002044
2045 sigs = { i.ident: i for i in prev_clazz.ctors }
2046 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
2047 sigs = { i.ident: i for i in prev_clazz.methods }
2048 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
2049 sigs = { i.ident: i for i in prev_clazz.fields }
2050 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
2051
2052 # Forget about class entirely when nothing new
2053 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
2054 del cur[prev_clazz.fullname]
2055
2056 for clazz in cur.values():
Adrian Roosb787c182019-01-03 18:54:33 +01002057 if "deprecated" in clazz.split and not clazz.fullname in prev:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002058 error(clazz, None, None, "Found API deprecation at birth")
2059
2060 for i in clazz.ctors + clazz.methods + clazz.fields:
Adrian Roosb787c182019-01-03 18:54:33 +01002061 if "deprecated" in i.split:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002062 error(clazz, i, None, "Found API deprecation at birth")
2063
2064 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
2065 format(reset=True)))
2066 for f in sorted(failures):
2067 print failures[f]
2068 print
2069
2070
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002071def show_stats(cur, prev):
2072 """Show API stats."""
2073
2074 stats = collections.defaultdict(int)
2075 for cur_clazz in cur.values():
2076 if not is_interesting(cur_clazz): continue
2077
2078 if cur_clazz.fullname not in prev:
2079 stats['new_classes'] += 1
2080 stats['new_ctors'] += len(cur_clazz.ctors)
2081 stats['new_methods'] += len(cur_clazz.methods)
2082 stats['new_fields'] += len(cur_clazz.fields)
2083 else:
2084 prev_clazz = prev[cur_clazz.fullname]
2085
2086 sigs = { i.ident: i for i in prev_clazz.ctors }
2087 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
2088 sigs = { i.ident: i for i in prev_clazz.methods }
2089 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
2090 sigs = { i.ident: i for i in prev_clazz.fields }
2091 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
2092
2093 if ctors + methods + fields > 0:
2094 stats['extend_classes'] += 1
2095 stats['extend_ctors'] += ctors
2096 stats['extend_methods'] += methods
2097 stats['extend_fields'] += fields
2098
2099 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
2100 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
2101
2102
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002103if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002104 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
2105 patterns. It ignores lint messages from a previous API level, if provided.")
2106 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
2107 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
2108 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01002109 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
2110 help="The base current.txt to use when examining system-current.txt or"
2111 " test-current.txt")
2112 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
2113 help="The base previous.txt to use when examining system-previous.txt or"
2114 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002115 parser.add_argument("--no-color", action='store_const', const=True,
2116 help="Disable terminal colors")
2117 parser.add_argument("--allow-google", action='store_const', const=True,
2118 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002119 parser.add_argument("--show-noticed", action='store_const', const=True,
2120 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002121 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
2122 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002123 parser.add_argument("--show-stats", action='store_const', const=True,
2124 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002125 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002126
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002127 if args['no_color']:
2128 USE_COLOR = False
2129
2130 if args['allow_google']:
2131 ALLOW_GOOGLE = True
2132
2133 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002134 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002135 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002136 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002137
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002138 if args['show_deprecations_at_birth']:
2139 with current_file as f:
2140 cur = _parse_stream(f)
2141 with previous_file as f:
2142 prev = _parse_stream(f)
2143 show_deprecations_at_birth(cur, prev)
2144 sys.exit()
2145
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002146 if args['show_stats']:
2147 with current_file as f:
2148 cur = _parse_stream(f)
2149 with previous_file as f:
2150 prev = _parse_stream(f)
2151 show_stats(cur, prev)
2152 sys.exit()
2153
Adrian Roos038a0292018-12-19 17:11:21 +01002154 classes_with_base = []
2155
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002156 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002157 if base_current_file:
2158 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002159 cur_fail, cur_noticed = examine_stream(f, base_f,
2160 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002161 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002162 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
2163
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002164 if not previous_file is None:
2165 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002166 if base_previous_file:
2167 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002168 prev_fail, prev_noticed = examine_stream(f, base_f,
2169 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002170 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002171 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002172
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002173 # ignore errors from previous API level
2174 for p in prev_fail:
2175 if p in cur_fail:
2176 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002177
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002178 # ignore classes unchanged from previous API level
2179 for k, v in prev_noticed.iteritems():
2180 if k in cur_noticed and v == cur_noticed[k]:
2181 del cur_noticed[k]
2182
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002183 """
2184 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002185 # look for compatibility issues
2186 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002187
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002188 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2189 for f in sorted(compat_fail):
2190 print compat_fail[f]
2191 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002192 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002193
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002194 if args['show_noticed'] and len(cur_noticed) != 0:
2195 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2196 for f in sorted(cur_noticed.keys()):
2197 print f
2198 print
2199
Jason Monk53b2a732017-11-10 15:43:17 -05002200 if len(cur_fail) != 0:
2201 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2202 for f in sorted(cur_fail):
2203 print cur_fail[f]
2204 print
2205 sys.exit(77)