blob: 59e89f515e8257fd6177359b83887bcfaae1f3e7 [file] [log] [blame]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001#!/usr/bin/env python
2
3# Copyright (C) 2014 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the 'License');
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an 'AS IS' BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Enforces common Android public API design patterns. It ignores lint messages from
19a previous API level, if provided.
20
21Usage: apilint.py current.txt
22Usage: apilint.py current.txt previous.txt
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070023
24You can also splice in blame details like this:
25$ git blame api/current.txt -t -e > /tmp/currentblame.txt
26$ apilint.py /tmp/currentblame.txt previous.txt --no-color
Jeff Sharkey8190f4882014-08-28 12:24:07 -070027"""
28
Adrian Roos1f1b6a82019-01-05 20:09:38 +010029import re, sys, collections, traceback, argparse, itertools
Jeff Sharkey8190f4882014-08-28 12:24:07 -070030
31
32BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
33
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070034ALLOW_GOOGLE = False
35USE_COLOR = True
36
Jeff Sharkey8190f4882014-08-28 12:24:07 -070037def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
38 # manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070039 if not USE_COLOR: return ""
Jeff Sharkey8190f4882014-08-28 12:24:07 -070040 codes = []
41 if reset: codes.append("0")
42 else:
43 if not fg is None: codes.append("3%d" % (fg))
44 if not bg is None:
45 if not bright: codes.append("4%d" % (bg))
46 else: codes.append("10%d" % (bg))
47 if bold: codes.append("1")
48 elif dim: codes.append("2")
49 else: codes.append("22")
50 return "\033[%sm" % (";".join(codes))
51
52
53class Field():
Adrian Roosb787c182019-01-03 18:54:33 +010054 def __init__(self, clazz, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070055 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070056 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070057 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070058 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -070059
Adrian Roosb787c182019-01-03 18:54:33 +010060 if sig_format == 2:
61 V2LineParser(raw).parse_into_field(self)
62 elif sig_format == 1:
63 # drop generics for now; may need multiple passes
64 raw = re.sub("<[^<]+?>", "", raw)
65 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -060066
Adrian Roosb787c182019-01-03 18:54:33 +010067 raw = raw.split()
68 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070069
Adrian Roosb787c182019-01-03 18:54:33 +010070 for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
71 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070072
Adrian Roosb787c182019-01-03 18:54:33 +010073 # ignore annotations for now
74 raw = [ r for r in raw if not r.startswith("@") ]
Jeff Sharkey40d67f42018-07-17 13:29:40 -060075
Adrian Roosb787c182019-01-03 18:54:33 +010076 self.typ = raw[0]
77 self.name = raw[1].strip(";")
78 if len(raw) >= 4 and raw[2] == "=":
79 self.value = raw[3].strip(';"')
80 else:
81 self.value = None
82
83 self.ident = "-".join((self.typ, self.name, self.value or ""))
Jeff Sharkey037458a2014-09-04 15:46:20 -070084
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -070085 def __hash__(self):
86 return hash(self.raw)
87
Jeff Sharkey8190f4882014-08-28 12:24:07 -070088 def __repr__(self):
89 return self.raw
90
Jeff Sharkey8190f4882014-08-28 12:24:07 -070091class Method():
Adrian Roosb787c182019-01-03 18:54:33 +010092 def __init__(self, clazz, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070093 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070094 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070095 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070096 self.blame = blame
97
Adrian Roosb787c182019-01-03 18:54:33 +010098 if sig_format == 2:
99 V2LineParser(raw).parse_into_method(self)
100 elif sig_format == 1:
101 # drop generics for now; may need multiple passes
102 raw = re.sub("<[^<]+?>", "", raw)
103 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700104
Adrian Roosb787c182019-01-03 18:54:33 +0100105 # handle each clause differently
106 raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600107
Adrian Roosb787c182019-01-03 18:54:33 +0100108 # parse prefixes
109 raw = re.split("[\s]+", raw_prefix)
110 for r in ["", ";"]:
111 while r in raw: raw.remove(r)
112 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700113
Adrian Roosb787c182019-01-03 18:54:33 +0100114 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator", "synchronized"]:
115 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700116
Adrian Roosb787c182019-01-03 18:54:33 +0100117 self.typ = raw[0]
118 self.name = raw[1]
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600119
Adrian Roosb787c182019-01-03 18:54:33 +0100120 # parse args
121 self.args = []
122 for arg in re.split(",\s*", raw_args):
123 arg = re.split("\s", arg)
124 # ignore annotations for now
125 arg = [ a for a in arg if not a.startswith("@") ]
126 if len(arg[0]) > 0:
127 self.args.append(arg[0])
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600128
Adrian Roosb787c182019-01-03 18:54:33 +0100129 # parse throws
130 self.throws = []
131 for throw in re.split(",\s*", raw_throws):
132 self.throws.append(throw)
133 else:
134 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600135
Adrian Roosb787c182019-01-03 18:54:33 +0100136 self.ident = "-".join((self.typ, self.name, "-".join(self.args)))
137
138 def sig_matches(self, typ, name, args):
139 return typ == self.typ and name == self.name and args == self.args
Jeff Sharkey037458a2014-09-04 15:46:20 -0700140
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700141 def __hash__(self):
142 return hash(self.raw)
143
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700144 def __repr__(self):
145 return self.raw
146
147
148class Class():
Adrian Roosb787c182019-01-03 18:54:33 +0100149 def __init__(self, pkg, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700150 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700151 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700152 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700153 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700154 self.ctors = []
155 self.fields = []
156 self.methods = []
157
Adrian Roosb787c182019-01-03 18:54:33 +0100158 if sig_format == 2:
159 V2LineParser(raw).parse_into_class(self)
160 elif sig_format == 1:
161 # drop generics for now; may need multiple passes
162 raw = re.sub("<[^<]+?>", "", raw)
163 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600164
Adrian Roosb787c182019-01-03 18:54:33 +0100165 raw = raw.split()
166 self.split = list(raw)
167 if "class" in raw:
168 self.fullname = raw[raw.index("class")+1]
169 elif "interface" in raw:
170 self.fullname = raw[raw.index("interface")+1]
171 elif "@interface" in raw:
172 self.fullname = raw[raw.index("@interface")+1]
173 else:
174 raise ValueError("Funky class type %s" % (self.raw))
Jeff Sharkey037458a2014-09-04 15:46:20 -0700175
Adrian Roosb787c182019-01-03 18:54:33 +0100176 if "extends" in raw:
177 self.extends = raw[raw.index("extends")+1]
178 else:
179 self.extends = None
180
181 if "implements" in raw:
182 self.implements = raw[raw.index("implements")+1]
183 else:
184 self.implements = None
Jeff Sharkey037458a2014-09-04 15:46:20 -0700185 else:
Adrian Roosb787c182019-01-03 18:54:33 +0100186 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700187
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700188 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800189 self.fullname_path = self.fullname.split(".")
190
Adrian Roosb787c182019-01-03 18:54:33 +0100191 if self.extends is not None:
192 self.extends_path = self.extends.split(".")
193 else:
194 self.extends_path = []
195
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700196 self.name = self.fullname[self.fullname.rindex(".")+1:]
197
Adrian Roos6eb57b02018-12-13 22:08:29 +0100198 def merge_from(self, other):
199 self.ctors.extend(other.ctors)
200 self.fields.extend(other.fields)
201 self.methods.extend(other.methods)
202
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700203 def __hash__(self):
204 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
205
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700206 def __repr__(self):
207 return self.raw
208
209
210class Package():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700211 def __init__(self, line, raw, blame):
212 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700213 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700214 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700215
216 raw = raw.split()
217 self.name = raw[raw.index("package")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800218 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700219
220 def __repr__(self):
221 return self.raw
222
Adrian Roose5eeae72019-01-04 20:10:06 +0100223class V2Tokenizer(object):
224 __slots__ = ["raw"]
225
Adrian Rooscf82e042019-01-29 15:01:28 +0100226 SIGNATURE_PREFIX = "// Signature format: "
Adrian Roos5cdfb692019-01-05 22:04:55 +0100227 DELIMITER = re.compile(r'\s+|[()@<>;,={}/"!?]|\[\]|\.\.\.')
Adrian Roosb787c182019-01-03 18:54:33 +0100228 STRING_SPECIAL = re.compile(r'["\\]')
229
230 def __init__(self, raw):
231 self.raw = raw
Adrian Roosb787c182019-01-03 18:54:33 +0100232
Adrian Roose5eeae72019-01-04 20:10:06 +0100233 def tokenize(self):
234 tokens = []
235 current = 0
236 raw = self.raw
237 length = len(raw)
Adrian Roosb787c182019-01-03 18:54:33 +0100238
Adrian Roose5eeae72019-01-04 20:10:06 +0100239 while current < length:
240 while current < length:
241 start = current
242 match = V2Tokenizer.DELIMITER.search(raw, start)
243 if match is not None:
244 match_start = match.start()
245 if match_start == current:
246 end = match.end()
247 else:
248 end = match_start
249 else:
250 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100251
Adrian Roose5eeae72019-01-04 20:10:06 +0100252 token = raw[start:end]
253 current = end
Adrian Roosb787c182019-01-03 18:54:33 +0100254
Adrian Roose5eeae72019-01-04 20:10:06 +0100255 if token == "" or token[0] == " ":
256 continue
257 else:
258 break
Adrian Roosb787c182019-01-03 18:54:33 +0100259
Adrian Roose5eeae72019-01-04 20:10:06 +0100260 if token == "@":
261 if raw[start:start+11] == "@interface ":
262 current = start + 11
263 tokens.append("@interface")
264 continue
265 elif token == '/':
266 if raw[start:start+2] == "//":
267 current = length
268 continue
269 elif token == '"':
270 current, string_token = self.tokenize_string(raw, length, current)
271 tokens.append(token + string_token)
272 continue
Adrian Roosb787c182019-01-03 18:54:33 +0100273
Adrian Roose5eeae72019-01-04 20:10:06 +0100274 tokens.append(token)
Adrian Roosb787c182019-01-03 18:54:33 +0100275
Adrian Roose5eeae72019-01-04 20:10:06 +0100276 return tokens
Adrian Roosb787c182019-01-03 18:54:33 +0100277
Adrian Roose5eeae72019-01-04 20:10:06 +0100278 def tokenize_string(self, raw, length, current):
279 start = current
280 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100281 while start < end:
Adrian Roose5eeae72019-01-04 20:10:06 +0100282 match = V2Tokenizer.STRING_SPECIAL.search(raw, start)
Adrian Roosb787c182019-01-03 18:54:33 +0100283 if match:
284 if match.group() == '"':
285 end = match.end()
286 break
287 elif match.group() == '\\':
288 # ignore whatever is after the slash
289 start += 2
290 else:
291 raise ValueError("Unexpected match: `%s`" % (match.group()))
292 else:
Adrian Roose5eeae72019-01-04 20:10:06 +0100293 raise ValueError("Unexpected EOF tokenizing string: `%s`" % (raw[current - 1:],))
Adrian Roosb787c182019-01-03 18:54:33 +0100294
Adrian Roose5eeae72019-01-04 20:10:06 +0100295 token = raw[current:end]
296 return end, token
Adrian Roosb787c182019-01-03 18:54:33 +0100297
Adrian Roose5eeae72019-01-04 20:10:06 +0100298class V2LineParser(object):
299 __slots__ = ["tokenized", "current", "len"]
300
Adrian Roos258c5722019-01-21 15:43:15 +0100301 FIELD_KINDS = ("field", "property", "enum_constant")
Adrian Roosd1e38922019-01-14 15:44:15 +0100302 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 +0100303 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())
304
305 def __init__(self, raw):
Adrian Roose5eeae72019-01-04 20:10:06 +0100306 self.tokenized = V2Tokenizer(raw).tokenize()
Adrian Roosb787c182019-01-03 18:54:33 +0100307 self.current = 0
Adrian Roose5eeae72019-01-04 20:10:06 +0100308 self.len = len(self.tokenized)
Adrian Roosb787c182019-01-03 18:54:33 +0100309
310 def parse_into_method(self, method):
311 method.split = []
312 kind = self.parse_one_of("ctor", "method")
313 method.split.append(kind)
314 annotations = self.parse_annotations()
315 method.split.extend(self.parse_modifiers())
316 self.parse_matching_paren("<", ">")
317 if "@Deprecated" in annotations:
318 method.split.append("deprecated")
319 if kind == "ctor":
320 method.typ = "ctor"
321 else:
322 method.typ = self.parse_type()
323 method.split.append(method.typ)
324 method.name = self.parse_name()
325 method.split.append(method.name)
326 self.parse_token("(")
327 method.args = self.parse_args()
328 self.parse_token(")")
329 method.throws = self.parse_throws()
330 if "@interface" in method.clazz.split:
331 self.parse_annotation_default()
332 self.parse_token(";")
333 self.parse_eof()
334
335 def parse_into_class(self, clazz):
336 clazz.split = []
337 annotations = self.parse_annotations()
338 if "@Deprecated" in annotations:
339 clazz.split.append("deprecated")
340 clazz.split.extend(self.parse_modifiers())
341 kind = self.parse_one_of("class", "interface", "@interface", "enum")
342 if kind == "enum":
343 # enums are implicitly final
344 clazz.split.append("final")
345 clazz.split.append(kind)
346 clazz.fullname = self.parse_name()
347 self.parse_matching_paren("<", ">")
348 extends = self.parse_extends()
349 clazz.extends = extends[0] if extends else None
350 implements = self.parse_implements()
351 clazz.implements = implements[0] if implements else None
352 # The checks assume that interfaces are always found in implements, which isn't true for
353 # subinterfaces.
354 if not implements and "interface" in clazz.split:
355 clazz.implements = clazz.extends
356 self.parse_token("{")
357 self.parse_eof()
358
359 def parse_into_field(self, field):
Adrian Roos258c5722019-01-21 15:43:15 +0100360 kind = self.parse_one_of(*V2LineParser.FIELD_KINDS)
Adrian Roosb787c182019-01-03 18:54:33 +0100361 field.split = [kind]
362 annotations = self.parse_annotations()
363 if "@Deprecated" in annotations:
364 field.split.append("deprecated")
365 field.split.extend(self.parse_modifiers())
366 field.typ = self.parse_type()
367 field.split.append(field.typ)
368 field.name = self.parse_name()
369 field.split.append(field.name)
370 if self.parse_if("="):
371 field.value = self.parse_value_stripped()
372 else:
373 field.value = None
374
375 self.parse_token(";")
376 self.parse_eof()
377
378 def lookahead(self):
379 return self.tokenized[self.current]
380
381 def parse_one_of(self, *options):
382 found = self.lookahead()
383 if found not in options:
384 raise ValueError("Parsing failed, expected one of `%s` but found `%s` in %s" % (options, found, repr(self.tokenized)))
385 return self.parse_token()
386
387 def parse_token(self, tok = None):
388 found = self.lookahead()
389 if tok is not None and found != tok:
390 raise ValueError("Parsing failed, expected `%s` but found `%s` in %s" % (tok, found, repr(self.tokenized)))
391 self.current += 1
392 return found
393
394 def eof(self):
Adrian Roose5eeae72019-01-04 20:10:06 +0100395 return self.current == self.len
Adrian Roosb787c182019-01-03 18:54:33 +0100396
397 def parse_eof(self):
398 if not self.eof():
399 raise ValueError("Parsing failed, expected EOF, but %s has not been parsed in %s" % (self.tokenized[self.current:], self.tokenized))
400
401 def parse_if(self, tok):
402 if not self.eof() and self.lookahead() == tok:
403 self.parse_token()
404 return True
405 return False
406
407 def parse_annotations(self):
408 ret = []
409 while self.lookahead() == "@":
410 ret.append(self.parse_annotation())
411 return ret
412
413 def parse_annotation(self):
414 ret = self.parse_token("@") + self.parse_token()
415 self.parse_matching_paren("(", ")")
416 return ret
417
418 def parse_matching_paren(self, open, close):
419 start = self.current
420 if not self.parse_if(open):
421 return
422 length = len(self.tokenized)
423 count = 1
424 while count > 0:
425 if self.current == length:
426 raise ValueError("Unexpected EOF looking for closing paren: `%s`" % (self.tokenized[start:],))
427 t = self.parse_token()
428 if t == open:
429 count += 1
430 elif t == close:
431 count -= 1
432 return self.tokenized[start:self.current]
433
434 def parse_modifiers(self):
435 ret = []
436 while self.lookahead() in V2LineParser.MODIFIERS:
437 ret.append(self.parse_token())
438 return ret
439
Adrian Roos5cdfb692019-01-05 22:04:55 +0100440 def parse_kotlin_nullability(self):
441 t = self.lookahead()
442 if t == "?" or t == "!":
443 return self.parse_token()
444 return None
445
Adrian Roosb787c182019-01-03 18:54:33 +0100446 def parse_type(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100447 self.parse_annotations()
Adrian Roosb787c182019-01-03 18:54:33 +0100448 type = self.parse_token()
Adrian Roosd1e38922019-01-14 15:44:15 +0100449 if type[-1] == '.':
450 self.parse_annotations()
451 type += self.parse_token()
Adrian Roosb787c182019-01-03 18:54:33 +0100452 if type in V2LineParser.JAVA_LANG_TYPES:
453 type = "java.lang." + type
454 self.parse_matching_paren("<", ">")
Adrian Roos5cdfb692019-01-05 22:04:55 +0100455 while True:
456 t = self.lookahead()
Adrian Roosd1e38922019-01-14 15:44:15 +0100457 if t == "@":
458 self.parse_annotation()
459 elif t == "[]":
Adrian Roos5cdfb692019-01-05 22:04:55 +0100460 type += self.parse_token()
461 elif self.parse_kotlin_nullability() is not None:
462 pass # discard nullability for now
463 else:
464 break
Adrian Roosb787c182019-01-03 18:54:33 +0100465 return type
466
467 def parse_arg_type(self):
468 type = self.parse_type()
469 if self.parse_if("..."):
470 type += "..."
Adrian Roos5cdfb692019-01-05 22:04:55 +0100471 self.parse_kotlin_nullability() # discard nullability for now
Adrian Roosb787c182019-01-03 18:54:33 +0100472 return type
473
474 def parse_name(self):
475 return self.parse_token()
476
477 def parse_args(self):
478 args = []
479 if self.lookahead() == ")":
480 return args
481
482 while True:
483 args.append(self.parse_arg())
484 if self.lookahead() == ")":
485 return args
486 self.parse_token(",")
487
488 def parse_arg(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100489 self.parse_if("vararg") # kotlin vararg
Adrian Roosb787c182019-01-03 18:54:33 +0100490 self.parse_annotations()
Adrian Roos5cdfb692019-01-05 22:04:55 +0100491 type = self.parse_arg_type()
492 l = self.lookahead()
493 if l != "," and l != ")":
Adrian Roosd1e38922019-01-14 15:44:15 +0100494 if self.lookahead() != '=':
495 self.parse_token() # kotlin argument name
Adrian Roos5cdfb692019-01-05 22:04:55 +0100496 if self.parse_if('='): # kotlin default value
Adrian Roosd1e38922019-01-14 15:44:15 +0100497 self.parse_expression()
Adrian Roos5cdfb692019-01-05 22:04:55 +0100498 return type
Adrian Roosb787c182019-01-03 18:54:33 +0100499
Adrian Roosd1e38922019-01-14 15:44:15 +0100500 def parse_expression(self):
501 while not self.lookahead() in [')', ',', ';']:
502 (self.parse_matching_paren('(', ')') or
503 self.parse_matching_paren('{', '}') or
504 self.parse_token())
505
Adrian Roosb787c182019-01-03 18:54:33 +0100506 def parse_throws(self):
507 ret = []
508 if self.parse_if("throws"):
509 ret.append(self.parse_type())
510 while self.parse_if(","):
511 ret.append(self.parse_type())
512 return ret
513
514 def parse_extends(self):
515 if self.parse_if("extends"):
516 return self.parse_space_delimited_type_list()
517 return []
518
519 def parse_implements(self):
520 if self.parse_if("implements"):
521 return self.parse_space_delimited_type_list()
522 return []
523
524 def parse_space_delimited_type_list(self, terminals = ["implements", "{"]):
525 types = []
526 while True:
527 types.append(self.parse_type())
528 if self.lookahead() in terminals:
529 return types
530
531 def parse_annotation_default(self):
532 if self.parse_if("default"):
Adrian Roosd1e38922019-01-14 15:44:15 +0100533 self.parse_expression()
Adrian Roosb787c182019-01-03 18:54:33 +0100534
535 def parse_value(self):
536 if self.lookahead() == "{":
537 return " ".join(self.parse_matching_paren("{", "}"))
538 elif self.lookahead() == "(":
539 return " ".join(self.parse_matching_paren("(", ")"))
540 else:
541 return self.parse_token()
542
543 def parse_value_stripped(self):
544 value = self.parse_value()
545 if value[0] == '"':
546 return value[1:-1]
547 return value
548
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700549
Adrian Roos038a0292018-12-19 17:11:21 +0100550def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
551 in_classes_with_base=[]):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700552 api = {}
Adrian Roos038a0292018-12-19 17:11:21 +0100553 in_classes_with_base = _retry_iterator(in_classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +0100554
555 if base_f:
Adrian Roos038a0292018-12-19 17:11:21 +0100556 base_classes = _retry_iterator(_parse_stream_to_generator(base_f))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100557 else:
558 base_classes = []
559
Adrian Roos038a0292018-12-19 17:11:21 +0100560 def handle_class(clazz):
Adrian Roos6eb57b02018-12-13 22:08:29 +0100561 if clazz_cb:
562 clazz_cb(clazz)
563 else: # In callback mode, don't keep track of the full API
564 api[clazz.fullname] = clazz
565
Adrian Roos038a0292018-12-19 17:11:21 +0100566 def handle_missed_classes_with_base(clazz):
567 for c in _yield_until_matching_class(in_classes_with_base, clazz):
568 base_class = _skip_to_matching_class(base_classes, c)
569 if base_class:
570 handle_class(base_class)
571
572 for clazz in _parse_stream_to_generator(f):
573 # Before looking at clazz, let's see if there's some classes that were not present, but
574 # may have an entry in the base stream.
575 handle_missed_classes_with_base(clazz)
576
577 base_class = _skip_to_matching_class(base_classes, clazz)
578 if base_class:
579 clazz.merge_from(base_class)
580 if out_classes_with_base is not None:
581 out_classes_with_base.append(clazz)
582 handle_class(clazz)
583
584 handle_missed_classes_with_base(None)
585
Adrian Roos6eb57b02018-12-13 22:08:29 +0100586 return api
587
588def _parse_stream_to_generator(f):
589 line = 0
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700590 pkg = None
591 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700592 blame = None
Adrian Roosb787c182019-01-03 18:54:33 +0100593 sig_format = 1
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700594
595 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Adrian Roos258c5722019-01-21 15:43:15 +0100596
597 field_prefixes = map(lambda kind: " %s" % (kind,), V2LineParser.FIELD_KINDS)
598 def startsWithFieldPrefix(raw):
599 for prefix in field_prefixes:
600 if raw.startswith(prefix):
601 return True
602 return False
603
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800604 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700605 line += 1
606 raw = raw.rstrip()
607 match = re_blame.match(raw)
608 if match is not None:
609 blame = match.groups()[0:2]
610 raw = match.groups()[2]
611 else:
612 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700613
Adrian Rooscf82e042019-01-29 15:01:28 +0100614 if line == 1 and raw.startswith("// Signature format: "):
615 sig_format_string = raw[len(V2Tokenizer.SIGNATURE_PREFIX):]
616 if sig_format_string in ["2.0", "3.0"]:
617 sig_format = 2
618 else:
619 raise ValueError("Unknown format: %s" % (sig_format_string,))
Adrian Roosb787c182019-01-03 18:54:33 +0100620 elif raw.startswith("package"):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700621 pkg = Package(line, raw, blame)
622 elif raw.startswith(" ") and raw.endswith("{"):
Adrian Roosb787c182019-01-03 18:54:33 +0100623 clazz = Class(pkg, line, raw, blame, sig_format=sig_format)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700624 elif raw.startswith(" ctor"):
Adrian Roosb787c182019-01-03 18:54:33 +0100625 clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700626 elif raw.startswith(" method"):
Adrian Roosb787c182019-01-03 18:54:33 +0100627 clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos258c5722019-01-21 15:43:15 +0100628 elif startsWithFieldPrefix(raw):
Adrian Roosb787c182019-01-03 18:54:33 +0100629 clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100630 elif raw.startswith(" }") and clazz:
Adrian Roos038a0292018-12-19 17:11:21 +0100631 yield clazz
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800632
Adrian Roos5ed42b62018-12-19 17:10:22 +0100633def _retry_iterator(it):
634 """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
635 for e in it:
636 while True:
637 retry = yield e
638 if not retry:
639 break
640 # send() was called, asking us to redeliver clazz on next(). Still need to yield
641 # a dummy value to the send() first though.
642 if (yield "Returning clazz on next()"):
643 raise TypeError("send() must be followed by next(), not send()")
644
Adrian Roos038a0292018-12-19 17:11:21 +0100645def _skip_to_matching_class(classes, needle):
646 """Takes a classes iterator and consumes entries until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700647
Adrian Roos6eb57b02018-12-13 22:08:29 +0100648 This relies on classes being sorted by package and class name."""
649
650 for clazz in classes:
651 if clazz.pkg.name < needle.pkg.name:
652 # We haven't reached the right package yet
653 continue
Adrian Roos5ed42b62018-12-19 17:10:22 +0100654 if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
655 # We're in the right package, but not the right class yet
Adrian Roos6eb57b02018-12-13 22:08:29 +0100656 continue
657 if clazz.fullname == needle.fullname:
658 return clazz
659 # We ran past the right class. Send it back into the generator, then report failure.
660 classes.send(clazz)
661 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700662
Adrian Roos038a0292018-12-19 17:11:21 +0100663def _yield_until_matching_class(classes, needle):
664 """Takes a class iterator and yields entries it until it reaches the class we're looking for.
665
666 This relies on classes being sorted by package and class name."""
667
668 for clazz in classes:
669 if needle is None:
670 yield clazz
671 elif clazz.pkg.name < needle.pkg.name:
672 # We haven't reached the right package yet
673 yield clazz
674 elif clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
675 # We're in the right package, but not the right class yet
676 yield clazz
677 elif clazz.fullname == needle.fullname:
678 # Class found, abort.
679 return
680 else:
681 # We ran past the right class. Send it back into the iterator, then abort.
682 classes.send(clazz)
683 return
684
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700685class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800686 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700687 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700688 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800689 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700690 self.msg = msg
691
692 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800693 self.head = "Error %s" % (rule) if rule else "Error"
694 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 -0700695 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800696 self.head = "Warning %s" % (rule) if rule else "Warning"
697 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 -0700698
699 self.line = clazz.line
700 blame = clazz.blame
701 if detail is not None:
702 dump += "\n in " + repr(detail)
703 self.line = detail.line
704 blame = detail.blame
705 dump += "\n in " + repr(clazz)
706 dump += "\n in " + repr(clazz.pkg)
707 dump += "\n at line " + repr(self.line)
708 if blame is not None:
709 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
710
711 self.dump = dump
712
713 def __repr__(self):
714 return self.dump
715
716
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700717failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700718
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800719def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700720 """Records an API failure to be processed later."""
721 global failures
722
Adrian Roosb787c182019-01-03 18:54:33 +0100723 sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700724 sig = sig.replace(" deprecated ", " ")
725
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800726 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700727
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700728
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800729def warn(clazz, detail, rule, msg):
730 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700731
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800732def error(clazz, detail, rule, msg):
733 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700734
735
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700736noticed = {}
737
738def notice(clazz):
739 global noticed
740
741 noticed[clazz.fullname] = hash(clazz)
742
743
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700744def verify_constants(clazz):
745 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700746 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600747 if clazz.fullname.startswith("android.os.Build"): return
748 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700749
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600750 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700751 for f in clazz.fields:
752 if "static" in f.split and "final" in f.split:
753 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800754 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600755 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700756 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
757 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600758 if f.typ in req and f.value is None:
759 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700760
761
762def verify_enums(clazz):
763 """Enums are bad, mmkay?"""
Adrian Roosb787c182019-01-03 18:54:33 +0100764 if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800765 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700766
767
768def verify_class_names(clazz):
769 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700770 if clazz.fullname.startswith("android.opengl"): return
771 if clazz.fullname.startswith("android.renderscript"): return
772 if re.match("android\.R\.[a-z]+", clazz.fullname): return
773
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700774 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800775 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700776 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800777 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700778 if clazz.name.endswith("Impl"):
779 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700780
781
782def verify_method_names(clazz):
783 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700784 if clazz.fullname.startswith("android.opengl"): return
785 if clazz.fullname.startswith("android.renderscript"): return
786 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700787
788 for m in clazz.methods:
789 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800790 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700791 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800792 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700793
794
795def verify_callbacks(clazz):
796 """Verify Callback classes.
797 All callback classes must be abstract.
798 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700799 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700800
801 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800802 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700803 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800804 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700805
806 if clazz.name.endswith("Callback"):
807 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800808 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700809
810 for m in clazz.methods:
811 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800812 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700813
814
815def verify_listeners(clazz):
816 """Verify Listener classes.
817 All Listener classes must be interface.
818 All methods must follow onFoo() naming style.
819 If only a single method, it must match class name:
820 interface OnFooListener { void onFoo() }"""
821
822 if clazz.name.endswith("Listener"):
Adrian Roosb787c182019-01-03 18:54:33 +0100823 if "abstract" in clazz.split and "class" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800824 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700825
826 for m in clazz.methods:
827 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800828 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700829
830 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
831 m = clazz.methods[0]
832 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800833 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700834
835
836def verify_actions(clazz):
837 """Verify intent actions.
838 All action names must be named ACTION_FOO.
839 All action values must be scoped by package and match name:
840 package android.foo {
841 String ACTION_BAR = "android.foo.action.BAR";
842 }"""
843 for f in clazz.fields:
844 if f.value is None: continue
845 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700846 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600847 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700848
849 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
850 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
851 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800852 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700853 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700854 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700855 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700856 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700857 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700858 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
859 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700860 else:
861 prefix = clazz.pkg.name + ".action"
862 expected = prefix + "." + f.name[7:]
863 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700864 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700865
866
867def verify_extras(clazz):
868 """Verify intent extras.
869 All extra names must be named EXTRA_FOO.
870 All extra values must be scoped by package and match name:
871 package android.foo {
872 String EXTRA_BAR = "android.foo.extra.BAR";
873 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700874 if clazz.fullname == "android.app.Notification": return
875 if clazz.fullname == "android.appwidget.AppWidgetManager": return
876
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700877 for f in clazz.fields:
878 if f.value is None: continue
879 if f.name.startswith("ACTION_"): continue
880
881 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
882 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
883 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800884 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700885 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700886 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700887 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700888 elif clazz.pkg.name == "android.app.admin":
889 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700890 else:
891 prefix = clazz.pkg.name + ".extra"
892 expected = prefix + "." + f.name[6:]
893 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700894 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700895
896
897def verify_equals(clazz):
898 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700899 eq = False
900 hc = False
901 for m in clazz.methods:
Adrian Roosb787c182019-01-03 18:54:33 +0100902 if "static" in m.split: continue
903 if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
904 if m.sig_matches("int", "hashCode", []): hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700905 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800906 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700907
908
909def verify_parcelable(clazz):
910 """Verify that Parcelable objects aren't hiding required bits."""
Adrian Roosb787c182019-01-03 18:54:33 +0100911 if clazz.implements == "android.os.Parcelable":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700912 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
913 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
914 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
915
916 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800917 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700918
Adrian Roosb787c182019-01-03 18:54:33 +0100919 if "final" not in clazz.split:
Jeff Sharkey331279b2016-02-29 16:02:02 -0700920 error(clazz, None, "FW8", "Parcelable classes must be final")
921
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700922 for c in clazz.ctors:
923 if c.args == ["android.os.Parcel"]:
924 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
925
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700926
927def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800928 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700929 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600930 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700931 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800932 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700933 for f in clazz.fields:
934 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800935 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700936
937
938def verify_fields(clazz):
939 """Verify that all exposed fields are final.
940 Exposed fields must follow myName style.
941 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700942
943 IGNORE_BARE_FIELDS = [
944 "android.app.ActivityManager.RecentTaskInfo",
945 "android.app.Notification",
946 "android.content.pm.ActivityInfo",
947 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600948 "android.content.pm.ComponentInfo",
949 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700950 "android.content.pm.FeatureGroupInfo",
951 "android.content.pm.InstrumentationInfo",
952 "android.content.pm.PackageInfo",
953 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600954 "android.content.res.Configuration",
955 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700956 "android.os.Message",
957 "android.system.StructPollfd",
958 ]
959
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700960 for f in clazz.fields:
961 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700962 if clazz.fullname in IGNORE_BARE_FIELDS:
963 pass
964 elif clazz.fullname.endswith("LayoutParams"):
965 pass
966 elif clazz.fullname.startswith("android.util.Mutable"):
967 pass
968 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800969 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700970
Adrian Roosd1e38922019-01-14 15:44:15 +0100971 if "static" not in f.split and "property" not in f.split:
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700972 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800973 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700974
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700975 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800976 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700977
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700978 if re.match("[A-Z_]+", f.name):
979 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800980 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700981
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700982
983def verify_register(clazz):
984 """Verify parity of registration methods.
985 Callback objects use register/unregister methods.
986 Listener objects use add/remove methods."""
987 methods = [ m.name for m in clazz.methods ]
988 for m in clazz.methods:
989 if "Callback" in m.raw:
990 if m.name.startswith("register"):
991 other = "unregister" + m.name[8:]
992 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800993 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700994 if m.name.startswith("unregister"):
995 other = "register" + m.name[10:]
996 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800997 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700998
999 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001000 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001001
1002 if "Listener" in m.raw:
1003 if m.name.startswith("add"):
1004 other = "remove" + m.name[3:]
1005 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001006 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001007 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
1008 other = "add" + m.name[6:]
1009 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001010 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001011
1012 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001013 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001014
1015
1016def verify_sync(clazz):
1017 """Verify synchronized methods aren't exposed."""
1018 for m in clazz.methods:
1019 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001020 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001021
1022
1023def verify_intent_builder(clazz):
1024 """Verify that Intent builders are createFooIntent() style."""
1025 if clazz.name == "Intent": return
1026
1027 for m in clazz.methods:
1028 if m.typ == "android.content.Intent":
1029 if m.name.startswith("create") and m.name.endswith("Intent"):
1030 pass
1031 else:
Adam Powell539ea122015-04-10 13:01:37 -07001032 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001033
1034
1035def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001036 """Verify that helper classes are named consistently with what they extend.
1037 All developer extendable methods should be named onFoo()."""
1038 test_methods = False
Adrian Roosb787c182019-01-03 18:54:33 +01001039 if clazz.extends == "android.app.Service":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001040 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001041 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001042 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001043
1044 found = False
1045 for f in clazz.fields:
1046 if f.name == "SERVICE_INTERFACE":
1047 found = True
1048 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001049 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001050
Adrian Roosb787c182019-01-03 18:54:33 +01001051 if clazz.extends == "android.content.ContentProvider":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001052 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001053 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001054 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001055
1056 found = False
1057 for f in clazz.fields:
1058 if f.name == "PROVIDER_INTERFACE":
1059 found = True
1060 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001061 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001062
Adrian Roosb787c182019-01-03 18:54:33 +01001063 if clazz.extends == "android.content.BroadcastReceiver":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001064 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001065 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001066 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001067
Adrian Roosb787c182019-01-03 18:54:33 +01001068 if clazz.extends == "android.app.Activity":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001069 test_methods = True
1070 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001071 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001072
1073 if test_methods:
1074 for m in clazz.methods:
1075 if "final" in m.split: continue
1076 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001077 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001078 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001079 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001080 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001081
1082
1083def verify_builder(clazz):
1084 """Verify builder classes.
1085 Methods should return the builder to enable chaining."""
Adrian Roosb787c182019-01-03 18:54:33 +01001086 if clazz.extends: return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001087 if not clazz.name.endswith("Builder"): return
1088
1089 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001090 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001091
1092 has_build = False
1093 for m in clazz.methods:
1094 if m.name == "build":
1095 has_build = True
1096 continue
1097
1098 if m.name.startswith("get"): continue
1099 if m.name.startswith("clear"): continue
1100
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001101 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001102 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001103
1104 if m.name.startswith("set"):
1105 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001106 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001107
1108 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001109 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001110
1111
1112def verify_aidl(clazz):
1113 """Catch people exposing raw AIDL."""
Adrian Roosb787c182019-01-03 18:54:33 +01001114 if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001115 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001116
1117
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001118def verify_internal(clazz):
1119 """Catch people exposing internal classes."""
1120 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001121 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001122
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001123def layering_build_ranking(ranking_list):
1124 r = {}
1125 for rank, ps in enumerate(ranking_list):
1126 if not isinstance(ps, list):
1127 ps = [ps]
1128 for p in ps:
1129 rs = r
1130 for n in p.split('.'):
1131 if n not in rs:
1132 rs[n] = {}
1133 rs = rs[n]
1134 rs['-rank'] = rank
1135 return r
1136
1137LAYERING_PACKAGE_RANKING = layering_build_ranking([
1138 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
1139 "android.app",
1140 "android.widget",
1141 "android.view",
1142 "android.animation",
1143 "android.provider",
1144 ["android.content","android.graphics.drawable"],
1145 "android.database",
1146 "android.text",
1147 "android.graphics",
1148 "android.os",
1149 "android.util"
1150])
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001151
1152def verify_layering(clazz):
1153 """Catch package layering violations.
1154 For example, something in android.os depending on android.app."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001155
1156 def rank(p):
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001157 r = None
1158 l = LAYERING_PACKAGE_RANKING
1159 for n in p.split('.'):
1160 if n in l:
1161 l = l[n]
1162 if '-rank' in l:
1163 r = l['-rank']
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001164 else:
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001165 break
1166 return r
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001167
1168 cr = rank(clazz.pkg.name)
1169 if cr is None: return
1170
1171 for f in clazz.fields:
1172 ir = rank(f.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001173 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001174 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001175
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001176 for m in itertools.chain(clazz.methods, clazz.ctors):
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001177 ir = rank(m.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001178 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001179 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001180 for arg in m.args:
1181 ir = rank(arg)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001182 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001183 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001184
1185
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001186def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001187 """Verifies that boolean accessors are named correctly.
1188 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001189
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001190 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
1191 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001192
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001193 gets = [ m for m in clazz.methods if is_get(m) ]
1194 sets = [ m for m in clazz.methods if is_set(m) ]
1195
1196 def error_if_exists(methods, trigger, expected, actual):
1197 for m in methods:
1198 if m.name == actual:
1199 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001200
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001201 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001202 if is_get(m):
1203 if re.match("is[A-Z]", m.name):
1204 target = m.name[2:]
1205 expected = "setIs" + target
1206 error_if_exists(sets, m.name, expected, "setHas" + target)
1207 elif re.match("has[A-Z]", m.name):
1208 target = m.name[3:]
1209 expected = "setHas" + target
1210 error_if_exists(sets, m.name, expected, "setIs" + target)
1211 error_if_exists(sets, m.name, expected, "set" + target)
1212 elif re.match("get[A-Z]", m.name):
1213 target = m.name[3:]
1214 expected = "set" + target
1215 error_if_exists(sets, m.name, expected, "setIs" + target)
1216 error_if_exists(sets, m.name, expected, "setHas" + target)
1217
1218 if is_set(m):
1219 if re.match("set[A-Z]", m.name):
1220 target = m.name[3:]
1221 expected = "get" + target
1222 error_if_exists(sets, m.name, expected, "is" + target)
1223 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001224
1225
1226def verify_collections(clazz):
1227 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001228 if clazz.fullname == "android.os.Bundle": return
1229
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001230 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
1231 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
1232 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001233 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001234 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001235 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001236 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001237 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001238
1239
1240def verify_flags(clazz):
1241 """Verifies that flags are non-overlapping."""
1242 known = collections.defaultdict(int)
1243 for f in clazz.fields:
1244 if "FLAG_" in f.name:
1245 try:
1246 val = int(f.value)
1247 except:
1248 continue
1249
1250 scope = f.name[0:f.name.index("FLAG_")]
1251 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001252 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001253 known[scope] |= val
1254
1255
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001256def verify_exception(clazz):
1257 """Verifies that methods don't throw generic exceptions."""
1258 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001259 for t in m.throws:
1260 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
1261 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001262
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001263 if t in ["android.os.RemoteException"]:
1264 if clazz.name == "android.content.ContentProviderClient": continue
1265 if clazz.name == "android.os.Binder": continue
1266 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -07001267
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001268 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
1269
1270 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
1271 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -07001272
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001273GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001274
1275def verify_google(clazz):
1276 """Verifies that APIs never reference Google."""
1277
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001278 if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001279 error(clazz, None, None, "Must never reference Google")
1280
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001281 for test in clazz.ctors, clazz.fields, clazz.methods:
1282 for t in test:
1283 if GOOGLE_IGNORECASE.search(t.raw) is not None:
1284 error(clazz, t, None, "Must never reference Google")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001285
1286
1287def verify_bitset(clazz):
1288 """Verifies that we avoid using heavy BitSet."""
1289
1290 for f in clazz.fields:
1291 if f.typ == "java.util.BitSet":
1292 error(clazz, f, None, "Field type must not be heavy BitSet")
1293
1294 for m in clazz.methods:
1295 if m.typ == "java.util.BitSet":
1296 error(clazz, m, None, "Return type must not be heavy BitSet")
1297 for arg in m.args:
1298 if arg == "java.util.BitSet":
1299 error(clazz, m, None, "Argument type must not be heavy BitSet")
1300
1301
1302def verify_manager(clazz):
1303 """Verifies that FooManager is only obtained from Context."""
1304
1305 if not clazz.name.endswith("Manager"): return
1306
1307 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001308 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001309
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001310 for m in clazz.methods:
1311 if m.typ == clazz.fullname:
1312 error(clazz, m, None, "Managers must always be obtained from Context")
1313
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001314
1315def verify_boxed(clazz):
1316 """Verifies that methods avoid boxed primitives."""
1317
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001318 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 -08001319
1320 for c in clazz.ctors:
1321 for arg in c.args:
1322 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001323 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001324
1325 for f in clazz.fields:
1326 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001327 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001328
1329 for m in clazz.methods:
1330 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001331 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001332 for arg in m.args:
1333 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001334 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001335
1336
1337def verify_static_utils(clazz):
1338 """Verifies that helper classes can't be constructed."""
1339 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001340 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001341
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001342 # Only care about classes with default constructors
1343 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
1344 test = []
1345 test.extend(clazz.fields)
1346 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001347
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001348 if len(test) == 0: return
1349 for t in test:
1350 if "static" not in t.split:
1351 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001352
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001353 error(clazz, None, None, "Fully-static utility classes must not have constructor")
1354
1355
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001356def verify_overload_args(clazz):
1357 """Verifies that method overloads add new arguments at the end."""
1358 if clazz.fullname.startswith("android.opengl"): return
1359
1360 overloads = collections.defaultdict(list)
1361 for m in clazz.methods:
1362 if "deprecated" in m.split: continue
1363 overloads[m.name].append(m)
1364
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001365 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001366 if len(methods) <= 1: continue
1367
1368 # Look for arguments common across all overloads
1369 def cluster(args):
1370 count = collections.defaultdict(int)
1371 res = set()
1372 for i in range(len(args)):
1373 a = args[i]
1374 res.add("%s#%d" % (a, count[a]))
1375 count[a] += 1
1376 return res
1377
1378 common_args = cluster(methods[0].args)
1379 for m in methods:
1380 common_args = common_args & cluster(m.args)
1381
1382 if len(common_args) == 0: continue
1383
1384 # Require that all common arguments are present at start of signature
1385 locked_sig = None
1386 for m in methods:
1387 sig = m.args[0:len(common_args)]
1388 if not common_args.issubset(cluster(sig)):
1389 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1390 elif not locked_sig:
1391 locked_sig = sig
1392 elif locked_sig != sig:
1393 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1394
1395
1396def verify_callback_handlers(clazz):
1397 """Verifies that methods adding listener/callback have overload
1398 for specifying delivery thread."""
1399
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001400 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001401 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001402 "animation",
1403 "view",
1404 "graphics",
1405 "transition",
1406 "widget",
1407 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001408 ]
1409 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001410 if s in clazz.pkg.name_path: return
1411 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001412
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001413 # Ignore UI classes which assume main thread
1414 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1415 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1416 if s in clazz.fullname: return
1417 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1418 for s in ["Loader"]:
1419 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001420
1421 found = {}
1422 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001423 examine = clazz.ctors + clazz.methods
1424 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001425 if m.name.startswith("unregister"): continue
1426 if m.name.startswith("remove"): continue
1427 if re.match("on[A-Z]+", m.name): continue
1428
1429 by_name[m.name].append(m)
1430
1431 for a in m.args:
1432 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1433 found[m.name] = m
1434
1435 for f in found.values():
1436 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001437 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001438 for m in by_name[f.name]:
1439 if "android.os.Handler" in m.args:
1440 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001441 if "java.util.concurrent.Executor" in m.args:
1442 takes_exec = True
1443 if not takes_exec:
1444 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001445
1446
1447def verify_context_first(clazz):
1448 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001449 examine = clazz.ctors + clazz.methods
1450 for m in examine:
1451 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001452 if "android.content.Context" in m.args[1:]:
1453 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001454 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1455 if "android.content.ContentResolver" in m.args[1:]:
1456 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001457
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001458
1459def verify_listener_last(clazz):
1460 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1461 examine = clazz.ctors + clazz.methods
1462 for m in examine:
1463 if "Listener" in m.name or "Callback" in m.name: continue
1464 found = False
1465 for a in m.args:
1466 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1467 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001468 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001469 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1470
1471
1472def verify_resource_names(clazz):
1473 """Verifies that resource names have consistent case."""
1474 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1475
1476 # Resources defined by files are foo_bar_baz
1477 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1478 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001479 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1480 if f.name.startswith("config_"):
1481 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1482
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001483 if re.match("[a-z1-9_]+$", f.name): continue
1484 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1485
1486 # Resources defined inside files are fooBarBaz
1487 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1488 for f in clazz.fields:
1489 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1490 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1491 if re.match("state_[a-z_]*$", f.name): continue
1492
1493 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1494 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1495
1496 # Styles are FooBar_Baz
1497 if clazz.name in ["style"]:
1498 for f in clazz.fields:
1499 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1500 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001501
1502
Jeff Sharkey331279b2016-02-29 16:02:02 -07001503def verify_files(clazz):
1504 """Verifies that methods accepting File also accept streams."""
1505
1506 has_file = set()
1507 has_stream = set()
1508
1509 test = []
1510 test.extend(clazz.ctors)
1511 test.extend(clazz.methods)
1512
1513 for m in test:
1514 if "java.io.File" in m.args:
1515 has_file.add(m)
1516 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:
1517 has_stream.add(m.name)
1518
1519 for m in has_file:
1520 if m.name not in has_stream:
1521 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1522
1523
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001524def verify_manager_list(clazz):
1525 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1526
1527 if not clazz.name.endswith("Manager"): return
1528
1529 for m in clazz.methods:
1530 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1531 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1532
1533
Jeff Sharkey26c80902016-12-21 13:41:17 -07001534def verify_abstract_inner(clazz):
1535 """Verifies that abstract inner classes are static."""
1536
1537 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
Adrian Roosb787c182019-01-03 18:54:33 +01001538 if "abstract" in clazz.split and "static" not in clazz.split:
Jeff Sharkey26c80902016-12-21 13:41:17 -07001539 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1540
1541
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001542def verify_runtime_exceptions(clazz):
1543 """Verifies that runtime exceptions aren't listed in throws."""
1544
1545 banned = [
1546 "java.lang.NullPointerException",
1547 "java.lang.ClassCastException",
1548 "java.lang.IndexOutOfBoundsException",
1549 "java.lang.reflect.UndeclaredThrowableException",
1550 "java.lang.reflect.MalformedParametersException",
1551 "java.lang.reflect.MalformedParameterizedTypeException",
1552 "java.lang.invoke.WrongMethodTypeException",
1553 "java.lang.EnumConstantNotPresentException",
1554 "java.lang.IllegalMonitorStateException",
1555 "java.lang.SecurityException",
1556 "java.lang.UnsupportedOperationException",
1557 "java.lang.annotation.AnnotationTypeMismatchException",
1558 "java.lang.annotation.IncompleteAnnotationException",
1559 "java.lang.TypeNotPresentException",
1560 "java.lang.IllegalStateException",
1561 "java.lang.ArithmeticException",
1562 "java.lang.IllegalArgumentException",
1563 "java.lang.ArrayStoreException",
1564 "java.lang.NegativeArraySizeException",
1565 "java.util.MissingResourceException",
1566 "java.util.EmptyStackException",
1567 "java.util.concurrent.CompletionException",
1568 "java.util.concurrent.RejectedExecutionException",
1569 "java.util.IllformedLocaleException",
1570 "java.util.ConcurrentModificationException",
1571 "java.util.NoSuchElementException",
1572 "java.io.UncheckedIOException",
1573 "java.time.DateTimeException",
1574 "java.security.ProviderException",
1575 "java.nio.BufferUnderflowException",
1576 "java.nio.BufferOverflowException",
1577 ]
1578
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001579 examine = clazz.ctors + clazz.methods
1580 for m in examine:
1581 for t in m.throws:
1582 if t in banned:
1583 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001584
1585
1586def verify_error(clazz):
1587 """Verifies that we always use Exception instead of Error."""
1588 if not clazz.extends: return
1589 if clazz.extends.endswith("Error"):
1590 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1591 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1592 error(clazz, None, None, "Exceptions must be named FooException")
1593
1594
1595def verify_units(clazz):
1596 """Verifies that we use consistent naming for units."""
1597
1598 # If we find K, recommend replacing with V
1599 bad = {
1600 "Ns": "Nanos",
1601 "Ms": "Millis or Micros",
1602 "Sec": "Seconds", "Secs": "Seconds",
1603 "Hr": "Hours", "Hrs": "Hours",
1604 "Mo": "Months", "Mos": "Months",
1605 "Yr": "Years", "Yrs": "Years",
1606 "Byte": "Bytes", "Space": "Bytes",
1607 }
1608
1609 for m in clazz.methods:
1610 if m.typ not in ["short","int","long"]: continue
1611 for k, v in bad.iteritems():
1612 if m.name.endswith(k):
1613 error(clazz, m, None, "Expected method name units to be " + v)
1614 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1615 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1616 if m.name.endswith("Seconds"):
1617 error(clazz, m, None, "Returned time values must be in milliseconds")
1618
1619 for m in clazz.methods:
1620 typ = m.typ
1621 if typ == "void":
1622 if len(m.args) != 1: continue
1623 typ = m.args[0]
1624
1625 if m.name.endswith("Fraction") and typ != "float":
1626 error(clazz, m, None, "Fractions must use floats")
1627 if m.name.endswith("Percentage") and typ != "int":
1628 error(clazz, m, None, "Percentage must use ints")
1629
1630
1631def verify_closable(clazz):
1632 """Verifies that classes are AutoClosable."""
Adrian Roosb787c182019-01-03 18:54:33 +01001633 if clazz.implements == "java.lang.AutoCloseable": return
1634 if clazz.implements == "java.io.Closeable": return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001635
1636 for m in clazz.methods:
1637 if len(m.args) > 0: continue
1638 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1639 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1640 return
1641
1642
Jake Wharton9e6738f2017-08-23 11:59:55 -04001643def verify_member_name_not_kotlin_keyword(clazz):
1644 """Prevent method names which are keywords in Kotlin."""
1645
1646 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1647 # This list does not include Java keywords as those are already impossible to use.
1648 keywords = [
1649 'as',
1650 'fun',
1651 'in',
1652 'is',
1653 'object',
1654 'typealias',
1655 'val',
1656 'var',
1657 'when',
1658 ]
1659
1660 for m in clazz.methods:
1661 if m.name in keywords:
1662 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1663 for f in clazz.fields:
1664 if f.name in keywords:
1665 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1666
1667
1668def verify_method_name_not_kotlin_operator(clazz):
1669 """Warn about method names which become operators in Kotlin."""
1670
1671 binary = set()
1672
1673 def unique_binary_op(m, op):
1674 if op in binary:
1675 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1676 binary.add(op)
1677
1678 for m in clazz.methods:
Adrian Roosd1e38922019-01-14 15:44:15 +01001679 if 'static' in m.split or 'operator' in m.split:
Jake Wharton9e6738f2017-08-23 11:59:55 -04001680 continue
1681
1682 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1683 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1684 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1685
1686 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1687 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1688 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1689 # practical way of checking that relationship here.
1690 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1691
1692 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1693 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1694 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1695 unique_binary_op(m, m.name)
1696
1697 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1698 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1699 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1700
1701 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1702 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1703 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1704
1705 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1706 if m.name == 'invoke':
1707 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1708
1709 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1710 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1711 and len(m.args) == 1 \
1712 and m.typ == 'void':
1713 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1714 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1715
1716
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001717def verify_collections_over_arrays(clazz):
1718 """Warn that [] should be Collections."""
1719
Adrian Roosb787c182019-01-03 18:54:33 +01001720 if "@interface" in clazz.split:
1721 return
1722
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001723 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1724 for m in clazz.methods:
1725 if m.typ.endswith("[]") and m.typ not in safe:
1726 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1727 for arg in m.args:
1728 if arg.endswith("[]") and arg not in safe:
1729 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1730
1731
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001732def verify_user_handle(clazz):
1733 """Methods taking UserHandle should be ForUser or AsUser."""
1734 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1735 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1736 if clazz.fullname == "android.content.pm.LauncherApps": return
1737 if clazz.fullname == "android.os.UserHandle": return
1738 if clazz.fullname == "android.os.UserManager": return
1739
1740 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001741 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001742
1743 has_arg = "android.os.UserHandle" in m.args
1744 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1745
1746 if clazz.fullname.endswith("Manager") and has_arg:
1747 warn(clazz, m, None, "When a method overload is needed to target a specific "
1748 "UserHandle, callers should be directed to use "
1749 "Context.createPackageContextAsUser() and re-obtain the relevant "
1750 "Manager, and no new API should be added")
1751 elif has_arg and not has_name:
1752 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1753 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001754
1755
1756def verify_params(clazz):
1757 """Parameter classes should be 'Params'."""
1758 if clazz.name.endswith("Params"): return
1759 if clazz.fullname == "android.app.ActivityOptions": return
1760 if clazz.fullname == "android.app.BroadcastOptions": return
1761 if clazz.fullname == "android.os.Bundle": return
1762 if clazz.fullname == "android.os.BaseBundle": return
1763 if clazz.fullname == "android.os.PersistableBundle": return
1764
1765 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1766 for b in bad:
1767 if clazz.name.endswith(b):
1768 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1769
1770
1771def verify_services(clazz):
1772 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1773 if clazz.fullname != "android.content.Context": return
1774
1775 for f in clazz.fields:
1776 if f.typ != "java.lang.String": continue
1777 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1778 if found:
1779 expected = found.group(1).lower()
1780 if f.value != expected:
1781 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1782
1783
1784def verify_tense(clazz):
1785 """Verify tenses of method names."""
1786 if clazz.fullname.startswith("android.opengl"): return
1787
1788 for m in clazz.methods:
1789 if m.name.endswith("Enable"):
1790 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1791
1792
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001793def verify_icu(clazz):
1794 """Verifies that richer ICU replacements are used."""
1795 better = {
1796 "java.util.TimeZone": "android.icu.util.TimeZone",
1797 "java.util.Calendar": "android.icu.util.Calendar",
1798 "java.util.Locale": "android.icu.util.ULocale",
1799 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1800 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1801 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1802 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1803 "java.lang.Character": "android.icu.lang.UCharacter",
1804 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1805 "java.text.Collator": "android.icu.text.Collator",
1806 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1807 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1808 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1809 "java.text.DateFormat": "android.icu.text.DateFormat",
1810 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1811 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1812 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1813 }
1814
1815 for m in clazz.ctors + clazz.methods:
1816 types = []
1817 types.extend(m.typ)
1818 types.extend(m.args)
1819 for arg in types:
1820 if arg in better:
1821 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1822
1823
1824def verify_clone(clazz):
1825 """Verify that clone() isn't implemented; see EJ page 61."""
1826 for m in clazz.methods:
1827 if m.name == "clone":
1828 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1829
1830
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001831def verify_pfd(clazz):
1832 """Verify that android APIs use PFD over FD."""
1833 examine = clazz.ctors + clazz.methods
1834 for m in examine:
1835 if m.typ == "java.io.FileDescriptor":
1836 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1837 if m.typ == "int":
1838 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1839 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1840 for arg in m.args:
1841 if arg == "java.io.FileDescriptor":
1842 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1843
1844 for f in clazz.fields:
1845 if f.typ == "java.io.FileDescriptor":
1846 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1847
1848
1849def verify_numbers(clazz):
1850 """Discourage small numbers types like short and byte."""
1851
1852 discouraged = ["short","byte"]
1853
1854 for c in clazz.ctors:
1855 for arg in c.args:
1856 if arg in discouraged:
1857 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1858
1859 for f in clazz.fields:
1860 if f.typ in discouraged:
1861 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1862
1863 for m in clazz.methods:
1864 if m.typ in discouraged:
1865 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1866 for arg in m.args:
1867 if arg in discouraged:
1868 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1869
1870
1871def verify_singleton(clazz):
1872 """Catch singleton objects with constructors."""
1873
1874 singleton = False
1875 for m in clazz.methods:
1876 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1877 singleton = True
1878
1879 if singleton:
1880 for c in clazz.ctors:
1881 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1882
1883
1884
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001885def is_interesting(clazz):
1886 """Test if given class is interesting from an Android PoV."""
1887
1888 if clazz.pkg.name.startswith("java"): return False
1889 if clazz.pkg.name.startswith("junit"): return False
1890 if clazz.pkg.name.startswith("org.apache"): return False
1891 if clazz.pkg.name.startswith("org.xml"): return False
1892 if clazz.pkg.name.startswith("org.json"): return False
1893 if clazz.pkg.name.startswith("org.w3c"): return False
1894 if clazz.pkg.name.startswith("android.icu."): return False
1895 return True
1896
1897
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001898def examine_clazz(clazz):
1899 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001900
1901 notice(clazz)
1902
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001903 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001904
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001905 verify_constants(clazz)
1906 verify_enums(clazz)
1907 verify_class_names(clazz)
1908 verify_method_names(clazz)
1909 verify_callbacks(clazz)
1910 verify_listeners(clazz)
1911 verify_actions(clazz)
1912 verify_extras(clazz)
1913 verify_equals(clazz)
1914 verify_parcelable(clazz)
1915 verify_protected(clazz)
1916 verify_fields(clazz)
1917 verify_register(clazz)
1918 verify_sync(clazz)
1919 verify_intent_builder(clazz)
1920 verify_helper_classes(clazz)
1921 verify_builder(clazz)
1922 verify_aidl(clazz)
1923 verify_internal(clazz)
1924 verify_layering(clazz)
1925 verify_boolean(clazz)
1926 verify_collections(clazz)
1927 verify_flags(clazz)
1928 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001929 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001930 verify_bitset(clazz)
1931 verify_manager(clazz)
1932 verify_boxed(clazz)
1933 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001934 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001935 verify_callback_handlers(clazz)
1936 verify_context_first(clazz)
1937 verify_listener_last(clazz)
1938 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001939 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001940 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001941 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001942 verify_runtime_exceptions(clazz)
1943 verify_error(clazz)
1944 verify_units(clazz)
1945 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001946 verify_member_name_not_kotlin_keyword(clazz)
1947 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001948 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001949 verify_user_handle(clazz)
1950 verify_params(clazz)
1951 verify_services(clazz)
1952 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001953 verify_icu(clazz)
1954 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001955 verify_pfd(clazz)
1956 verify_numbers(clazz)
1957 verify_singleton(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001958
1959
Adrian Roos038a0292018-12-19 17:11:21 +01001960def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001961 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001962 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001963 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001964 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01001965 _parse_stream(stream, examine_clazz, base_f=base_stream,
1966 in_classes_with_base=in_classes_with_base,
1967 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001968 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001969
1970
1971def examine_api(api):
1972 """Find all style issues in the given parsed API."""
1973 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001974 failures = {}
1975 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001976 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001977 return failures
1978
1979
Jeff Sharkey037458a2014-09-04 15:46:20 -07001980def verify_compat(cur, prev):
1981 """Find any incompatible API changes between two levels."""
1982 global failures
1983
1984 def class_exists(api, test):
1985 return test.fullname in api
1986
1987 def ctor_exists(api, clazz, test):
1988 for m in clazz.ctors:
1989 if m.ident == test.ident: return True
1990 return False
1991
1992 def all_methods(api, clazz):
1993 methods = list(clazz.methods)
1994 if clazz.extends is not None:
1995 methods.extend(all_methods(api, api[clazz.extends]))
1996 return methods
1997
1998 def method_exists(api, clazz, test):
1999 methods = all_methods(api, clazz)
2000 for m in methods:
2001 if m.ident == test.ident: return True
2002 return False
2003
2004 def field_exists(api, clazz, test):
2005 for f in clazz.fields:
2006 if f.ident == test.ident: return True
2007 return False
2008
2009 failures = {}
2010 for key in sorted(prev.keys()):
2011 prev_clazz = prev[key]
2012
2013 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002014 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002015 continue
2016
2017 cur_clazz = cur[key]
2018
2019 for test in prev_clazz.ctors:
2020 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002021 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002022
2023 methods = all_methods(prev, prev_clazz)
2024 for test in methods:
2025 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002026 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002027
2028 for test in prev_clazz.fields:
2029 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002030 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002031
2032 return failures
2033
2034
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002035def show_deprecations_at_birth(cur, prev):
2036 """Show API deprecations at birth."""
2037 global failures
2038
2039 # Remove all existing things so we're left with new
2040 for prev_clazz in prev.values():
2041 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002042 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002043
2044 sigs = { i.ident: i for i in prev_clazz.ctors }
2045 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
2046 sigs = { i.ident: i for i in prev_clazz.methods }
2047 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
2048 sigs = { i.ident: i for i in prev_clazz.fields }
2049 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
2050
2051 # Forget about class entirely when nothing new
2052 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
2053 del cur[prev_clazz.fullname]
2054
2055 for clazz in cur.values():
Adrian Roosb787c182019-01-03 18:54:33 +01002056 if "deprecated" in clazz.split and not clazz.fullname in prev:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002057 error(clazz, None, None, "Found API deprecation at birth")
2058
2059 for i in clazz.ctors + clazz.methods + clazz.fields:
Adrian Roosb787c182019-01-03 18:54:33 +01002060 if "deprecated" in i.split:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002061 error(clazz, i, None, "Found API deprecation at birth")
2062
2063 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
2064 format(reset=True)))
2065 for f in sorted(failures):
2066 print failures[f]
2067 print
2068
2069
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002070def show_stats(cur, prev):
2071 """Show API stats."""
2072
2073 stats = collections.defaultdict(int)
2074 for cur_clazz in cur.values():
2075 if not is_interesting(cur_clazz): continue
2076
2077 if cur_clazz.fullname not in prev:
2078 stats['new_classes'] += 1
2079 stats['new_ctors'] += len(cur_clazz.ctors)
2080 stats['new_methods'] += len(cur_clazz.methods)
2081 stats['new_fields'] += len(cur_clazz.fields)
2082 else:
2083 prev_clazz = prev[cur_clazz.fullname]
2084
2085 sigs = { i.ident: i for i in prev_clazz.ctors }
2086 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
2087 sigs = { i.ident: i for i in prev_clazz.methods }
2088 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
2089 sigs = { i.ident: i for i in prev_clazz.fields }
2090 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
2091
2092 if ctors + methods + fields > 0:
2093 stats['extend_classes'] += 1
2094 stats['extend_ctors'] += ctors
2095 stats['extend_methods'] += methods
2096 stats['extend_fields'] += fields
2097
2098 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
2099 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
2100
2101
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002102if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002103 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
2104 patterns. It ignores lint messages from a previous API level, if provided.")
2105 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
2106 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
2107 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01002108 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
2109 help="The base current.txt to use when examining system-current.txt or"
2110 " test-current.txt")
2111 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
2112 help="The base previous.txt to use when examining system-previous.txt or"
2113 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002114 parser.add_argument("--no-color", action='store_const', const=True,
2115 help="Disable terminal colors")
2116 parser.add_argument("--allow-google", action='store_const', const=True,
2117 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002118 parser.add_argument("--show-noticed", action='store_const', const=True,
2119 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002120 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
2121 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002122 parser.add_argument("--show-stats", action='store_const', const=True,
2123 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002124 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002125
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002126 if args['no_color']:
2127 USE_COLOR = False
2128
2129 if args['allow_google']:
2130 ALLOW_GOOGLE = True
2131
2132 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002133 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002134 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002135 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002136
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002137 if args['show_deprecations_at_birth']:
2138 with current_file as f:
2139 cur = _parse_stream(f)
2140 with previous_file as f:
2141 prev = _parse_stream(f)
2142 show_deprecations_at_birth(cur, prev)
2143 sys.exit()
2144
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002145 if args['show_stats']:
2146 with current_file as f:
2147 cur = _parse_stream(f)
2148 with previous_file as f:
2149 prev = _parse_stream(f)
2150 show_stats(cur, prev)
2151 sys.exit()
2152
Adrian Roos038a0292018-12-19 17:11:21 +01002153 classes_with_base = []
2154
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002155 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002156 if base_current_file:
2157 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002158 cur_fail, cur_noticed = examine_stream(f, base_f,
2159 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002160 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002161 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
2162
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002163 if not previous_file is None:
2164 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002165 if base_previous_file:
2166 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002167 prev_fail, prev_noticed = examine_stream(f, base_f,
2168 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002169 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002170 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002171
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002172 # ignore errors from previous API level
2173 for p in prev_fail:
2174 if p in cur_fail:
2175 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002176
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002177 # ignore classes unchanged from previous API level
2178 for k, v in prev_noticed.iteritems():
2179 if k in cur_noticed and v == cur_noticed[k]:
2180 del cur_noticed[k]
2181
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002182 """
2183 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002184 # look for compatibility issues
2185 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002186
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002187 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2188 for f in sorted(compat_fail):
2189 print compat_fail[f]
2190 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002191 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002192
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002193 if args['show_noticed'] and len(cur_noticed) != 0:
2194 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2195 for f in sorted(cur_noticed.keys()):
2196 print f
2197 print
2198
Jason Monk53b2a732017-11-10 15:43:17 -05002199 if len(cur_fail) != 0:
2200 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2201 for f in sorted(cur_fail):
2202 print cur_fail[f]
2203 print
2204 sys.exit(77)