blob: ef405e4b1343c382eedc3aa0b1721426d962a46c [file] [log] [blame]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001#!/usr/bin/env python
2
3# Copyright (C) 2014 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the 'License');
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an 'AS IS' BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Enforces common Android public API design patterns. It ignores lint messages from
19a previous API level, if provided.
20
21Usage: apilint.py current.txt
22Usage: apilint.py current.txt previous.txt
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070023
24You can also splice in blame details like this:
25$ git blame api/current.txt -t -e > /tmp/currentblame.txt
26$ apilint.py /tmp/currentblame.txt previous.txt --no-color
Jeff Sharkey8190f4882014-08-28 12:24:07 -070027"""
28
Adrian Roos1f1b6a82019-01-05 20:09:38 +010029import re, sys, collections, traceback, argparse, itertools
Jeff Sharkey8190f4882014-08-28 12:24:07 -070030
31
32BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
33
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070034ALLOW_GOOGLE = False
35USE_COLOR = True
36
Jeff Sharkey8190f4882014-08-28 12:24:07 -070037def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
38 # manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070039 if not USE_COLOR: return ""
Jeff Sharkey8190f4882014-08-28 12:24:07 -070040 codes = []
41 if reset: codes.append("0")
42 else:
43 if not fg is None: codes.append("3%d" % (fg))
44 if not bg is None:
45 if not bright: codes.append("4%d" % (bg))
46 else: codes.append("10%d" % (bg))
47 if bold: codes.append("1")
48 elif dim: codes.append("2")
49 else: codes.append("22")
50 return "\033[%sm" % (";".join(codes))
51
52
53class Field():
Adrian Roosb787c182019-01-03 18:54:33 +010054 def __init__(self, clazz, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070055 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070056 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070057 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070058 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -070059
Adrian Roosb787c182019-01-03 18:54:33 +010060 if sig_format == 2:
61 V2LineParser(raw).parse_into_field(self)
62 elif sig_format == 1:
63 # drop generics for now; may need multiple passes
64 raw = re.sub("<[^<]+?>", "", raw)
65 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -060066
Adrian Roosb787c182019-01-03 18:54:33 +010067 raw = raw.split()
68 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070069
Adrian Roosb787c182019-01-03 18:54:33 +010070 for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
71 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070072
Adrian Roosb787c182019-01-03 18:54:33 +010073 # ignore annotations for now
74 raw = [ r for r in raw if not r.startswith("@") ]
Jeff Sharkey40d67f42018-07-17 13:29:40 -060075
Adrian Roosb787c182019-01-03 18:54:33 +010076 self.typ = raw[0]
77 self.name = raw[1].strip(";")
78 if len(raw) >= 4 and raw[2] == "=":
79 self.value = raw[3].strip(';"')
80 else:
81 self.value = None
82
83 self.ident = "-".join((self.typ, self.name, self.value or ""))
Jeff Sharkey037458a2014-09-04 15:46:20 -070084
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -070085 def __hash__(self):
86 return hash(self.raw)
87
Jeff Sharkey8190f4882014-08-28 12:24:07 -070088 def __repr__(self):
89 return self.raw
90
Jeff Sharkey8190f4882014-08-28 12:24:07 -070091class Method():
Adrian Roosb787c182019-01-03 18:54:33 +010092 def __init__(self, clazz, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070093 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070094 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070095 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070096 self.blame = blame
97
Adrian Roosb787c182019-01-03 18:54:33 +010098 if sig_format == 2:
99 V2LineParser(raw).parse_into_method(self)
100 elif sig_format == 1:
101 # drop generics for now; may need multiple passes
102 raw = re.sub("<[^<]+?>", "", raw)
103 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700104
Adrian Roosb787c182019-01-03 18:54:33 +0100105 # handle each clause differently
106 raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600107
Adrian Roosb787c182019-01-03 18:54:33 +0100108 # parse prefixes
109 raw = re.split("[\s]+", raw_prefix)
110 for r in ["", ";"]:
111 while r in raw: raw.remove(r)
112 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700113
Adrian Roosb787c182019-01-03 18:54:33 +0100114 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator", "synchronized"]:
115 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700116
Adrian Roosb787c182019-01-03 18:54:33 +0100117 self.typ = raw[0]
118 self.name = raw[1]
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600119
Adrian Roosb787c182019-01-03 18:54:33 +0100120 # parse args
121 self.args = []
122 for arg in re.split(",\s*", raw_args):
123 arg = re.split("\s", arg)
124 # ignore annotations for now
125 arg = [ a for a in arg if not a.startswith("@") ]
126 if len(arg[0]) > 0:
127 self.args.append(arg[0])
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600128
Adrian Roosb787c182019-01-03 18:54:33 +0100129 # parse throws
130 self.throws = []
131 for throw in re.split(",\s*", raw_throws):
132 self.throws.append(throw)
133 else:
134 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600135
Adrian Roosb787c182019-01-03 18:54:33 +0100136 self.ident = "-".join((self.typ, self.name, "-".join(self.args)))
137
138 def sig_matches(self, typ, name, args):
139 return typ == self.typ and name == self.name and args == self.args
Jeff Sharkey037458a2014-09-04 15:46:20 -0700140
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700141 def __hash__(self):
142 return hash(self.raw)
143
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700144 def __repr__(self):
145 return self.raw
146
147
148class Class():
Adrian Roosb787c182019-01-03 18:54:33 +0100149 def __init__(self, pkg, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700150 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700151 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700152 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700153 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700154 self.ctors = []
155 self.fields = []
156 self.methods = []
157
Adrian Roosb787c182019-01-03 18:54:33 +0100158 if sig_format == 2:
159 V2LineParser(raw).parse_into_class(self)
160 elif sig_format == 1:
161 # drop generics for now; may need multiple passes
162 raw = re.sub("<[^<]+?>", "", raw)
163 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600164
Adrian Roosb787c182019-01-03 18:54:33 +0100165 raw = raw.split()
166 self.split = list(raw)
167 if "class" in raw:
168 self.fullname = raw[raw.index("class")+1]
169 elif "interface" in raw:
170 self.fullname = raw[raw.index("interface")+1]
171 elif "@interface" in raw:
172 self.fullname = raw[raw.index("@interface")+1]
173 else:
174 raise ValueError("Funky class type %s" % (self.raw))
Jeff Sharkey037458a2014-09-04 15:46:20 -0700175
Adrian Roosb787c182019-01-03 18:54:33 +0100176 if "extends" in raw:
177 self.extends = raw[raw.index("extends")+1]
178 else:
179 self.extends = None
180
181 if "implements" in raw:
182 self.implements = raw[raw.index("implements")+1]
183 else:
184 self.implements = None
Jeff Sharkey037458a2014-09-04 15:46:20 -0700185 else:
Adrian Roosb787c182019-01-03 18:54:33 +0100186 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700187
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700188 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800189 self.fullname_path = self.fullname.split(".")
190
Adrian Roosb787c182019-01-03 18:54:33 +0100191 if self.extends is not None:
192 self.extends_path = self.extends.split(".")
193 else:
194 self.extends_path = []
195
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700196 self.name = self.fullname[self.fullname.rindex(".")+1:]
197
Adrian Roos6eb57b02018-12-13 22:08:29 +0100198 def merge_from(self, other):
199 self.ctors.extend(other.ctors)
200 self.fields.extend(other.fields)
201 self.methods.extend(other.methods)
202
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700203 def __hash__(self):
204 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
205
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700206 def __repr__(self):
207 return self.raw
208
209
210class Package():
Adrian Roosb1faa0b2019-02-26 11:54:40 +0100211 NAME = re.compile("package(?: .*)? ([A-Za-z.]+)")
212
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700213 def __init__(self, line, raw, blame):
214 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700215 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700216 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700217
Adrian Roosb1faa0b2019-02-26 11:54:40 +0100218 self.name = Package.NAME.match(raw).group(1)
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800219 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700220
221 def __repr__(self):
222 return self.raw
223
Adrian Roose5eeae72019-01-04 20:10:06 +0100224class V2Tokenizer(object):
225 __slots__ = ["raw"]
226
Adrian Rooscf82e042019-01-29 15:01:28 +0100227 SIGNATURE_PREFIX = "// Signature format: "
Adrian Roos5cdfb692019-01-05 22:04:55 +0100228 DELIMITER = re.compile(r'\s+|[()@<>;,={}/"!?]|\[\]|\.\.\.')
Adrian Roosb787c182019-01-03 18:54:33 +0100229 STRING_SPECIAL = re.compile(r'["\\]')
230
231 def __init__(self, raw):
232 self.raw = raw
Adrian Roosb787c182019-01-03 18:54:33 +0100233
Adrian Roose5eeae72019-01-04 20:10:06 +0100234 def tokenize(self):
235 tokens = []
236 current = 0
237 raw = self.raw
238 length = len(raw)
Adrian Roosb787c182019-01-03 18:54:33 +0100239
Adrian Roose5eeae72019-01-04 20:10:06 +0100240 while current < length:
241 while current < length:
242 start = current
243 match = V2Tokenizer.DELIMITER.search(raw, start)
244 if match is not None:
245 match_start = match.start()
246 if match_start == current:
247 end = match.end()
248 else:
249 end = match_start
250 else:
251 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100252
Adrian Roose5eeae72019-01-04 20:10:06 +0100253 token = raw[start:end]
254 current = end
Adrian Roosb787c182019-01-03 18:54:33 +0100255
Adrian Roose5eeae72019-01-04 20:10:06 +0100256 if token == "" or token[0] == " ":
257 continue
258 else:
259 break
Adrian Roosb787c182019-01-03 18:54:33 +0100260
Adrian Roose5eeae72019-01-04 20:10:06 +0100261 if token == "@":
262 if raw[start:start+11] == "@interface ":
263 current = start + 11
264 tokens.append("@interface")
265 continue
266 elif token == '/':
267 if raw[start:start+2] == "//":
268 current = length
269 continue
270 elif token == '"':
271 current, string_token = self.tokenize_string(raw, length, current)
272 tokens.append(token + string_token)
273 continue
Adrian Roosb787c182019-01-03 18:54:33 +0100274
Adrian Roose5eeae72019-01-04 20:10:06 +0100275 tokens.append(token)
Adrian Roosb787c182019-01-03 18:54:33 +0100276
Adrian Roose5eeae72019-01-04 20:10:06 +0100277 return tokens
Adrian Roosb787c182019-01-03 18:54:33 +0100278
Adrian Roose5eeae72019-01-04 20:10:06 +0100279 def tokenize_string(self, raw, length, current):
280 start = current
281 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100282 while start < end:
Adrian Roose5eeae72019-01-04 20:10:06 +0100283 match = V2Tokenizer.STRING_SPECIAL.search(raw, start)
Adrian Roosb787c182019-01-03 18:54:33 +0100284 if match:
285 if match.group() == '"':
286 end = match.end()
287 break
288 elif match.group() == '\\':
289 # ignore whatever is after the slash
290 start += 2
291 else:
292 raise ValueError("Unexpected match: `%s`" % (match.group()))
293 else:
Adrian Roose5eeae72019-01-04 20:10:06 +0100294 raise ValueError("Unexpected EOF tokenizing string: `%s`" % (raw[current - 1:],))
Adrian Roosb787c182019-01-03 18:54:33 +0100295
Adrian Roose5eeae72019-01-04 20:10:06 +0100296 token = raw[current:end]
297 return end, token
Adrian Roosb787c182019-01-03 18:54:33 +0100298
Adrian Roose5eeae72019-01-04 20:10:06 +0100299class V2LineParser(object):
300 __slots__ = ["tokenized", "current", "len"]
301
Adrian Roos258c5722019-01-21 15:43:15 +0100302 FIELD_KINDS = ("field", "property", "enum_constant")
Adrian Roosd1e38922019-01-14 15:44:15 +0100303 MODIFIERS = set("public protected internal private abstract default static final transient volatile synchronized native operator sealed strictfp infix inline suspend vararg".split())
Adrian Roosb787c182019-01-03 18:54:33 +0100304 JAVA_LANG_TYPES = set("AbstractMethodError AbstractStringBuilder Appendable ArithmeticException ArrayIndexOutOfBoundsException ArrayStoreException AssertionError AutoCloseable Boolean BootstrapMethodError Byte Character CharSequence Class ClassCastException ClassCircularityError ClassFormatError ClassLoader ClassNotFoundException Cloneable CloneNotSupportedException Comparable Compiler Deprecated Double Enum EnumConstantNotPresentException Error Exception ExceptionInInitializerError Float FunctionalInterface IllegalAccessError IllegalAccessException IllegalArgumentException IllegalMonitorStateException IllegalStateException IllegalThreadStateException IncompatibleClassChangeError IndexOutOfBoundsException InheritableThreadLocal InstantiationError InstantiationException Integer InternalError InterruptedException Iterable LinkageError Long Math NegativeArraySizeException NoClassDefFoundError NoSuchFieldError NoSuchFieldException NoSuchMethodError NoSuchMethodException NullPointerException Number NumberFormatException Object OutOfMemoryError Override Package package-info.java Process ProcessBuilder ProcessEnvironment ProcessImpl Readable ReflectiveOperationException Runnable Runtime RuntimeException RuntimePermission SafeVarargs SecurityException SecurityManager Short StackOverflowError StackTraceElement StrictMath String StringBuffer StringBuilder StringIndexOutOfBoundsException SuppressWarnings System Thread ThreadDeath ThreadGroup ThreadLocal Throwable TypeNotPresentException UNIXProcess UnknownError UnsatisfiedLinkError UnsupportedClassVersionError UnsupportedOperationException VerifyError VirtualMachineError Void".split())
305
306 def __init__(self, raw):
Adrian Roose5eeae72019-01-04 20:10:06 +0100307 self.tokenized = V2Tokenizer(raw).tokenize()
Adrian Roosb787c182019-01-03 18:54:33 +0100308 self.current = 0
Adrian Roose5eeae72019-01-04 20:10:06 +0100309 self.len = len(self.tokenized)
Adrian Roosb787c182019-01-03 18:54:33 +0100310
311 def parse_into_method(self, method):
312 method.split = []
313 kind = self.parse_one_of("ctor", "method")
314 method.split.append(kind)
315 annotations = self.parse_annotations()
316 method.split.extend(self.parse_modifiers())
317 self.parse_matching_paren("<", ">")
318 if "@Deprecated" in annotations:
319 method.split.append("deprecated")
320 if kind == "ctor":
321 method.typ = "ctor"
322 else:
323 method.typ = self.parse_type()
324 method.split.append(method.typ)
325 method.name = self.parse_name()
326 method.split.append(method.name)
327 self.parse_token("(")
328 method.args = self.parse_args()
329 self.parse_token(")")
330 method.throws = self.parse_throws()
331 if "@interface" in method.clazz.split:
332 self.parse_annotation_default()
333 self.parse_token(";")
334 self.parse_eof()
335
336 def parse_into_class(self, clazz):
337 clazz.split = []
338 annotations = self.parse_annotations()
339 if "@Deprecated" in annotations:
340 clazz.split.append("deprecated")
341 clazz.split.extend(self.parse_modifiers())
342 kind = self.parse_one_of("class", "interface", "@interface", "enum")
343 if kind == "enum":
344 # enums are implicitly final
345 clazz.split.append("final")
346 clazz.split.append(kind)
347 clazz.fullname = self.parse_name()
348 self.parse_matching_paren("<", ">")
349 extends = self.parse_extends()
350 clazz.extends = extends[0] if extends else None
351 implements = self.parse_implements()
352 clazz.implements = implements[0] if implements else None
353 # The checks assume that interfaces are always found in implements, which isn't true for
354 # subinterfaces.
355 if not implements and "interface" in clazz.split:
356 clazz.implements = clazz.extends
357 self.parse_token("{")
358 self.parse_eof()
359
360 def parse_into_field(self, field):
Adrian Roos258c5722019-01-21 15:43:15 +0100361 kind = self.parse_one_of(*V2LineParser.FIELD_KINDS)
Adrian Roosb787c182019-01-03 18:54:33 +0100362 field.split = [kind]
363 annotations = self.parse_annotations()
364 if "@Deprecated" in annotations:
365 field.split.append("deprecated")
366 field.split.extend(self.parse_modifiers())
367 field.typ = self.parse_type()
368 field.split.append(field.typ)
369 field.name = self.parse_name()
370 field.split.append(field.name)
371 if self.parse_if("="):
372 field.value = self.parse_value_stripped()
373 else:
374 field.value = None
375
376 self.parse_token(";")
377 self.parse_eof()
378
379 def lookahead(self):
380 return self.tokenized[self.current]
381
382 def parse_one_of(self, *options):
383 found = self.lookahead()
384 if found not in options:
385 raise ValueError("Parsing failed, expected one of `%s` but found `%s` in %s" % (options, found, repr(self.tokenized)))
386 return self.parse_token()
387
388 def parse_token(self, tok = None):
389 found = self.lookahead()
390 if tok is not None and found != tok:
391 raise ValueError("Parsing failed, expected `%s` but found `%s` in %s" % (tok, found, repr(self.tokenized)))
392 self.current += 1
393 return found
394
395 def eof(self):
Adrian Roose5eeae72019-01-04 20:10:06 +0100396 return self.current == self.len
Adrian Roosb787c182019-01-03 18:54:33 +0100397
398 def parse_eof(self):
399 if not self.eof():
400 raise ValueError("Parsing failed, expected EOF, but %s has not been parsed in %s" % (self.tokenized[self.current:], self.tokenized))
401
402 def parse_if(self, tok):
403 if not self.eof() and self.lookahead() == tok:
404 self.parse_token()
405 return True
406 return False
407
408 def parse_annotations(self):
409 ret = []
410 while self.lookahead() == "@":
411 ret.append(self.parse_annotation())
412 return ret
413
414 def parse_annotation(self):
415 ret = self.parse_token("@") + self.parse_token()
416 self.parse_matching_paren("(", ")")
417 return ret
418
419 def parse_matching_paren(self, open, close):
420 start = self.current
421 if not self.parse_if(open):
422 return
423 length = len(self.tokenized)
424 count = 1
425 while count > 0:
426 if self.current == length:
427 raise ValueError("Unexpected EOF looking for closing paren: `%s`" % (self.tokenized[start:],))
428 t = self.parse_token()
429 if t == open:
430 count += 1
431 elif t == close:
432 count -= 1
433 return self.tokenized[start:self.current]
434
435 def parse_modifiers(self):
436 ret = []
437 while self.lookahead() in V2LineParser.MODIFIERS:
438 ret.append(self.parse_token())
439 return ret
440
Adrian Roos5cdfb692019-01-05 22:04:55 +0100441 def parse_kotlin_nullability(self):
442 t = self.lookahead()
443 if t == "?" or t == "!":
444 return self.parse_token()
445 return None
446
Adrian Roosb787c182019-01-03 18:54:33 +0100447 def parse_type(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100448 self.parse_annotations()
Adrian Roosb787c182019-01-03 18:54:33 +0100449 type = self.parse_token()
Adrian Roosd1e38922019-01-14 15:44:15 +0100450 if type[-1] == '.':
451 self.parse_annotations()
452 type += self.parse_token()
Adrian Roosb787c182019-01-03 18:54:33 +0100453 if type in V2LineParser.JAVA_LANG_TYPES:
454 type = "java.lang." + type
455 self.parse_matching_paren("<", ">")
Adrian Roos5cdfb692019-01-05 22:04:55 +0100456 while True:
457 t = self.lookahead()
Adrian Roosd1e38922019-01-14 15:44:15 +0100458 if t == "@":
459 self.parse_annotation()
460 elif t == "[]":
Adrian Roos5cdfb692019-01-05 22:04:55 +0100461 type += self.parse_token()
462 elif self.parse_kotlin_nullability() is not None:
463 pass # discard nullability for now
464 else:
465 break
Adrian Roosb787c182019-01-03 18:54:33 +0100466 return type
467
468 def parse_arg_type(self):
469 type = self.parse_type()
470 if self.parse_if("..."):
471 type += "..."
Adrian Roos5cdfb692019-01-05 22:04:55 +0100472 self.parse_kotlin_nullability() # discard nullability for now
Adrian Roosb787c182019-01-03 18:54:33 +0100473 return type
474
475 def parse_name(self):
476 return self.parse_token()
477
478 def parse_args(self):
479 args = []
480 if self.lookahead() == ")":
481 return args
482
483 while True:
484 args.append(self.parse_arg())
485 if self.lookahead() == ")":
486 return args
487 self.parse_token(",")
488
489 def parse_arg(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100490 self.parse_if("vararg") # kotlin vararg
Adrian Roosb787c182019-01-03 18:54:33 +0100491 self.parse_annotations()
Adrian Roos5cdfb692019-01-05 22:04:55 +0100492 type = self.parse_arg_type()
493 l = self.lookahead()
494 if l != "," and l != ")":
Adrian Roosd1e38922019-01-14 15:44:15 +0100495 if self.lookahead() != '=':
496 self.parse_token() # kotlin argument name
Adrian Roos5cdfb692019-01-05 22:04:55 +0100497 if self.parse_if('='): # kotlin default value
Adrian Roosd1e38922019-01-14 15:44:15 +0100498 self.parse_expression()
Adrian Roos5cdfb692019-01-05 22:04:55 +0100499 return type
Adrian Roosb787c182019-01-03 18:54:33 +0100500
Adrian Roosd1e38922019-01-14 15:44:15 +0100501 def parse_expression(self):
502 while not self.lookahead() in [')', ',', ';']:
503 (self.parse_matching_paren('(', ')') or
504 self.parse_matching_paren('{', '}') or
505 self.parse_token())
506
Adrian Roosb787c182019-01-03 18:54:33 +0100507 def parse_throws(self):
508 ret = []
509 if self.parse_if("throws"):
510 ret.append(self.parse_type())
511 while self.parse_if(","):
512 ret.append(self.parse_type())
513 return ret
514
515 def parse_extends(self):
516 if self.parse_if("extends"):
517 return self.parse_space_delimited_type_list()
518 return []
519
520 def parse_implements(self):
521 if self.parse_if("implements"):
522 return self.parse_space_delimited_type_list()
523 return []
524
525 def parse_space_delimited_type_list(self, terminals = ["implements", "{"]):
526 types = []
527 while True:
528 types.append(self.parse_type())
529 if self.lookahead() in terminals:
530 return types
531
532 def parse_annotation_default(self):
533 if self.parse_if("default"):
Adrian Roosd1e38922019-01-14 15:44:15 +0100534 self.parse_expression()
Adrian Roosb787c182019-01-03 18:54:33 +0100535
536 def parse_value(self):
537 if self.lookahead() == "{":
538 return " ".join(self.parse_matching_paren("{", "}"))
539 elif self.lookahead() == "(":
540 return " ".join(self.parse_matching_paren("(", ")"))
541 else:
542 return self.parse_token()
543
544 def parse_value_stripped(self):
545 value = self.parse_value()
546 if value[0] == '"':
547 return value[1:-1]
548 return value
549
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700550
Adrian Roos038a0292018-12-19 17:11:21 +0100551def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
552 in_classes_with_base=[]):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700553 api = {}
Adrian Roos038a0292018-12-19 17:11:21 +0100554 in_classes_with_base = _retry_iterator(in_classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +0100555
556 if base_f:
Adrian Roos038a0292018-12-19 17:11:21 +0100557 base_classes = _retry_iterator(_parse_stream_to_generator(base_f))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100558 else:
559 base_classes = []
560
Adrian Roos038a0292018-12-19 17:11:21 +0100561 def handle_class(clazz):
Adrian Roos6eb57b02018-12-13 22:08:29 +0100562 if clazz_cb:
563 clazz_cb(clazz)
564 else: # In callback mode, don't keep track of the full API
565 api[clazz.fullname] = clazz
566
Adrian Roos038a0292018-12-19 17:11:21 +0100567 def handle_missed_classes_with_base(clazz):
568 for c in _yield_until_matching_class(in_classes_with_base, clazz):
569 base_class = _skip_to_matching_class(base_classes, c)
570 if base_class:
571 handle_class(base_class)
572
573 for clazz in _parse_stream_to_generator(f):
574 # Before looking at clazz, let's see if there's some classes that were not present, but
575 # may have an entry in the base stream.
576 handle_missed_classes_with_base(clazz)
577
578 base_class = _skip_to_matching_class(base_classes, clazz)
579 if base_class:
580 clazz.merge_from(base_class)
581 if out_classes_with_base is not None:
582 out_classes_with_base.append(clazz)
583 handle_class(clazz)
584
585 handle_missed_classes_with_base(None)
586
Adrian Roos6eb57b02018-12-13 22:08:29 +0100587 return api
588
589def _parse_stream_to_generator(f):
590 line = 0
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700591 pkg = None
592 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700593 blame = None
Adrian Roosb787c182019-01-03 18:54:33 +0100594 sig_format = 1
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700595
596 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Adrian Roos258c5722019-01-21 15:43:15 +0100597
598 field_prefixes = map(lambda kind: " %s" % (kind,), V2LineParser.FIELD_KINDS)
599 def startsWithFieldPrefix(raw):
600 for prefix in field_prefixes:
601 if raw.startswith(prefix):
602 return True
603 return False
604
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800605 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700606 line += 1
607 raw = raw.rstrip()
608 match = re_blame.match(raw)
609 if match is not None:
610 blame = match.groups()[0:2]
611 raw = match.groups()[2]
612 else:
613 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700614
Adrian Rooscf82e042019-01-29 15:01:28 +0100615 if line == 1 and raw.startswith("// Signature format: "):
616 sig_format_string = raw[len(V2Tokenizer.SIGNATURE_PREFIX):]
617 if sig_format_string in ["2.0", "3.0"]:
618 sig_format = 2
619 else:
620 raise ValueError("Unknown format: %s" % (sig_format_string,))
Adrian Roosb787c182019-01-03 18:54:33 +0100621 elif raw.startswith("package"):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700622 pkg = Package(line, raw, blame)
623 elif raw.startswith(" ") and raw.endswith("{"):
Adrian Roosb787c182019-01-03 18:54:33 +0100624 clazz = Class(pkg, line, raw, blame, sig_format=sig_format)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700625 elif raw.startswith(" ctor"):
Adrian Roosb787c182019-01-03 18:54:33 +0100626 clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700627 elif raw.startswith(" method"):
Adrian Roosb787c182019-01-03 18:54:33 +0100628 clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos258c5722019-01-21 15:43:15 +0100629 elif startsWithFieldPrefix(raw):
Adrian Roosb787c182019-01-03 18:54:33 +0100630 clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100631 elif raw.startswith(" }") and clazz:
Adrian Roos038a0292018-12-19 17:11:21 +0100632 yield clazz
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800633
Adrian Roos5ed42b62018-12-19 17:10:22 +0100634def _retry_iterator(it):
635 """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
636 for e in it:
637 while True:
638 retry = yield e
639 if not retry:
640 break
641 # send() was called, asking us to redeliver clazz on next(). Still need to yield
642 # a dummy value to the send() first though.
643 if (yield "Returning clazz on next()"):
644 raise TypeError("send() must be followed by next(), not send()")
645
Adrian Roos038a0292018-12-19 17:11:21 +0100646def _skip_to_matching_class(classes, needle):
647 """Takes a classes iterator and consumes entries until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700648
Adrian Roos6eb57b02018-12-13 22:08:29 +0100649 This relies on classes being sorted by package and class name."""
650
651 for clazz in classes:
652 if clazz.pkg.name < needle.pkg.name:
653 # We haven't reached the right package yet
654 continue
Adrian Roos5ed42b62018-12-19 17:10:22 +0100655 if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
656 # We're in the right package, but not the right class yet
Adrian Roos6eb57b02018-12-13 22:08:29 +0100657 continue
658 if clazz.fullname == needle.fullname:
659 return clazz
660 # We ran past the right class. Send it back into the generator, then report failure.
661 classes.send(clazz)
662 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700663
Adrian Roos038a0292018-12-19 17:11:21 +0100664def _yield_until_matching_class(classes, needle):
665 """Takes a class iterator and yields entries it until it reaches the class we're looking for.
666
667 This relies on classes being sorted by package and class name."""
668
669 for clazz in classes:
670 if needle is None:
671 yield clazz
672 elif clazz.pkg.name < needle.pkg.name:
673 # We haven't reached the right package yet
674 yield clazz
675 elif clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
676 # We're in the right package, but not the right class yet
677 yield clazz
678 elif clazz.fullname == needle.fullname:
679 # Class found, abort.
680 return
681 else:
682 # We ran past the right class. Send it back into the iterator, then abort.
683 classes.send(clazz)
684 return
685
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700686class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800687 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700688 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700689 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800690 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700691 self.msg = msg
692
693 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800694 self.head = "Error %s" % (rule) if rule else "Error"
695 dump = "%s%s:%s %s" % (format(fg=RED, bg=BLACK, bold=True), self.head, format(reset=True), msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700696 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800697 self.head = "Warning %s" % (rule) if rule else "Warning"
698 dump = "%s%s:%s %s" % (format(fg=YELLOW, bg=BLACK, bold=True), self.head, format(reset=True), msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700699
700 self.line = clazz.line
701 blame = clazz.blame
702 if detail is not None:
703 dump += "\n in " + repr(detail)
704 self.line = detail.line
705 blame = detail.blame
706 dump += "\n in " + repr(clazz)
707 dump += "\n in " + repr(clazz.pkg)
708 dump += "\n at line " + repr(self.line)
709 if blame is not None:
710 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
711
712 self.dump = dump
713
714 def __repr__(self):
715 return self.dump
716
717
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700718failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700719
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800720def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700721 """Records an API failure to be processed later."""
722 global failures
723
Adrian Roosb787c182019-01-03 18:54:33 +0100724 sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700725 sig = sig.replace(" deprecated ", " ")
726
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800727 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700728
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700729
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800730def warn(clazz, detail, rule, msg):
731 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700732
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800733def error(clazz, detail, rule, msg):
734 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700735
736
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700737noticed = {}
738
739def notice(clazz):
740 global noticed
741
742 noticed[clazz.fullname] = hash(clazz)
743
744
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700745def verify_constants(clazz):
746 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700747 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600748 if clazz.fullname.startswith("android.os.Build"): return
749 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700750
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600751 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700752 for f in clazz.fields:
753 if "static" in f.split and "final" in f.split:
754 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800755 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600756 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700757 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
758 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600759 if f.typ in req and f.value is None:
760 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700761
762
763def verify_enums(clazz):
764 """Enums are bad, mmkay?"""
Adrian Roosb787c182019-01-03 18:54:33 +0100765 if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800766 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700767
768
769def verify_class_names(clazz):
770 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700771 if clazz.fullname.startswith("android.opengl"): return
772 if clazz.fullname.startswith("android.renderscript"): return
773 if re.match("android\.R\.[a-z]+", clazz.fullname): return
774
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700775 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800776 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700777 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800778 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700779 if clazz.name.endswith("Impl"):
780 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700781
782
783def verify_method_names(clazz):
784 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700785 if clazz.fullname.startswith("android.opengl"): return
786 if clazz.fullname.startswith("android.renderscript"): return
787 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700788
789 for m in clazz.methods:
790 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800791 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700792 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800793 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700794
795
796def verify_callbacks(clazz):
797 """Verify Callback classes.
798 All callback classes must be abstract.
799 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700800 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700801
802 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800803 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700804 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800805 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700806
807 if clazz.name.endswith("Callback"):
808 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800809 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700810
811 for m in clazz.methods:
812 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800813 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700814
815
816def verify_listeners(clazz):
817 """Verify Listener classes.
818 All Listener classes must be interface.
819 All methods must follow onFoo() naming style.
820 If only a single method, it must match class name:
821 interface OnFooListener { void onFoo() }"""
822
823 if clazz.name.endswith("Listener"):
Adrian Roosb787c182019-01-03 18:54:33 +0100824 if "abstract" in clazz.split and "class" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800825 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700826
827 for m in clazz.methods:
828 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800829 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700830
831 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
832 m = clazz.methods[0]
833 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800834 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700835
836
837def verify_actions(clazz):
838 """Verify intent actions.
839 All action names must be named ACTION_FOO.
840 All action values must be scoped by package and match name:
841 package android.foo {
842 String ACTION_BAR = "android.foo.action.BAR";
843 }"""
844 for f in clazz.fields:
845 if f.value is None: continue
846 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700847 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600848 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700849
850 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
851 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
852 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800853 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700854 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700855 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700856 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700857 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700858 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700859 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
860 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700861 else:
862 prefix = clazz.pkg.name + ".action"
863 expected = prefix + "." + f.name[7:]
864 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700865 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700866
867
868def verify_extras(clazz):
869 """Verify intent extras.
870 All extra names must be named EXTRA_FOO.
871 All extra values must be scoped by package and match name:
872 package android.foo {
873 String EXTRA_BAR = "android.foo.extra.BAR";
874 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700875 if clazz.fullname == "android.app.Notification": return
876 if clazz.fullname == "android.appwidget.AppWidgetManager": return
877
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700878 for f in clazz.fields:
879 if f.value is None: continue
880 if f.name.startswith("ACTION_"): continue
881
882 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
883 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
884 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800885 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700886 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700887 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700888 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700889 elif clazz.pkg.name == "android.app.admin":
890 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700891 else:
892 prefix = clazz.pkg.name + ".extra"
893 expected = prefix + "." + f.name[6:]
894 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700895 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700896
897
898def verify_equals(clazz):
899 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e62016-12-21 13:46:33 -0700900 eq = False
901 hc = False
902 for m in clazz.methods:
Adrian Roosb787c182019-01-03 18:54:33 +0100903 if "static" in m.split: continue
904 if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
905 if m.sig_matches("int", "hashCode", []): hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700906 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800907 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700908
909
910def verify_parcelable(clazz):
911 """Verify that Parcelable objects aren't hiding required bits."""
Adrian Roosb787c182019-01-03 18:54:33 +0100912 if clazz.implements == "android.os.Parcelable":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700913 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
914 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
915 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
916
917 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800918 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700919
Adrian Roosb787c182019-01-03 18:54:33 +0100920 if "final" not in clazz.split:
Jeff Sharkey331279b2016-02-29 16:02:02 -0700921 error(clazz, None, "FW8", "Parcelable classes must be final")
922
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700923 for c in clazz.ctors:
924 if c.args == ["android.os.Parcel"]:
925 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
926
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700927
928def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800929 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700930 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600931 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700932 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800933 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700934 for f in clazz.fields:
935 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800936 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700937
938
939def verify_fields(clazz):
940 """Verify that all exposed fields are final.
941 Exposed fields must follow myName style.
942 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700943
944 IGNORE_BARE_FIELDS = [
945 "android.app.ActivityManager.RecentTaskInfo",
946 "android.app.Notification",
947 "android.content.pm.ActivityInfo",
948 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600949 "android.content.pm.ComponentInfo",
950 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700951 "android.content.pm.FeatureGroupInfo",
952 "android.content.pm.InstrumentationInfo",
953 "android.content.pm.PackageInfo",
954 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600955 "android.content.res.Configuration",
956 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700957 "android.os.Message",
958 "android.system.StructPollfd",
959 ]
960
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700961 for f in clazz.fields:
962 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700963 if clazz.fullname in IGNORE_BARE_FIELDS:
964 pass
965 elif clazz.fullname.endswith("LayoutParams"):
966 pass
967 elif clazz.fullname.startswith("android.util.Mutable"):
968 pass
969 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800970 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700971
Adrian Roosd1e38922019-01-14 15:44:15 +0100972 if "static" not in f.split and "property" not in f.split:
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700973 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800974 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700975
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700976 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800977 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700978
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700979 if re.match("[A-Z_]+", f.name):
980 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800981 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700982
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700983
984def verify_register(clazz):
985 """Verify parity of registration methods.
986 Callback objects use register/unregister methods.
987 Listener objects use add/remove methods."""
988 methods = [ m.name for m in clazz.methods ]
989 for m in clazz.methods:
990 if "Callback" in m.raw:
991 if m.name.startswith("register"):
992 other = "unregister" + m.name[8:]
993 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800994 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700995 if m.name.startswith("unregister"):
996 other = "register" + m.name[10:]
997 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800998 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700999
1000 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001001 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001002
1003 if "Listener" in m.raw:
1004 if m.name.startswith("add"):
1005 other = "remove" + m.name[3:]
1006 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001007 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001008 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
1009 other = "add" + m.name[6:]
1010 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001011 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001012
1013 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001014 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001015
1016
1017def verify_sync(clazz):
1018 """Verify synchronized methods aren't exposed."""
1019 for m in clazz.methods:
1020 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001021 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001022
1023
1024def verify_intent_builder(clazz):
1025 """Verify that Intent builders are createFooIntent() style."""
1026 if clazz.name == "Intent": return
1027
1028 for m in clazz.methods:
1029 if m.typ == "android.content.Intent":
1030 if m.name.startswith("create") and m.name.endswith("Intent"):
1031 pass
1032 else:
Adam Powell539ea122015-04-10 13:01:37 -07001033 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001034
1035
1036def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001037 """Verify that helper classes are named consistently with what they extend.
1038 All developer extendable methods should be named onFoo()."""
1039 test_methods = False
Adrian Roosb787c182019-01-03 18:54:33 +01001040 if clazz.extends == "android.app.Service":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001041 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001042 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001043 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001044
1045 found = False
1046 for f in clazz.fields:
1047 if f.name == "SERVICE_INTERFACE":
1048 found = True
1049 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001050 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001051
Adrian Roosb787c182019-01-03 18:54:33 +01001052 if clazz.extends == "android.content.ContentProvider":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001053 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001054 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001055 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001056
1057 found = False
1058 for f in clazz.fields:
1059 if f.name == "PROVIDER_INTERFACE":
1060 found = True
1061 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001062 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001063
Adrian Roosb787c182019-01-03 18:54:33 +01001064 if clazz.extends == "android.content.BroadcastReceiver":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001065 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001066 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001067 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001068
Adrian Roosb787c182019-01-03 18:54:33 +01001069 if clazz.extends == "android.app.Activity":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001070 test_methods = True
1071 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001072 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001073
1074 if test_methods:
1075 for m in clazz.methods:
1076 if "final" in m.split: continue
1077 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001078 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001079 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001080 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001081 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001082
1083
1084def verify_builder(clazz):
1085 """Verify builder classes.
1086 Methods should return the builder to enable chaining."""
Adrian Roosb787c182019-01-03 18:54:33 +01001087 if clazz.extends: return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001088 if not clazz.name.endswith("Builder"): return
1089
1090 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001091 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001092
1093 has_build = False
1094 for m in clazz.methods:
1095 if m.name == "build":
1096 has_build = True
1097 continue
1098
1099 if m.name.startswith("get"): continue
1100 if m.name.startswith("clear"): continue
1101
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001102 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001103 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001104
1105 if m.name.startswith("set"):
1106 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001107 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001108
1109 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001110 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001111
Adrian Roosdeb0ff22019-02-27 23:58:13 +01001112 if "final" not in clazz.split:
1113 error(clazz, None, None, "Builder should be final")
1114
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001115
1116def verify_aidl(clazz):
1117 """Catch people exposing raw AIDL."""
Adrian Roosb787c182019-01-03 18:54:33 +01001118 if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001119 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001120
1121
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001122def verify_internal(clazz):
1123 """Catch people exposing internal classes."""
1124 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001125 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001126
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001127def layering_build_ranking(ranking_list):
1128 r = {}
1129 for rank, ps in enumerate(ranking_list):
1130 if not isinstance(ps, list):
1131 ps = [ps]
1132 for p in ps:
1133 rs = r
1134 for n in p.split('.'):
1135 if n not in rs:
1136 rs[n] = {}
1137 rs = rs[n]
1138 rs['-rank'] = rank
1139 return r
1140
1141LAYERING_PACKAGE_RANKING = layering_build_ranking([
1142 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
1143 "android.app",
1144 "android.widget",
1145 "android.view",
1146 "android.animation",
1147 "android.provider",
1148 ["android.content","android.graphics.drawable"],
1149 "android.database",
1150 "android.text",
1151 "android.graphics",
1152 "android.os",
1153 "android.util"
1154])
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001155
1156def verify_layering(clazz):
1157 """Catch package layering violations.
1158 For example, something in android.os depending on android.app."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001159
1160 def rank(p):
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001161 r = None
1162 l = LAYERING_PACKAGE_RANKING
1163 for n in p.split('.'):
1164 if n in l:
1165 l = l[n]
1166 if '-rank' in l:
1167 r = l['-rank']
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001168 else:
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001169 break
1170 return r
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001171
1172 cr = rank(clazz.pkg.name)
1173 if cr is None: return
1174
1175 for f in clazz.fields:
1176 ir = rank(f.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001177 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001178 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001179
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001180 for m in itertools.chain(clazz.methods, clazz.ctors):
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001181 ir = rank(m.typ)
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 return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001184 for arg in m.args:
1185 ir = rank(arg)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001186 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001187 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001188
1189
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001190def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001191 """Verifies that boolean accessors are named correctly.
1192 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001193
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001194 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
1195 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001196
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001197 gets = [ m for m in clazz.methods if is_get(m) ]
1198 sets = [ m for m in clazz.methods if is_set(m) ]
1199
1200 def error_if_exists(methods, trigger, expected, actual):
1201 for m in methods:
1202 if m.name == actual:
1203 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001204
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001205 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001206 if is_get(m):
1207 if re.match("is[A-Z]", m.name):
1208 target = m.name[2:]
1209 expected = "setIs" + target
1210 error_if_exists(sets, m.name, expected, "setHas" + target)
1211 elif re.match("has[A-Z]", m.name):
1212 target = m.name[3:]
1213 expected = "setHas" + target
1214 error_if_exists(sets, m.name, expected, "setIs" + target)
1215 error_if_exists(sets, m.name, expected, "set" + target)
1216 elif re.match("get[A-Z]", m.name):
1217 target = m.name[3:]
1218 expected = "set" + target
1219 error_if_exists(sets, m.name, expected, "setIs" + target)
1220 error_if_exists(sets, m.name, expected, "setHas" + target)
1221
1222 if is_set(m):
1223 if re.match("set[A-Z]", m.name):
1224 target = m.name[3:]
1225 expected = "get" + target
1226 error_if_exists(sets, m.name, expected, "is" + target)
1227 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001228
1229
1230def verify_collections(clazz):
1231 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001232 if clazz.fullname == "android.os.Bundle": return
1233
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001234 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
1235 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
1236 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001237 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001238 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001239 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001240 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001241 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001242
1243
1244def verify_flags(clazz):
1245 """Verifies that flags are non-overlapping."""
1246 known = collections.defaultdict(int)
1247 for f in clazz.fields:
1248 if "FLAG_" in f.name:
1249 try:
1250 val = int(f.value)
1251 except:
1252 continue
1253
1254 scope = f.name[0:f.name.index("FLAG_")]
1255 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001256 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001257 known[scope] |= val
1258
1259
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001260def verify_exception(clazz):
1261 """Verifies that methods don't throw generic exceptions."""
1262 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001263 for t in m.throws:
1264 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
1265 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001266
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001267 if t in ["android.os.RemoteException"]:
1268 if clazz.name == "android.content.ContentProviderClient": continue
1269 if clazz.name == "android.os.Binder": continue
1270 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -07001271
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001272 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
1273
1274 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
1275 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -07001276
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001277GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001278
1279def verify_google(clazz):
1280 """Verifies that APIs never reference Google."""
1281
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001282 if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001283 error(clazz, None, None, "Must never reference Google")
1284
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001285 for test in clazz.ctors, clazz.fields, clazz.methods:
1286 for t in test:
1287 if GOOGLE_IGNORECASE.search(t.raw) is not None:
1288 error(clazz, t, None, "Must never reference Google")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001289
1290
1291def verify_bitset(clazz):
1292 """Verifies that we avoid using heavy BitSet."""
1293
1294 for f in clazz.fields:
1295 if f.typ == "java.util.BitSet":
1296 error(clazz, f, None, "Field type must not be heavy BitSet")
1297
1298 for m in clazz.methods:
1299 if m.typ == "java.util.BitSet":
1300 error(clazz, m, None, "Return type must not be heavy BitSet")
1301 for arg in m.args:
1302 if arg == "java.util.BitSet":
1303 error(clazz, m, None, "Argument type must not be heavy BitSet")
1304
1305
1306def verify_manager(clazz):
1307 """Verifies that FooManager is only obtained from Context."""
1308
1309 if not clazz.name.endswith("Manager"): return
1310
1311 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001312 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001313
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001314 for m in clazz.methods:
1315 if m.typ == clazz.fullname:
1316 error(clazz, m, None, "Managers must always be obtained from Context")
1317
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001318
1319def verify_boxed(clazz):
1320 """Verifies that methods avoid boxed primitives."""
1321
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001322 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 -08001323
1324 for c in clazz.ctors:
1325 for arg in c.args:
1326 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001327 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001328
1329 for f in clazz.fields:
1330 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001331 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001332
1333 for m in clazz.methods:
1334 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001335 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001336 for arg in m.args:
1337 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001338 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001339
1340
1341def verify_static_utils(clazz):
1342 """Verifies that helper classes can't be constructed."""
1343 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001344 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001345
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001346 # Only care about classes with default constructors
1347 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
1348 test = []
1349 test.extend(clazz.fields)
1350 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001351
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001352 if len(test) == 0: return
1353 for t in test:
1354 if "static" not in t.split:
1355 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001356
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001357 error(clazz, None, None, "Fully-static utility classes must not have constructor")
1358
1359
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001360def verify_overload_args(clazz):
1361 """Verifies that method overloads add new arguments at the end."""
1362 if clazz.fullname.startswith("android.opengl"): return
1363
1364 overloads = collections.defaultdict(list)
1365 for m in clazz.methods:
1366 if "deprecated" in m.split: continue
1367 overloads[m.name].append(m)
1368
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001369 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001370 if len(methods) <= 1: continue
1371
1372 # Look for arguments common across all overloads
1373 def cluster(args):
1374 count = collections.defaultdict(int)
1375 res = set()
1376 for i in range(len(args)):
1377 a = args[i]
1378 res.add("%s#%d" % (a, count[a]))
1379 count[a] += 1
1380 return res
1381
1382 common_args = cluster(methods[0].args)
1383 for m in methods:
1384 common_args = common_args & cluster(m.args)
1385
1386 if len(common_args) == 0: continue
1387
1388 # Require that all common arguments are present at start of signature
1389 locked_sig = None
1390 for m in methods:
1391 sig = m.args[0:len(common_args)]
1392 if not common_args.issubset(cluster(sig)):
1393 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1394 elif not locked_sig:
1395 locked_sig = sig
1396 elif locked_sig != sig:
1397 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1398
1399
1400def verify_callback_handlers(clazz):
1401 """Verifies that methods adding listener/callback have overload
1402 for specifying delivery thread."""
1403
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001404 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001405 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001406 "animation",
1407 "view",
1408 "graphics",
1409 "transition",
1410 "widget",
1411 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001412 ]
1413 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001414 if s in clazz.pkg.name_path: return
1415 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001416
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001417 # Ignore UI classes which assume main thread
1418 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1419 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1420 if s in clazz.fullname: return
1421 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1422 for s in ["Loader"]:
1423 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001424
1425 found = {}
1426 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001427 examine = clazz.ctors + clazz.methods
1428 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001429 if m.name.startswith("unregister"): continue
1430 if m.name.startswith("remove"): continue
1431 if re.match("on[A-Z]+", m.name): continue
1432
1433 by_name[m.name].append(m)
1434
1435 for a in m.args:
1436 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1437 found[m.name] = m
1438
1439 for f in found.values():
1440 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001441 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001442 for m in by_name[f.name]:
1443 if "android.os.Handler" in m.args:
1444 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001445 if "java.util.concurrent.Executor" in m.args:
1446 takes_exec = True
1447 if not takes_exec:
1448 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001449
1450
1451def verify_context_first(clazz):
1452 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001453 examine = clazz.ctors + clazz.methods
1454 for m in examine:
1455 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001456 if "android.content.Context" in m.args[1:]:
1457 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001458 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1459 if "android.content.ContentResolver" in m.args[1:]:
1460 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001461
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001462
1463def verify_listener_last(clazz):
1464 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1465 examine = clazz.ctors + clazz.methods
1466 for m in examine:
1467 if "Listener" in m.name or "Callback" in m.name: continue
1468 found = False
1469 for a in m.args:
1470 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1471 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001472 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001473 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1474
1475
1476def verify_resource_names(clazz):
1477 """Verifies that resource names have consistent case."""
1478 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1479
1480 # Resources defined by files are foo_bar_baz
1481 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1482 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001483 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1484 if f.name.startswith("config_"):
1485 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1486
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001487 if re.match("[a-z1-9_]+$", f.name): continue
1488 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1489
1490 # Resources defined inside files are fooBarBaz
1491 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1492 for f in clazz.fields:
1493 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1494 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1495 if re.match("state_[a-z_]*$", f.name): continue
1496
1497 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1498 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1499
1500 # Styles are FooBar_Baz
1501 if clazz.name in ["style"]:
1502 for f in clazz.fields:
1503 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1504 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001505
1506
Jeff Sharkey331279b2016-02-29 16:02:02 -07001507def verify_files(clazz):
1508 """Verifies that methods accepting File also accept streams."""
1509
1510 has_file = set()
1511 has_stream = set()
1512
1513 test = []
1514 test.extend(clazz.ctors)
1515 test.extend(clazz.methods)
1516
1517 for m in test:
1518 if "java.io.File" in m.args:
1519 has_file.add(m)
1520 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:
1521 has_stream.add(m.name)
1522
1523 for m in has_file:
1524 if m.name not in has_stream:
1525 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1526
1527
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001528def verify_manager_list(clazz):
1529 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1530
1531 if not clazz.name.endswith("Manager"): return
1532
1533 for m in clazz.methods:
1534 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1535 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1536
1537
Jeff Sharkey26c80902016-12-21 13:41:17 -07001538def verify_abstract_inner(clazz):
1539 """Verifies that abstract inner classes are static."""
1540
1541 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
Adrian Roosb787c182019-01-03 18:54:33 +01001542 if "abstract" in clazz.split and "static" not in clazz.split:
Jeff Sharkey26c80902016-12-21 13:41:17 -07001543 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1544
1545
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001546def verify_runtime_exceptions(clazz):
1547 """Verifies that runtime exceptions aren't listed in throws."""
1548
1549 banned = [
1550 "java.lang.NullPointerException",
1551 "java.lang.ClassCastException",
1552 "java.lang.IndexOutOfBoundsException",
1553 "java.lang.reflect.UndeclaredThrowableException",
1554 "java.lang.reflect.MalformedParametersException",
1555 "java.lang.reflect.MalformedParameterizedTypeException",
1556 "java.lang.invoke.WrongMethodTypeException",
1557 "java.lang.EnumConstantNotPresentException",
1558 "java.lang.IllegalMonitorStateException",
1559 "java.lang.SecurityException",
1560 "java.lang.UnsupportedOperationException",
1561 "java.lang.annotation.AnnotationTypeMismatchException",
1562 "java.lang.annotation.IncompleteAnnotationException",
1563 "java.lang.TypeNotPresentException",
1564 "java.lang.IllegalStateException",
1565 "java.lang.ArithmeticException",
1566 "java.lang.IllegalArgumentException",
1567 "java.lang.ArrayStoreException",
1568 "java.lang.NegativeArraySizeException",
1569 "java.util.MissingResourceException",
1570 "java.util.EmptyStackException",
1571 "java.util.concurrent.CompletionException",
1572 "java.util.concurrent.RejectedExecutionException",
1573 "java.util.IllformedLocaleException",
1574 "java.util.ConcurrentModificationException",
1575 "java.util.NoSuchElementException",
1576 "java.io.UncheckedIOException",
1577 "java.time.DateTimeException",
1578 "java.security.ProviderException",
1579 "java.nio.BufferUnderflowException",
1580 "java.nio.BufferOverflowException",
1581 ]
1582
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001583 examine = clazz.ctors + clazz.methods
1584 for m in examine:
1585 for t in m.throws:
1586 if t in banned:
1587 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001588
1589
1590def verify_error(clazz):
1591 """Verifies that we always use Exception instead of Error."""
1592 if not clazz.extends: return
1593 if clazz.extends.endswith("Error"):
1594 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1595 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1596 error(clazz, None, None, "Exceptions must be named FooException")
1597
1598
1599def verify_units(clazz):
1600 """Verifies that we use consistent naming for units."""
1601
1602 # If we find K, recommend replacing with V
1603 bad = {
1604 "Ns": "Nanos",
1605 "Ms": "Millis or Micros",
1606 "Sec": "Seconds", "Secs": "Seconds",
1607 "Hr": "Hours", "Hrs": "Hours",
1608 "Mo": "Months", "Mos": "Months",
1609 "Yr": "Years", "Yrs": "Years",
1610 "Byte": "Bytes", "Space": "Bytes",
1611 }
1612
1613 for m in clazz.methods:
1614 if m.typ not in ["short","int","long"]: continue
1615 for k, v in bad.iteritems():
1616 if m.name.endswith(k):
1617 error(clazz, m, None, "Expected method name units to be " + v)
1618 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1619 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1620 if m.name.endswith("Seconds"):
1621 error(clazz, m, None, "Returned time values must be in milliseconds")
1622
1623 for m in clazz.methods:
1624 typ = m.typ
1625 if typ == "void":
1626 if len(m.args) != 1: continue
1627 typ = m.args[0]
1628
1629 if m.name.endswith("Fraction") and typ != "float":
1630 error(clazz, m, None, "Fractions must use floats")
1631 if m.name.endswith("Percentage") and typ != "int":
1632 error(clazz, m, None, "Percentage must use ints")
1633
1634
1635def verify_closable(clazz):
1636 """Verifies that classes are AutoClosable."""
Adrian Roosb787c182019-01-03 18:54:33 +01001637 if clazz.implements == "java.lang.AutoCloseable": return
1638 if clazz.implements == "java.io.Closeable": return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001639
1640 for m in clazz.methods:
1641 if len(m.args) > 0: continue
1642 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1643 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1644 return
1645
1646
Jake Wharton9e6738f2017-08-23 11:59:55 -04001647def verify_member_name_not_kotlin_keyword(clazz):
1648 """Prevent method names which are keywords in Kotlin."""
1649
1650 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1651 # This list does not include Java keywords as those are already impossible to use.
1652 keywords = [
1653 'as',
1654 'fun',
1655 'in',
1656 'is',
1657 'object',
1658 'typealias',
1659 'val',
1660 'var',
1661 'when',
1662 ]
1663
1664 for m in clazz.methods:
1665 if m.name in keywords:
1666 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1667 for f in clazz.fields:
1668 if f.name in keywords:
1669 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1670
1671
1672def verify_method_name_not_kotlin_operator(clazz):
1673 """Warn about method names which become operators in Kotlin."""
1674
1675 binary = set()
1676
1677 def unique_binary_op(m, op):
1678 if op in binary:
1679 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1680 binary.add(op)
1681
1682 for m in clazz.methods:
Adrian Roosd1e38922019-01-14 15:44:15 +01001683 if 'static' in m.split or 'operator' in m.split:
Jake Wharton9e6738f2017-08-23 11:59:55 -04001684 continue
1685
1686 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1687 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1688 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1689
1690 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1691 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1692 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1693 # practical way of checking that relationship here.
1694 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1695
1696 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1697 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1698 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1699 unique_binary_op(m, m.name)
1700
1701 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1702 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1703 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1704
1705 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1706 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1707 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1708
1709 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1710 if m.name == 'invoke':
1711 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1712
1713 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1714 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1715 and len(m.args) == 1 \
1716 and m.typ == 'void':
1717 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1718 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1719
1720
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001721def verify_collections_over_arrays(clazz):
1722 """Warn that [] should be Collections."""
1723
Adrian Roosb787c182019-01-03 18:54:33 +01001724 if "@interface" in clazz.split:
1725 return
1726
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001727 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1728 for m in clazz.methods:
1729 if m.typ.endswith("[]") and m.typ not in safe:
1730 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1731 for arg in m.args:
1732 if arg.endswith("[]") and arg not in safe:
1733 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1734
1735
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001736def verify_user_handle(clazz):
1737 """Methods taking UserHandle should be ForUser or AsUser."""
1738 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1739 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1740 if clazz.fullname == "android.content.pm.LauncherApps": return
1741 if clazz.fullname == "android.os.UserHandle": return
1742 if clazz.fullname == "android.os.UserManager": return
1743
1744 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001745 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001746
1747 has_arg = "android.os.UserHandle" in m.args
1748 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1749
1750 if clazz.fullname.endswith("Manager") and has_arg:
1751 warn(clazz, m, None, "When a method overload is needed to target a specific "
1752 "UserHandle, callers should be directed to use "
1753 "Context.createPackageContextAsUser() and re-obtain the relevant "
1754 "Manager, and no new API should be added")
1755 elif has_arg and not has_name:
1756 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1757 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001758
1759
1760def verify_params(clazz):
1761 """Parameter classes should be 'Params'."""
1762 if clazz.name.endswith("Params"): return
1763 if clazz.fullname == "android.app.ActivityOptions": return
1764 if clazz.fullname == "android.app.BroadcastOptions": return
1765 if clazz.fullname == "android.os.Bundle": return
1766 if clazz.fullname == "android.os.BaseBundle": return
1767 if clazz.fullname == "android.os.PersistableBundle": return
1768
1769 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1770 for b in bad:
1771 if clazz.name.endswith(b):
1772 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1773
1774
1775def verify_services(clazz):
1776 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1777 if clazz.fullname != "android.content.Context": return
1778
1779 for f in clazz.fields:
1780 if f.typ != "java.lang.String": continue
1781 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1782 if found:
1783 expected = found.group(1).lower()
1784 if f.value != expected:
1785 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1786
1787
1788def verify_tense(clazz):
1789 """Verify tenses of method names."""
1790 if clazz.fullname.startswith("android.opengl"): return
1791
1792 for m in clazz.methods:
1793 if m.name.endswith("Enable"):
1794 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1795
1796
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001797def verify_icu(clazz):
1798 """Verifies that richer ICU replacements are used."""
1799 better = {
1800 "java.util.TimeZone": "android.icu.util.TimeZone",
1801 "java.util.Calendar": "android.icu.util.Calendar",
1802 "java.util.Locale": "android.icu.util.ULocale",
1803 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1804 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1805 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1806 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1807 "java.lang.Character": "android.icu.lang.UCharacter",
1808 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1809 "java.text.Collator": "android.icu.text.Collator",
1810 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1811 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1812 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1813 "java.text.DateFormat": "android.icu.text.DateFormat",
1814 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1815 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1816 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1817 }
1818
1819 for m in clazz.ctors + clazz.methods:
1820 types = []
1821 types.extend(m.typ)
1822 types.extend(m.args)
1823 for arg in types:
1824 if arg in better:
1825 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1826
1827
1828def verify_clone(clazz):
1829 """Verify that clone() isn't implemented; see EJ page 61."""
1830 for m in clazz.methods:
1831 if m.name == "clone":
1832 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1833
1834
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001835def verify_pfd(clazz):
1836 """Verify that android APIs use PFD over FD."""
1837 examine = clazz.ctors + clazz.methods
1838 for m in examine:
1839 if m.typ == "java.io.FileDescriptor":
1840 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1841 if m.typ == "int":
1842 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1843 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1844 for arg in m.args:
1845 if arg == "java.io.FileDescriptor":
1846 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1847
1848 for f in clazz.fields:
1849 if f.typ == "java.io.FileDescriptor":
1850 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1851
1852
1853def verify_numbers(clazz):
1854 """Discourage small numbers types like short and byte."""
1855
1856 discouraged = ["short","byte"]
1857
1858 for c in clazz.ctors:
1859 for arg in c.args:
1860 if arg in discouraged:
1861 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1862
1863 for f in clazz.fields:
1864 if f.typ in discouraged:
1865 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1866
1867 for m in clazz.methods:
1868 if m.typ in discouraged:
1869 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1870 for arg in m.args:
1871 if arg in discouraged:
1872 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1873
1874
1875def verify_singleton(clazz):
1876 """Catch singleton objects with constructors."""
1877
1878 singleton = False
1879 for m in clazz.methods:
1880 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1881 singleton = True
1882
1883 if singleton:
1884 for c in clazz.ctors:
1885 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1886
1887
1888
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001889def is_interesting(clazz):
1890 """Test if given class is interesting from an Android PoV."""
1891
1892 if clazz.pkg.name.startswith("java"): return False
1893 if clazz.pkg.name.startswith("junit"): return False
1894 if clazz.pkg.name.startswith("org.apache"): return False
1895 if clazz.pkg.name.startswith("org.xml"): return False
1896 if clazz.pkg.name.startswith("org.json"): return False
1897 if clazz.pkg.name.startswith("org.w3c"): return False
1898 if clazz.pkg.name.startswith("android.icu."): return False
1899 return True
1900
1901
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001902def examine_clazz(clazz):
1903 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001904
1905 notice(clazz)
1906
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001907 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001908
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001909 verify_constants(clazz)
1910 verify_enums(clazz)
1911 verify_class_names(clazz)
1912 verify_method_names(clazz)
1913 verify_callbacks(clazz)
1914 verify_listeners(clazz)
1915 verify_actions(clazz)
1916 verify_extras(clazz)
1917 verify_equals(clazz)
1918 verify_parcelable(clazz)
1919 verify_protected(clazz)
1920 verify_fields(clazz)
1921 verify_register(clazz)
1922 verify_sync(clazz)
1923 verify_intent_builder(clazz)
1924 verify_helper_classes(clazz)
1925 verify_builder(clazz)
1926 verify_aidl(clazz)
1927 verify_internal(clazz)
1928 verify_layering(clazz)
1929 verify_boolean(clazz)
1930 verify_collections(clazz)
1931 verify_flags(clazz)
1932 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001933 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001934 verify_bitset(clazz)
1935 verify_manager(clazz)
1936 verify_boxed(clazz)
1937 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001938 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001939 verify_callback_handlers(clazz)
1940 verify_context_first(clazz)
1941 verify_listener_last(clazz)
1942 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001943 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001944 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001945 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001946 verify_runtime_exceptions(clazz)
1947 verify_error(clazz)
1948 verify_units(clazz)
1949 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001950 verify_member_name_not_kotlin_keyword(clazz)
1951 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001952 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001953 verify_user_handle(clazz)
1954 verify_params(clazz)
1955 verify_services(clazz)
1956 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001957 verify_icu(clazz)
1958 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001959 verify_pfd(clazz)
1960 verify_numbers(clazz)
1961 verify_singleton(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001962
1963
Adrian Roos038a0292018-12-19 17:11:21 +01001964def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001965 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001966 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001967 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001968 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01001969 _parse_stream(stream, examine_clazz, base_f=base_stream,
1970 in_classes_with_base=in_classes_with_base,
1971 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001972 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001973
1974
1975def examine_api(api):
1976 """Find all style issues in the given parsed API."""
1977 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001978 failures = {}
1979 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001980 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001981 return failures
1982
1983
Jeff Sharkey037458a2014-09-04 15:46:20 -07001984def verify_compat(cur, prev):
1985 """Find any incompatible API changes between two levels."""
1986 global failures
1987
1988 def class_exists(api, test):
1989 return test.fullname in api
1990
1991 def ctor_exists(api, clazz, test):
1992 for m in clazz.ctors:
1993 if m.ident == test.ident: return True
1994 return False
1995
1996 def all_methods(api, clazz):
1997 methods = list(clazz.methods)
1998 if clazz.extends is not None:
1999 methods.extend(all_methods(api, api[clazz.extends]))
2000 return methods
2001
2002 def method_exists(api, clazz, test):
2003 methods = all_methods(api, clazz)
2004 for m in methods:
2005 if m.ident == test.ident: return True
2006 return False
2007
2008 def field_exists(api, clazz, test):
2009 for f in clazz.fields:
2010 if f.ident == test.ident: return True
2011 return False
2012
2013 failures = {}
2014 for key in sorted(prev.keys()):
2015 prev_clazz = prev[key]
2016
2017 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002018 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002019 continue
2020
2021 cur_clazz = cur[key]
2022
2023 for test in prev_clazz.ctors:
2024 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002025 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002026
2027 methods = all_methods(prev, prev_clazz)
2028 for test in methods:
2029 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002030 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002031
2032 for test in prev_clazz.fields:
2033 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002034 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002035
2036 return failures
2037
2038
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002039def show_deprecations_at_birth(cur, prev):
2040 """Show API deprecations at birth."""
2041 global failures
2042
2043 # Remove all existing things so we're left with new
2044 for prev_clazz in prev.values():
2045 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002046 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002047
2048 sigs = { i.ident: i for i in prev_clazz.ctors }
2049 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
2050 sigs = { i.ident: i for i in prev_clazz.methods }
2051 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
2052 sigs = { i.ident: i for i in prev_clazz.fields }
2053 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
2054
2055 # Forget about class entirely when nothing new
2056 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
2057 del cur[prev_clazz.fullname]
2058
2059 for clazz in cur.values():
Adrian Roosb787c182019-01-03 18:54:33 +01002060 if "deprecated" in clazz.split and not clazz.fullname in prev:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002061 error(clazz, None, None, "Found API deprecation at birth")
2062
2063 for i in clazz.ctors + clazz.methods + clazz.fields:
Adrian Roosb787c182019-01-03 18:54:33 +01002064 if "deprecated" in i.split:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002065 error(clazz, i, None, "Found API deprecation at birth")
2066
2067 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
2068 format(reset=True)))
2069 for f in sorted(failures):
2070 print failures[f]
2071 print
2072
2073
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002074def show_stats(cur, prev):
2075 """Show API stats."""
2076
2077 stats = collections.defaultdict(int)
2078 for cur_clazz in cur.values():
2079 if not is_interesting(cur_clazz): continue
2080
2081 if cur_clazz.fullname not in prev:
2082 stats['new_classes'] += 1
2083 stats['new_ctors'] += len(cur_clazz.ctors)
2084 stats['new_methods'] += len(cur_clazz.methods)
2085 stats['new_fields'] += len(cur_clazz.fields)
2086 else:
2087 prev_clazz = prev[cur_clazz.fullname]
2088
2089 sigs = { i.ident: i for i in prev_clazz.ctors }
2090 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
2091 sigs = { i.ident: i for i in prev_clazz.methods }
2092 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
2093 sigs = { i.ident: i for i in prev_clazz.fields }
2094 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
2095
2096 if ctors + methods + fields > 0:
2097 stats['extend_classes'] += 1
2098 stats['extend_ctors'] += ctors
2099 stats['extend_methods'] += methods
2100 stats['extend_fields'] += fields
2101
2102 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
2103 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
2104
2105
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002106if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002107 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
2108 patterns. It ignores lint messages from a previous API level, if provided.")
2109 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
2110 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
2111 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01002112 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
2113 help="The base current.txt to use when examining system-current.txt or"
2114 " test-current.txt")
2115 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
2116 help="The base previous.txt to use when examining system-previous.txt or"
2117 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002118 parser.add_argument("--no-color", action='store_const', const=True,
2119 help="Disable terminal colors")
2120 parser.add_argument("--allow-google", action='store_const', const=True,
2121 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002122 parser.add_argument("--show-noticed", action='store_const', const=True,
2123 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002124 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
2125 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002126 parser.add_argument("--show-stats", action='store_const', const=True,
2127 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002128 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002129
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002130 if args['no_color']:
2131 USE_COLOR = False
2132
2133 if args['allow_google']:
2134 ALLOW_GOOGLE = True
2135
2136 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002137 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002138 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002139 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002140
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002141 if args['show_deprecations_at_birth']:
2142 with current_file as f:
2143 cur = _parse_stream(f)
2144 with previous_file as f:
2145 prev = _parse_stream(f)
2146 show_deprecations_at_birth(cur, prev)
2147 sys.exit()
2148
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002149 if args['show_stats']:
2150 with current_file as f:
2151 cur = _parse_stream(f)
2152 with previous_file as f:
2153 prev = _parse_stream(f)
2154 show_stats(cur, prev)
2155 sys.exit()
2156
Adrian Roos038a0292018-12-19 17:11:21 +01002157 classes_with_base = []
2158
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002159 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002160 if base_current_file:
2161 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002162 cur_fail, cur_noticed = examine_stream(f, base_f,
2163 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002164 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002165 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
2166
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002167 if not previous_file is None:
2168 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002169 if base_previous_file:
2170 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002171 prev_fail, prev_noticed = examine_stream(f, base_f,
2172 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002173 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002174 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002175
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002176 # ignore errors from previous API level
2177 for p in prev_fail:
2178 if p in cur_fail:
2179 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002180
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002181 # ignore classes unchanged from previous API level
2182 for k, v in prev_noticed.iteritems():
2183 if k in cur_noticed and v == cur_noticed[k]:
2184 del cur_noticed[k]
2185
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002186 """
2187 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002188 # look for compatibility issues
2189 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002190
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002191 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2192 for f in sorted(compat_fail):
2193 print compat_fail[f]
2194 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002195 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002196
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002197 if args['show_noticed'] and len(cur_noticed) != 0:
2198 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2199 for f in sorted(cur_noticed.keys()):
2200 print f
2201 print
2202
Jason Monk53b2a732017-11-10 15:43:17 -05002203 if len(cur_fail) != 0:
2204 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2205 for f in sorted(cur_fail):
2206 print cur_fail[f]
2207 print
2208 sys.exit(77)