blob: 75c3eba7765bc182a85b93abdd2309a8b703ce48 [file] [log] [blame]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001#!/usr/bin/env python
2
3# Copyright (C) 2014 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the 'License');
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an 'AS IS' BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Enforces common Android public API design patterns. It ignores lint messages from
19a previous API level, if provided.
20
21Usage: apilint.py current.txt
22Usage: apilint.py current.txt previous.txt
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070023
24You can also splice in blame details like this:
25$ git blame api/current.txt -t -e > /tmp/currentblame.txt
26$ apilint.py /tmp/currentblame.txt previous.txt --no-color
Jeff Sharkey8190f4882014-08-28 12:24:07 -070027"""
28
Adrian Roos1f1b6a82019-01-05 20:09:38 +010029import re, sys, collections, traceback, argparse, itertools
Jeff Sharkey8190f4882014-08-28 12:24:07 -070030
31
32BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
33
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070034ALLOW_GOOGLE = False
35USE_COLOR = True
36
Jeff Sharkey8190f4882014-08-28 12:24:07 -070037def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
38 # manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070039 if not USE_COLOR: return ""
Jeff Sharkey8190f4882014-08-28 12:24:07 -070040 codes = []
41 if reset: codes.append("0")
42 else:
43 if not fg is None: codes.append("3%d" % (fg))
44 if not bg is None:
45 if not bright: codes.append("4%d" % (bg))
46 else: codes.append("10%d" % (bg))
47 if bold: codes.append("1")
48 elif dim: codes.append("2")
49 else: codes.append("22")
50 return "\033[%sm" % (";".join(codes))
51
52
53class Field():
Adrian Roosb787c182019-01-03 18:54:33 +010054 def __init__(self, clazz, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070055 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070056 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070057 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070058 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -070059
Adrian Roosb787c182019-01-03 18:54:33 +010060 if sig_format == 2:
61 V2LineParser(raw).parse_into_field(self)
62 elif sig_format == 1:
63 # drop generics for now; may need multiple passes
64 raw = re.sub("<[^<]+?>", "", raw)
65 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -060066
Adrian Roosb787c182019-01-03 18:54:33 +010067 raw = raw.split()
68 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070069
Adrian Roosb787c182019-01-03 18:54:33 +010070 for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
71 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070072
Adrian Roosb787c182019-01-03 18:54:33 +010073 # ignore annotations for now
74 raw = [ r for r in raw if not r.startswith("@") ]
Jeff Sharkey40d67f42018-07-17 13:29:40 -060075
Adrian Roosb787c182019-01-03 18:54:33 +010076 self.typ = raw[0]
77 self.name = raw[1].strip(";")
78 if len(raw) >= 4 and raw[2] == "=":
79 self.value = raw[3].strip(';"')
80 else:
81 self.value = None
82
83 self.ident = "-".join((self.typ, self.name, self.value or ""))
Jeff Sharkey037458a2014-09-04 15:46:20 -070084
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -070085 def __hash__(self):
86 return hash(self.raw)
87
Jeff Sharkey8190f4882014-08-28 12:24:07 -070088 def __repr__(self):
89 return self.raw
90
Jeff Sharkey8190f4882014-08-28 12:24:07 -070091class Method():
Adrian Roosb787c182019-01-03 18:54:33 +010092 def __init__(self, clazz, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070093 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070094 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070095 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070096 self.blame = blame
97
Adrian Roosb787c182019-01-03 18:54:33 +010098 if sig_format == 2:
99 V2LineParser(raw).parse_into_method(self)
100 elif sig_format == 1:
101 # drop generics for now; may need multiple passes
102 raw = re.sub("<[^<]+?>", "", raw)
103 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700104
Adrian Roosb787c182019-01-03 18:54:33 +0100105 # handle each clause differently
106 raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600107
Adrian Roosb787c182019-01-03 18:54:33 +0100108 # parse prefixes
109 raw = re.split("[\s]+", raw_prefix)
110 for r in ["", ";"]:
111 while r in raw: raw.remove(r)
112 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700113
Adrian Roosb787c182019-01-03 18:54:33 +0100114 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator", "synchronized"]:
115 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700116
Adrian Roosb787c182019-01-03 18:54:33 +0100117 self.typ = raw[0]
118 self.name = raw[1]
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600119
Adrian Roosb787c182019-01-03 18:54:33 +0100120 # parse args
121 self.args = []
122 for arg in re.split(",\s*", raw_args):
123 arg = re.split("\s", arg)
124 # ignore annotations for now
125 arg = [ a for a in arg if not a.startswith("@") ]
126 if len(arg[0]) > 0:
127 self.args.append(arg[0])
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600128
Adrian Roosb787c182019-01-03 18:54:33 +0100129 # parse throws
130 self.throws = []
131 for throw in re.split(",\s*", raw_throws):
132 self.throws.append(throw)
133 else:
134 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600135
Adrian Roosb787c182019-01-03 18:54:33 +0100136 self.ident = "-".join((self.typ, self.name, "-".join(self.args)))
137
138 def sig_matches(self, typ, name, args):
139 return typ == self.typ and name == self.name and args == self.args
Jeff Sharkey037458a2014-09-04 15:46:20 -0700140
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700141 def __hash__(self):
142 return hash(self.raw)
143
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700144 def __repr__(self):
145 return self.raw
146
147
148class Class():
Adrian Roosb787c182019-01-03 18:54:33 +0100149 def __init__(self, pkg, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700150 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700151 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700152 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700153 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700154 self.ctors = []
155 self.fields = []
156 self.methods = []
157
Adrian Roosb787c182019-01-03 18:54:33 +0100158 if sig_format == 2:
159 V2LineParser(raw).parse_into_class(self)
160 elif sig_format == 1:
161 # drop generics for now; may need multiple passes
162 raw = re.sub("<[^<]+?>", "", raw)
163 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600164
Adrian Roosb787c182019-01-03 18:54:33 +0100165 raw = raw.split()
166 self.split = list(raw)
167 if "class" in raw:
168 self.fullname = raw[raw.index("class")+1]
169 elif "interface" in raw:
170 self.fullname = raw[raw.index("interface")+1]
171 elif "@interface" in raw:
172 self.fullname = raw[raw.index("@interface")+1]
173 else:
174 raise ValueError("Funky class type %s" % (self.raw))
Jeff Sharkey037458a2014-09-04 15:46:20 -0700175
Adrian Roosb787c182019-01-03 18:54:33 +0100176 if "extends" in raw:
177 self.extends = raw[raw.index("extends")+1]
178 else:
179 self.extends = None
180
181 if "implements" in raw:
182 self.implements = raw[raw.index("implements")+1]
183 else:
184 self.implements = None
Jeff Sharkey037458a2014-09-04 15:46:20 -0700185 else:
Adrian Roosb787c182019-01-03 18:54:33 +0100186 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700187
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700188 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800189 self.fullname_path = self.fullname.split(".")
190
Adrian Roosb787c182019-01-03 18:54:33 +0100191 if self.extends is not None:
192 self.extends_path = self.extends.split(".")
193 else:
194 self.extends_path = []
195
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700196 self.name = self.fullname[self.fullname.rindex(".")+1:]
197
Adrian Roos6eb57b02018-12-13 22:08:29 +0100198 def merge_from(self, other):
199 self.ctors.extend(other.ctors)
200 self.fields.extend(other.fields)
201 self.methods.extend(other.methods)
202
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700203 def __hash__(self):
204 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
205
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700206 def __repr__(self):
207 return self.raw
208
209
210class Package():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700211 def __init__(self, line, raw, blame):
212 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700213 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700214 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700215
216 raw = raw.split()
217 self.name = raw[raw.index("package")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800218 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700219
220 def __repr__(self):
221 return self.raw
222
Adrian Roose5eeae72019-01-04 20:10:06 +0100223class V2Tokenizer(object):
224 __slots__ = ["raw"]
225
Adrian Roos5cdfb692019-01-05 22:04:55 +0100226 DELIMITER = re.compile(r'\s+|[()@<>;,={}/"!?]|\[\]|\.\.\.')
Adrian Roosb787c182019-01-03 18:54:33 +0100227 STRING_SPECIAL = re.compile(r'["\\]')
228
229 def __init__(self, raw):
230 self.raw = raw
Adrian Roosb787c182019-01-03 18:54:33 +0100231
Adrian Roose5eeae72019-01-04 20:10:06 +0100232 def tokenize(self):
233 tokens = []
234 current = 0
235 raw = self.raw
236 length = len(raw)
Adrian Roosb787c182019-01-03 18:54:33 +0100237
Adrian Roose5eeae72019-01-04 20:10:06 +0100238 while current < length:
239 while current < length:
240 start = current
241 match = V2Tokenizer.DELIMITER.search(raw, start)
242 if match is not None:
243 match_start = match.start()
244 if match_start == current:
245 end = match.end()
246 else:
247 end = match_start
248 else:
249 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100250
Adrian Roose5eeae72019-01-04 20:10:06 +0100251 token = raw[start:end]
252 current = end
Adrian Roosb787c182019-01-03 18:54:33 +0100253
Adrian Roose5eeae72019-01-04 20:10:06 +0100254 if token == "" or token[0] == " ":
255 continue
256 else:
257 break
Adrian Roosb787c182019-01-03 18:54:33 +0100258
Adrian Roose5eeae72019-01-04 20:10:06 +0100259 if token == "@":
260 if raw[start:start+11] == "@interface ":
261 current = start + 11
262 tokens.append("@interface")
263 continue
264 elif token == '/':
265 if raw[start:start+2] == "//":
266 current = length
267 continue
268 elif token == '"':
269 current, string_token = self.tokenize_string(raw, length, current)
270 tokens.append(token + string_token)
271 continue
Adrian Roosb787c182019-01-03 18:54:33 +0100272
Adrian Roose5eeae72019-01-04 20:10:06 +0100273 tokens.append(token)
Adrian Roosb787c182019-01-03 18:54:33 +0100274
Adrian Roose5eeae72019-01-04 20:10:06 +0100275 return tokens
Adrian Roosb787c182019-01-03 18:54:33 +0100276
Adrian Roose5eeae72019-01-04 20:10:06 +0100277 def tokenize_string(self, raw, length, current):
278 start = current
279 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100280 while start < end:
Adrian Roose5eeae72019-01-04 20:10:06 +0100281 match = V2Tokenizer.STRING_SPECIAL.search(raw, start)
Adrian Roosb787c182019-01-03 18:54:33 +0100282 if match:
283 if match.group() == '"':
284 end = match.end()
285 break
286 elif match.group() == '\\':
287 # ignore whatever is after the slash
288 start += 2
289 else:
290 raise ValueError("Unexpected match: `%s`" % (match.group()))
291 else:
Adrian Roose5eeae72019-01-04 20:10:06 +0100292 raise ValueError("Unexpected EOF tokenizing string: `%s`" % (raw[current - 1:],))
Adrian Roosb787c182019-01-03 18:54:33 +0100293
Adrian Roose5eeae72019-01-04 20:10:06 +0100294 token = raw[current:end]
295 return end, token
Adrian Roosb787c182019-01-03 18:54:33 +0100296
Adrian Roose5eeae72019-01-04 20:10:06 +0100297class V2LineParser(object):
298 __slots__ = ["tokenized", "current", "len"]
299
Adrian Roos258c5722019-01-21 15:43:15 +0100300 FIELD_KINDS = ("field", "property", "enum_constant")
Adrian Roosd1e38922019-01-14 15:44:15 +0100301 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 +0100302 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())
303
304 def __init__(self, raw):
Adrian Roose5eeae72019-01-04 20:10:06 +0100305 self.tokenized = V2Tokenizer(raw).tokenize()
Adrian Roosb787c182019-01-03 18:54:33 +0100306 self.current = 0
Adrian Roose5eeae72019-01-04 20:10:06 +0100307 self.len = len(self.tokenized)
Adrian Roosb787c182019-01-03 18:54:33 +0100308
309 def parse_into_method(self, method):
310 method.split = []
311 kind = self.parse_one_of("ctor", "method")
312 method.split.append(kind)
313 annotations = self.parse_annotations()
314 method.split.extend(self.parse_modifiers())
315 self.parse_matching_paren("<", ">")
316 if "@Deprecated" in annotations:
317 method.split.append("deprecated")
318 if kind == "ctor":
319 method.typ = "ctor"
320 else:
321 method.typ = self.parse_type()
322 method.split.append(method.typ)
323 method.name = self.parse_name()
324 method.split.append(method.name)
325 self.parse_token("(")
326 method.args = self.parse_args()
327 self.parse_token(")")
328 method.throws = self.parse_throws()
329 if "@interface" in method.clazz.split:
330 self.parse_annotation_default()
331 self.parse_token(";")
332 self.parse_eof()
333
334 def parse_into_class(self, clazz):
335 clazz.split = []
336 annotations = self.parse_annotations()
337 if "@Deprecated" in annotations:
338 clazz.split.append("deprecated")
339 clazz.split.extend(self.parse_modifiers())
340 kind = self.parse_one_of("class", "interface", "@interface", "enum")
341 if kind == "enum":
342 # enums are implicitly final
343 clazz.split.append("final")
344 clazz.split.append(kind)
345 clazz.fullname = self.parse_name()
346 self.parse_matching_paren("<", ">")
347 extends = self.parse_extends()
348 clazz.extends = extends[0] if extends else None
349 implements = self.parse_implements()
350 clazz.implements = implements[0] if implements else None
351 # The checks assume that interfaces are always found in implements, which isn't true for
352 # subinterfaces.
353 if not implements and "interface" in clazz.split:
354 clazz.implements = clazz.extends
355 self.parse_token("{")
356 self.parse_eof()
357
358 def parse_into_field(self, field):
Adrian Roos258c5722019-01-21 15:43:15 +0100359 kind = self.parse_one_of(*V2LineParser.FIELD_KINDS)
Adrian Roosb787c182019-01-03 18:54:33 +0100360 field.split = [kind]
361 annotations = self.parse_annotations()
362 if "@Deprecated" in annotations:
363 field.split.append("deprecated")
364 field.split.extend(self.parse_modifiers())
365 field.typ = self.parse_type()
366 field.split.append(field.typ)
367 field.name = self.parse_name()
368 field.split.append(field.name)
369 if self.parse_if("="):
370 field.value = self.parse_value_stripped()
371 else:
372 field.value = None
373
374 self.parse_token(";")
375 self.parse_eof()
376
377 def lookahead(self):
378 return self.tokenized[self.current]
379
380 def parse_one_of(self, *options):
381 found = self.lookahead()
382 if found not in options:
383 raise ValueError("Parsing failed, expected one of `%s` but found `%s` in %s" % (options, found, repr(self.tokenized)))
384 return self.parse_token()
385
386 def parse_token(self, tok = None):
387 found = self.lookahead()
388 if tok is not None and found != tok:
389 raise ValueError("Parsing failed, expected `%s` but found `%s` in %s" % (tok, found, repr(self.tokenized)))
390 self.current += 1
391 return found
392
393 def eof(self):
Adrian Roose5eeae72019-01-04 20:10:06 +0100394 return self.current == self.len
Adrian Roosb787c182019-01-03 18:54:33 +0100395
396 def parse_eof(self):
397 if not self.eof():
398 raise ValueError("Parsing failed, expected EOF, but %s has not been parsed in %s" % (self.tokenized[self.current:], self.tokenized))
399
400 def parse_if(self, tok):
401 if not self.eof() and self.lookahead() == tok:
402 self.parse_token()
403 return True
404 return False
405
406 def parse_annotations(self):
407 ret = []
408 while self.lookahead() == "@":
409 ret.append(self.parse_annotation())
410 return ret
411
412 def parse_annotation(self):
413 ret = self.parse_token("@") + self.parse_token()
414 self.parse_matching_paren("(", ")")
415 return ret
416
417 def parse_matching_paren(self, open, close):
418 start = self.current
419 if not self.parse_if(open):
420 return
421 length = len(self.tokenized)
422 count = 1
423 while count > 0:
424 if self.current == length:
425 raise ValueError("Unexpected EOF looking for closing paren: `%s`" % (self.tokenized[start:],))
426 t = self.parse_token()
427 if t == open:
428 count += 1
429 elif t == close:
430 count -= 1
431 return self.tokenized[start:self.current]
432
433 def parse_modifiers(self):
434 ret = []
435 while self.lookahead() in V2LineParser.MODIFIERS:
436 ret.append(self.parse_token())
437 return ret
438
Adrian Roos5cdfb692019-01-05 22:04:55 +0100439 def parse_kotlin_nullability(self):
440 t = self.lookahead()
441 if t == "?" or t == "!":
442 return self.parse_token()
443 return None
444
Adrian Roosb787c182019-01-03 18:54:33 +0100445 def parse_type(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100446 self.parse_annotations()
Adrian Roosb787c182019-01-03 18:54:33 +0100447 type = self.parse_token()
Adrian Roosd1e38922019-01-14 15:44:15 +0100448 if type[-1] == '.':
449 self.parse_annotations()
450 type += self.parse_token()
Adrian Roosb787c182019-01-03 18:54:33 +0100451 if type in V2LineParser.JAVA_LANG_TYPES:
452 type = "java.lang." + type
453 self.parse_matching_paren("<", ">")
Adrian Roos5cdfb692019-01-05 22:04:55 +0100454 while True:
455 t = self.lookahead()
Adrian Roosd1e38922019-01-14 15:44:15 +0100456 if t == "@":
457 self.parse_annotation()
458 elif t == "[]":
Adrian Roos5cdfb692019-01-05 22:04:55 +0100459 type += self.parse_token()
460 elif self.parse_kotlin_nullability() is not None:
461 pass # discard nullability for now
462 else:
463 break
Adrian Roosb787c182019-01-03 18:54:33 +0100464 return type
465
466 def parse_arg_type(self):
467 type = self.parse_type()
468 if self.parse_if("..."):
469 type += "..."
Adrian Roos5cdfb692019-01-05 22:04:55 +0100470 self.parse_kotlin_nullability() # discard nullability for now
Adrian Roosb787c182019-01-03 18:54:33 +0100471 return type
472
473 def parse_name(self):
474 return self.parse_token()
475
476 def parse_args(self):
477 args = []
478 if self.lookahead() == ")":
479 return args
480
481 while True:
482 args.append(self.parse_arg())
483 if self.lookahead() == ")":
484 return args
485 self.parse_token(",")
486
487 def parse_arg(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100488 self.parse_if("vararg") # kotlin vararg
Adrian Roosb787c182019-01-03 18:54:33 +0100489 self.parse_annotations()
Adrian Roos5cdfb692019-01-05 22:04:55 +0100490 type = self.parse_arg_type()
491 l = self.lookahead()
492 if l != "," and l != ")":
Adrian Roosd1e38922019-01-14 15:44:15 +0100493 if self.lookahead() != '=':
494 self.parse_token() # kotlin argument name
Adrian Roos5cdfb692019-01-05 22:04:55 +0100495 if self.parse_if('='): # kotlin default value
Adrian Roosd1e38922019-01-14 15:44:15 +0100496 self.parse_expression()
Adrian Roos5cdfb692019-01-05 22:04:55 +0100497 return type
Adrian Roosb787c182019-01-03 18:54:33 +0100498
Adrian Roosd1e38922019-01-14 15:44:15 +0100499 def parse_expression(self):
500 while not self.lookahead() in [')', ',', ';']:
501 (self.parse_matching_paren('(', ')') or
502 self.parse_matching_paren('{', '}') or
503 self.parse_token())
504
Adrian Roosb787c182019-01-03 18:54:33 +0100505 def parse_throws(self):
506 ret = []
507 if self.parse_if("throws"):
508 ret.append(self.parse_type())
509 while self.parse_if(","):
510 ret.append(self.parse_type())
511 return ret
512
513 def parse_extends(self):
514 if self.parse_if("extends"):
515 return self.parse_space_delimited_type_list()
516 return []
517
518 def parse_implements(self):
519 if self.parse_if("implements"):
520 return self.parse_space_delimited_type_list()
521 return []
522
523 def parse_space_delimited_type_list(self, terminals = ["implements", "{"]):
524 types = []
525 while True:
526 types.append(self.parse_type())
527 if self.lookahead() in terminals:
528 return types
529
530 def parse_annotation_default(self):
531 if self.parse_if("default"):
Adrian Roosd1e38922019-01-14 15:44:15 +0100532 self.parse_expression()
Adrian Roosb787c182019-01-03 18:54:33 +0100533
534 def parse_value(self):
535 if self.lookahead() == "{":
536 return " ".join(self.parse_matching_paren("{", "}"))
537 elif self.lookahead() == "(":
538 return " ".join(self.parse_matching_paren("(", ")"))
539 else:
540 return self.parse_token()
541
542 def parse_value_stripped(self):
543 value = self.parse_value()
544 if value[0] == '"':
545 return value[1:-1]
546 return value
547
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700548
Adrian Roos038a0292018-12-19 17:11:21 +0100549def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
550 in_classes_with_base=[]):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700551 api = {}
Adrian Roos038a0292018-12-19 17:11:21 +0100552 in_classes_with_base = _retry_iterator(in_classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +0100553
554 if base_f:
Adrian Roos038a0292018-12-19 17:11:21 +0100555 base_classes = _retry_iterator(_parse_stream_to_generator(base_f))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100556 else:
557 base_classes = []
558
Adrian Roos038a0292018-12-19 17:11:21 +0100559 def handle_class(clazz):
Adrian Roos6eb57b02018-12-13 22:08:29 +0100560 if clazz_cb:
561 clazz_cb(clazz)
562 else: # In callback mode, don't keep track of the full API
563 api[clazz.fullname] = clazz
564
Adrian Roos038a0292018-12-19 17:11:21 +0100565 def handle_missed_classes_with_base(clazz):
566 for c in _yield_until_matching_class(in_classes_with_base, clazz):
567 base_class = _skip_to_matching_class(base_classes, c)
568 if base_class:
569 handle_class(base_class)
570
571 for clazz in _parse_stream_to_generator(f):
572 # Before looking at clazz, let's see if there's some classes that were not present, but
573 # may have an entry in the base stream.
574 handle_missed_classes_with_base(clazz)
575
576 base_class = _skip_to_matching_class(base_classes, clazz)
577 if base_class:
578 clazz.merge_from(base_class)
579 if out_classes_with_base is not None:
580 out_classes_with_base.append(clazz)
581 handle_class(clazz)
582
583 handle_missed_classes_with_base(None)
584
Adrian Roos6eb57b02018-12-13 22:08:29 +0100585 return api
586
587def _parse_stream_to_generator(f):
588 line = 0
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700589 pkg = None
590 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700591 blame = None
Adrian Roosb787c182019-01-03 18:54:33 +0100592 sig_format = 1
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700593
594 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Adrian Roos258c5722019-01-21 15:43:15 +0100595
596 field_prefixes = map(lambda kind: " %s" % (kind,), V2LineParser.FIELD_KINDS)
597 def startsWithFieldPrefix(raw):
598 for prefix in field_prefixes:
599 if raw.startswith(prefix):
600 return True
601 return False
602
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800603 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700604 line += 1
605 raw = raw.rstrip()
606 match = re_blame.match(raw)
607 if match is not None:
608 blame = match.groups()[0:2]
609 raw = match.groups()[2]
610 else:
611 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700612
Adrian Roosb787c182019-01-03 18:54:33 +0100613 if line == 1 and raw == "// Signature format: 2.0":
614 sig_format = 2
615 elif raw.startswith("package"):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700616 pkg = Package(line, raw, blame)
617 elif raw.startswith(" ") and raw.endswith("{"):
Adrian Roosb787c182019-01-03 18:54:33 +0100618 clazz = Class(pkg, line, raw, blame, sig_format=sig_format)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700619 elif raw.startswith(" ctor"):
Adrian Roosb787c182019-01-03 18:54:33 +0100620 clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700621 elif raw.startswith(" method"):
Adrian Roosb787c182019-01-03 18:54:33 +0100622 clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos258c5722019-01-21 15:43:15 +0100623 elif startsWithFieldPrefix(raw):
Adrian Roosb787c182019-01-03 18:54:33 +0100624 clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100625 elif raw.startswith(" }") and clazz:
Adrian Roos038a0292018-12-19 17:11:21 +0100626 yield clazz
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800627
Adrian Roos5ed42b62018-12-19 17:10:22 +0100628def _retry_iterator(it):
629 """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
630 for e in it:
631 while True:
632 retry = yield e
633 if not retry:
634 break
635 # send() was called, asking us to redeliver clazz on next(). Still need to yield
636 # a dummy value to the send() first though.
637 if (yield "Returning clazz on next()"):
638 raise TypeError("send() must be followed by next(), not send()")
639
Adrian Roos038a0292018-12-19 17:11:21 +0100640def _skip_to_matching_class(classes, needle):
641 """Takes a classes iterator and consumes entries until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700642
Adrian Roos6eb57b02018-12-13 22:08:29 +0100643 This relies on classes being sorted by package and class name."""
644
645 for clazz in classes:
646 if clazz.pkg.name < needle.pkg.name:
647 # We haven't reached the right package yet
648 continue
Adrian Roos5ed42b62018-12-19 17:10:22 +0100649 if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
650 # We're in the right package, but not the right class yet
Adrian Roos6eb57b02018-12-13 22:08:29 +0100651 continue
652 if clazz.fullname == needle.fullname:
653 return clazz
654 # We ran past the right class. Send it back into the generator, then report failure.
655 classes.send(clazz)
656 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700657
Adrian Roos038a0292018-12-19 17:11:21 +0100658def _yield_until_matching_class(classes, needle):
659 """Takes a class iterator and yields entries it until it reaches the class we're looking for.
660
661 This relies on classes being sorted by package and class name."""
662
663 for clazz in classes:
664 if needle is None:
665 yield clazz
666 elif clazz.pkg.name < needle.pkg.name:
667 # We haven't reached the right package yet
668 yield clazz
669 elif clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
670 # We're in the right package, but not the right class yet
671 yield clazz
672 elif clazz.fullname == needle.fullname:
673 # Class found, abort.
674 return
675 else:
676 # We ran past the right class. Send it back into the iterator, then abort.
677 classes.send(clazz)
678 return
679
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700680class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800681 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700682 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700683 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800684 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700685 self.msg = msg
686
687 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800688 self.head = "Error %s" % (rule) if rule else "Error"
689 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 -0700690 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800691 self.head = "Warning %s" % (rule) if rule else "Warning"
692 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 -0700693
694 self.line = clazz.line
695 blame = clazz.blame
696 if detail is not None:
697 dump += "\n in " + repr(detail)
698 self.line = detail.line
699 blame = detail.blame
700 dump += "\n in " + repr(clazz)
701 dump += "\n in " + repr(clazz.pkg)
702 dump += "\n at line " + repr(self.line)
703 if blame is not None:
704 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
705
706 self.dump = dump
707
708 def __repr__(self):
709 return self.dump
710
711
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700712failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700713
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800714def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700715 """Records an API failure to be processed later."""
716 global failures
717
Adrian Roosb787c182019-01-03 18:54:33 +0100718 sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700719 sig = sig.replace(" deprecated ", " ")
720
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800721 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700722
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700723
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800724def warn(clazz, detail, rule, msg):
725 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700726
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800727def error(clazz, detail, rule, msg):
728 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700729
730
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700731noticed = {}
732
733def notice(clazz):
734 global noticed
735
736 noticed[clazz.fullname] = hash(clazz)
737
738
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700739def verify_constants(clazz):
740 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700741 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600742 if clazz.fullname.startswith("android.os.Build"): return
743 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700744
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600745 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700746 for f in clazz.fields:
747 if "static" in f.split and "final" in f.split:
748 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800749 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600750 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700751 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
752 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600753 if f.typ in req and f.value is None:
754 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700755
756
757def verify_enums(clazz):
758 """Enums are bad, mmkay?"""
Adrian Roosb787c182019-01-03 18:54:33 +0100759 if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800760 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700761
762
763def verify_class_names(clazz):
764 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700765 if clazz.fullname.startswith("android.opengl"): return
766 if clazz.fullname.startswith("android.renderscript"): return
767 if re.match("android\.R\.[a-z]+", clazz.fullname): return
768
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700769 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800770 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700771 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800772 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700773 if clazz.name.endswith("Impl"):
774 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700775
776
777def verify_method_names(clazz):
778 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700779 if clazz.fullname.startswith("android.opengl"): return
780 if clazz.fullname.startswith("android.renderscript"): return
781 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700782
783 for m in clazz.methods:
784 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800785 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700786 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800787 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700788
789
790def verify_callbacks(clazz):
791 """Verify Callback classes.
792 All callback classes must be abstract.
793 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700794 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700795
796 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800797 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700798 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800799 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700800
801 if clazz.name.endswith("Callback"):
802 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800803 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700804
805 for m in clazz.methods:
806 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800807 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700808
809
810def verify_listeners(clazz):
811 """Verify Listener classes.
812 All Listener classes must be interface.
813 All methods must follow onFoo() naming style.
814 If only a single method, it must match class name:
815 interface OnFooListener { void onFoo() }"""
816
817 if clazz.name.endswith("Listener"):
Adrian Roosb787c182019-01-03 18:54:33 +0100818 if "abstract" in clazz.split and "class" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800819 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700820
821 for m in clazz.methods:
822 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800823 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700824
825 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
826 m = clazz.methods[0]
827 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800828 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700829
830
831def verify_actions(clazz):
832 """Verify intent actions.
833 All action names must be named ACTION_FOO.
834 All action values must be scoped by package and match name:
835 package android.foo {
836 String ACTION_BAR = "android.foo.action.BAR";
837 }"""
838 for f in clazz.fields:
839 if f.value is None: continue
840 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700841 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600842 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700843
844 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
845 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
846 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800847 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700848 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700849 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700850 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700851 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700852 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700853 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
854 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700855 else:
856 prefix = clazz.pkg.name + ".action"
857 expected = prefix + "." + f.name[7:]
858 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700859 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700860
861
862def verify_extras(clazz):
863 """Verify intent extras.
864 All extra names must be named EXTRA_FOO.
865 All extra values must be scoped by package and match name:
866 package android.foo {
867 String EXTRA_BAR = "android.foo.extra.BAR";
868 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700869 if clazz.fullname == "android.app.Notification": return
870 if clazz.fullname == "android.appwidget.AppWidgetManager": return
871
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700872 for f in clazz.fields:
873 if f.value is None: continue
874 if f.name.startswith("ACTION_"): continue
875
876 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
877 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
878 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800879 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700880 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700881 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700882 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700883 elif clazz.pkg.name == "android.app.admin":
884 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700885 else:
886 prefix = clazz.pkg.name + ".extra"
887 expected = prefix + "." + f.name[6:]
888 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700889 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700890
891
892def verify_equals(clazz):
893 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700894 eq = False
895 hc = False
896 for m in clazz.methods:
Adrian Roosb787c182019-01-03 18:54:33 +0100897 if "static" in m.split: continue
898 if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
899 if m.sig_matches("int", "hashCode", []): hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700900 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800901 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700902
903
904def verify_parcelable(clazz):
905 """Verify that Parcelable objects aren't hiding required bits."""
Adrian Roosb787c182019-01-03 18:54:33 +0100906 if clazz.implements == "android.os.Parcelable":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700907 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
908 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
909 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
910
911 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800912 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700913
Adrian Roosb787c182019-01-03 18:54:33 +0100914 if "final" not in clazz.split:
Jeff Sharkey331279b2016-02-29 16:02:02 -0700915 error(clazz, None, "FW8", "Parcelable classes must be final")
916
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700917 for c in clazz.ctors:
918 if c.args == ["android.os.Parcel"]:
919 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
920
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700921
922def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800923 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700924 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600925 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700926 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800927 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700928 for f in clazz.fields:
929 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800930 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700931
932
933def verify_fields(clazz):
934 """Verify that all exposed fields are final.
935 Exposed fields must follow myName style.
936 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700937
938 IGNORE_BARE_FIELDS = [
939 "android.app.ActivityManager.RecentTaskInfo",
940 "android.app.Notification",
941 "android.content.pm.ActivityInfo",
942 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600943 "android.content.pm.ComponentInfo",
944 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700945 "android.content.pm.FeatureGroupInfo",
946 "android.content.pm.InstrumentationInfo",
947 "android.content.pm.PackageInfo",
948 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600949 "android.content.res.Configuration",
950 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700951 "android.os.Message",
952 "android.system.StructPollfd",
953 ]
954
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700955 for f in clazz.fields:
956 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700957 if clazz.fullname in IGNORE_BARE_FIELDS:
958 pass
959 elif clazz.fullname.endswith("LayoutParams"):
960 pass
961 elif clazz.fullname.startswith("android.util.Mutable"):
962 pass
963 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800964 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700965
Adrian Roosd1e38922019-01-14 15:44:15 +0100966 if "static" not in f.split and "property" not in f.split:
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700967 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800968 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700969
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700970 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800971 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700972
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700973 if re.match("[A-Z_]+", f.name):
974 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800975 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700976
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700977
978def verify_register(clazz):
979 """Verify parity of registration methods.
980 Callback objects use register/unregister methods.
981 Listener objects use add/remove methods."""
982 methods = [ m.name for m in clazz.methods ]
983 for m in clazz.methods:
984 if "Callback" in m.raw:
985 if m.name.startswith("register"):
986 other = "unregister" + m.name[8:]
987 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800988 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700989 if m.name.startswith("unregister"):
990 other = "register" + m.name[10:]
991 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800992 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700993
994 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800995 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700996
997 if "Listener" in m.raw:
998 if m.name.startswith("add"):
999 other = "remove" + m.name[3:]
1000 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001001 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001002 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
1003 other = "add" + m.name[6:]
1004 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001005 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001006
1007 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001008 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001009
1010
1011def verify_sync(clazz):
1012 """Verify synchronized methods aren't exposed."""
1013 for m in clazz.methods:
1014 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001015 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001016
1017
1018def verify_intent_builder(clazz):
1019 """Verify that Intent builders are createFooIntent() style."""
1020 if clazz.name == "Intent": return
1021
1022 for m in clazz.methods:
1023 if m.typ == "android.content.Intent":
1024 if m.name.startswith("create") and m.name.endswith("Intent"):
1025 pass
1026 else:
Adam Powell539ea122015-04-10 13:01:37 -07001027 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001028
1029
1030def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001031 """Verify that helper classes are named consistently with what they extend.
1032 All developer extendable methods should be named onFoo()."""
1033 test_methods = False
Adrian Roosb787c182019-01-03 18:54:33 +01001034 if clazz.extends == "android.app.Service":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001035 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001036 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001037 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001038
1039 found = False
1040 for f in clazz.fields:
1041 if f.name == "SERVICE_INTERFACE":
1042 found = True
1043 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001044 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001045
Adrian Roosb787c182019-01-03 18:54:33 +01001046 if clazz.extends == "android.content.ContentProvider":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001047 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001048 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001049 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001050
1051 found = False
1052 for f in clazz.fields:
1053 if f.name == "PROVIDER_INTERFACE":
1054 found = True
1055 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001056 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001057
Adrian Roosb787c182019-01-03 18:54:33 +01001058 if clazz.extends == "android.content.BroadcastReceiver":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001059 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001060 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001061 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001062
Adrian Roosb787c182019-01-03 18:54:33 +01001063 if clazz.extends == "android.app.Activity":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001064 test_methods = True
1065 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001066 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001067
1068 if test_methods:
1069 for m in clazz.methods:
1070 if "final" in m.split: continue
1071 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001072 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001073 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001074 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001075 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001076
1077
1078def verify_builder(clazz):
1079 """Verify builder classes.
1080 Methods should return the builder to enable chaining."""
Adrian Roosb787c182019-01-03 18:54:33 +01001081 if clazz.extends: return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001082 if not clazz.name.endswith("Builder"): return
1083
1084 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001085 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001086
1087 has_build = False
1088 for m in clazz.methods:
1089 if m.name == "build":
1090 has_build = True
1091 continue
1092
1093 if m.name.startswith("get"): continue
1094 if m.name.startswith("clear"): continue
1095
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001096 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001097 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001098
1099 if m.name.startswith("set"):
1100 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001101 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001102
1103 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001104 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001105
1106
1107def verify_aidl(clazz):
1108 """Catch people exposing raw AIDL."""
Adrian Roosb787c182019-01-03 18:54:33 +01001109 if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001110 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001111
1112
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001113def verify_internal(clazz):
1114 """Catch people exposing internal classes."""
1115 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001116 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001117
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001118def layering_build_ranking(ranking_list):
1119 r = {}
1120 for rank, ps in enumerate(ranking_list):
1121 if not isinstance(ps, list):
1122 ps = [ps]
1123 for p in ps:
1124 rs = r
1125 for n in p.split('.'):
1126 if n not in rs:
1127 rs[n] = {}
1128 rs = rs[n]
1129 rs['-rank'] = rank
1130 return r
1131
1132LAYERING_PACKAGE_RANKING = layering_build_ranking([
1133 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
1134 "android.app",
1135 "android.widget",
1136 "android.view",
1137 "android.animation",
1138 "android.provider",
1139 ["android.content","android.graphics.drawable"],
1140 "android.database",
1141 "android.text",
1142 "android.graphics",
1143 "android.os",
1144 "android.util"
1145])
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001146
1147def verify_layering(clazz):
1148 """Catch package layering violations.
1149 For example, something in android.os depending on android.app."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001150
1151 def rank(p):
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001152 r = None
1153 l = LAYERING_PACKAGE_RANKING
1154 for n in p.split('.'):
1155 if n in l:
1156 l = l[n]
1157 if '-rank' in l:
1158 r = l['-rank']
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001159 else:
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001160 break
1161 return r
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001162
1163 cr = rank(clazz.pkg.name)
1164 if cr is None: return
1165
1166 for f in clazz.fields:
1167 ir = rank(f.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001168 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001169 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001170
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001171 for m in itertools.chain(clazz.methods, clazz.ctors):
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001172 ir = rank(m.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001173 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001174 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001175 for arg in m.args:
1176 ir = rank(arg)
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, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001179
1180
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001181def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001182 """Verifies that boolean accessors are named correctly.
1183 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001184
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001185 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
1186 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001187
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001188 gets = [ m for m in clazz.methods if is_get(m) ]
1189 sets = [ m for m in clazz.methods if is_set(m) ]
1190
1191 def error_if_exists(methods, trigger, expected, actual):
1192 for m in methods:
1193 if m.name == actual:
1194 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001195
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001196 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001197 if is_get(m):
1198 if re.match("is[A-Z]", m.name):
1199 target = m.name[2:]
1200 expected = "setIs" + target
1201 error_if_exists(sets, m.name, expected, "setHas" + target)
1202 elif re.match("has[A-Z]", m.name):
1203 target = m.name[3:]
1204 expected = "setHas" + target
1205 error_if_exists(sets, m.name, expected, "setIs" + target)
1206 error_if_exists(sets, m.name, expected, "set" + target)
1207 elif re.match("get[A-Z]", m.name):
1208 target = m.name[3:]
1209 expected = "set" + target
1210 error_if_exists(sets, m.name, expected, "setIs" + target)
1211 error_if_exists(sets, m.name, expected, "setHas" + target)
1212
1213 if is_set(m):
1214 if re.match("set[A-Z]", m.name):
1215 target = m.name[3:]
1216 expected = "get" + target
1217 error_if_exists(sets, m.name, expected, "is" + target)
1218 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001219
1220
1221def verify_collections(clazz):
1222 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001223 if clazz.fullname == "android.os.Bundle": return
1224
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001225 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
1226 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
1227 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001228 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001229 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001230 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001231 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001232 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001233
1234
1235def verify_flags(clazz):
1236 """Verifies that flags are non-overlapping."""
1237 known = collections.defaultdict(int)
1238 for f in clazz.fields:
1239 if "FLAG_" in f.name:
1240 try:
1241 val = int(f.value)
1242 except:
1243 continue
1244
1245 scope = f.name[0:f.name.index("FLAG_")]
1246 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001247 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001248 known[scope] |= val
1249
1250
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001251def verify_exception(clazz):
1252 """Verifies that methods don't throw generic exceptions."""
1253 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001254 for t in m.throws:
1255 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
1256 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001257
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001258 if t in ["android.os.RemoteException"]:
1259 if clazz.name == "android.content.ContentProviderClient": continue
1260 if clazz.name == "android.os.Binder": continue
1261 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -07001262
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001263 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
1264
1265 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
1266 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -07001267
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001268GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001269
1270def verify_google(clazz):
1271 """Verifies that APIs never reference Google."""
1272
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001273 if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001274 error(clazz, None, None, "Must never reference Google")
1275
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001276 for test in clazz.ctors, clazz.fields, clazz.methods:
1277 for t in test:
1278 if GOOGLE_IGNORECASE.search(t.raw) is not None:
1279 error(clazz, t, None, "Must never reference Google")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001280
1281
1282def verify_bitset(clazz):
1283 """Verifies that we avoid using heavy BitSet."""
1284
1285 for f in clazz.fields:
1286 if f.typ == "java.util.BitSet":
1287 error(clazz, f, None, "Field type must not be heavy BitSet")
1288
1289 for m in clazz.methods:
1290 if m.typ == "java.util.BitSet":
1291 error(clazz, m, None, "Return type must not be heavy BitSet")
1292 for arg in m.args:
1293 if arg == "java.util.BitSet":
1294 error(clazz, m, None, "Argument type must not be heavy BitSet")
1295
1296
1297def verify_manager(clazz):
1298 """Verifies that FooManager is only obtained from Context."""
1299
1300 if not clazz.name.endswith("Manager"): return
1301
1302 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001303 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001304
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001305 for m in clazz.methods:
1306 if m.typ == clazz.fullname:
1307 error(clazz, m, None, "Managers must always be obtained from Context")
1308
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001309
1310def verify_boxed(clazz):
1311 """Verifies that methods avoid boxed primitives."""
1312
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001313 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 -08001314
1315 for c in clazz.ctors:
1316 for arg in c.args:
1317 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001318 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001319
1320 for f in clazz.fields:
1321 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001322 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001323
1324 for m in clazz.methods:
1325 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001326 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001327 for arg in m.args:
1328 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001329 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001330
1331
1332def verify_static_utils(clazz):
1333 """Verifies that helper classes can't be constructed."""
1334 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001335 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001336
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001337 # Only care about classes with default constructors
1338 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
1339 test = []
1340 test.extend(clazz.fields)
1341 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001342
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001343 if len(test) == 0: return
1344 for t in test:
1345 if "static" not in t.split:
1346 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001347
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001348 error(clazz, None, None, "Fully-static utility classes must not have constructor")
1349
1350
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001351def verify_overload_args(clazz):
1352 """Verifies that method overloads add new arguments at the end."""
1353 if clazz.fullname.startswith("android.opengl"): return
1354
1355 overloads = collections.defaultdict(list)
1356 for m in clazz.methods:
1357 if "deprecated" in m.split: continue
1358 overloads[m.name].append(m)
1359
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001360 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001361 if len(methods) <= 1: continue
1362
1363 # Look for arguments common across all overloads
1364 def cluster(args):
1365 count = collections.defaultdict(int)
1366 res = set()
1367 for i in range(len(args)):
1368 a = args[i]
1369 res.add("%s#%d" % (a, count[a]))
1370 count[a] += 1
1371 return res
1372
1373 common_args = cluster(methods[0].args)
1374 for m in methods:
1375 common_args = common_args & cluster(m.args)
1376
1377 if len(common_args) == 0: continue
1378
1379 # Require that all common arguments are present at start of signature
1380 locked_sig = None
1381 for m in methods:
1382 sig = m.args[0:len(common_args)]
1383 if not common_args.issubset(cluster(sig)):
1384 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1385 elif not locked_sig:
1386 locked_sig = sig
1387 elif locked_sig != sig:
1388 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1389
1390
1391def verify_callback_handlers(clazz):
1392 """Verifies that methods adding listener/callback have overload
1393 for specifying delivery thread."""
1394
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001395 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001396 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001397 "animation",
1398 "view",
1399 "graphics",
1400 "transition",
1401 "widget",
1402 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001403 ]
1404 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001405 if s in clazz.pkg.name_path: return
1406 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001407
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001408 # Ignore UI classes which assume main thread
1409 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1410 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1411 if s in clazz.fullname: return
1412 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1413 for s in ["Loader"]:
1414 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001415
1416 found = {}
1417 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001418 examine = clazz.ctors + clazz.methods
1419 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001420 if m.name.startswith("unregister"): continue
1421 if m.name.startswith("remove"): continue
1422 if re.match("on[A-Z]+", m.name): continue
1423
1424 by_name[m.name].append(m)
1425
1426 for a in m.args:
1427 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1428 found[m.name] = m
1429
1430 for f in found.values():
1431 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001432 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001433 for m in by_name[f.name]:
1434 if "android.os.Handler" in m.args:
1435 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001436 if "java.util.concurrent.Executor" in m.args:
1437 takes_exec = True
1438 if not takes_exec:
1439 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001440
1441
1442def verify_context_first(clazz):
1443 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001444 examine = clazz.ctors + clazz.methods
1445 for m in examine:
1446 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001447 if "android.content.Context" in m.args[1:]:
1448 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001449 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1450 if "android.content.ContentResolver" in m.args[1:]:
1451 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001452
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001453
1454def verify_listener_last(clazz):
1455 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1456 examine = clazz.ctors + clazz.methods
1457 for m in examine:
1458 if "Listener" in m.name or "Callback" in m.name: continue
1459 found = False
1460 for a in m.args:
1461 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1462 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001463 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001464 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1465
1466
1467def verify_resource_names(clazz):
1468 """Verifies that resource names have consistent case."""
1469 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1470
1471 # Resources defined by files are foo_bar_baz
1472 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1473 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001474 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1475 if f.name.startswith("config_"):
1476 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1477
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001478 if re.match("[a-z1-9_]+$", f.name): continue
1479 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1480
1481 # Resources defined inside files are fooBarBaz
1482 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1483 for f in clazz.fields:
1484 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1485 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1486 if re.match("state_[a-z_]*$", f.name): continue
1487
1488 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1489 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1490
1491 # Styles are FooBar_Baz
1492 if clazz.name in ["style"]:
1493 for f in clazz.fields:
1494 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1495 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001496
1497
Jeff Sharkey331279b2016-02-29 16:02:02 -07001498def verify_files(clazz):
1499 """Verifies that methods accepting File also accept streams."""
1500
1501 has_file = set()
1502 has_stream = set()
1503
1504 test = []
1505 test.extend(clazz.ctors)
1506 test.extend(clazz.methods)
1507
1508 for m in test:
1509 if "java.io.File" in m.args:
1510 has_file.add(m)
1511 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:
1512 has_stream.add(m.name)
1513
1514 for m in has_file:
1515 if m.name not in has_stream:
1516 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1517
1518
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001519def verify_manager_list(clazz):
1520 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1521
1522 if not clazz.name.endswith("Manager"): return
1523
1524 for m in clazz.methods:
1525 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1526 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1527
1528
Jeff Sharkey26c80902016-12-21 13:41:17 -07001529def verify_abstract_inner(clazz):
1530 """Verifies that abstract inner classes are static."""
1531
1532 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
Adrian Roosb787c182019-01-03 18:54:33 +01001533 if "abstract" in clazz.split and "static" not in clazz.split:
Jeff Sharkey26c80902016-12-21 13:41:17 -07001534 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1535
1536
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001537def verify_runtime_exceptions(clazz):
1538 """Verifies that runtime exceptions aren't listed in throws."""
1539
1540 banned = [
1541 "java.lang.NullPointerException",
1542 "java.lang.ClassCastException",
1543 "java.lang.IndexOutOfBoundsException",
1544 "java.lang.reflect.UndeclaredThrowableException",
1545 "java.lang.reflect.MalformedParametersException",
1546 "java.lang.reflect.MalformedParameterizedTypeException",
1547 "java.lang.invoke.WrongMethodTypeException",
1548 "java.lang.EnumConstantNotPresentException",
1549 "java.lang.IllegalMonitorStateException",
1550 "java.lang.SecurityException",
1551 "java.lang.UnsupportedOperationException",
1552 "java.lang.annotation.AnnotationTypeMismatchException",
1553 "java.lang.annotation.IncompleteAnnotationException",
1554 "java.lang.TypeNotPresentException",
1555 "java.lang.IllegalStateException",
1556 "java.lang.ArithmeticException",
1557 "java.lang.IllegalArgumentException",
1558 "java.lang.ArrayStoreException",
1559 "java.lang.NegativeArraySizeException",
1560 "java.util.MissingResourceException",
1561 "java.util.EmptyStackException",
1562 "java.util.concurrent.CompletionException",
1563 "java.util.concurrent.RejectedExecutionException",
1564 "java.util.IllformedLocaleException",
1565 "java.util.ConcurrentModificationException",
1566 "java.util.NoSuchElementException",
1567 "java.io.UncheckedIOException",
1568 "java.time.DateTimeException",
1569 "java.security.ProviderException",
1570 "java.nio.BufferUnderflowException",
1571 "java.nio.BufferOverflowException",
1572 ]
1573
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001574 examine = clazz.ctors + clazz.methods
1575 for m in examine:
1576 for t in m.throws:
1577 if t in banned:
1578 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001579
1580
1581def verify_error(clazz):
1582 """Verifies that we always use Exception instead of Error."""
1583 if not clazz.extends: return
1584 if clazz.extends.endswith("Error"):
1585 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1586 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1587 error(clazz, None, None, "Exceptions must be named FooException")
1588
1589
1590def verify_units(clazz):
1591 """Verifies that we use consistent naming for units."""
1592
1593 # If we find K, recommend replacing with V
1594 bad = {
1595 "Ns": "Nanos",
1596 "Ms": "Millis or Micros",
1597 "Sec": "Seconds", "Secs": "Seconds",
1598 "Hr": "Hours", "Hrs": "Hours",
1599 "Mo": "Months", "Mos": "Months",
1600 "Yr": "Years", "Yrs": "Years",
1601 "Byte": "Bytes", "Space": "Bytes",
1602 }
1603
1604 for m in clazz.methods:
1605 if m.typ not in ["short","int","long"]: continue
1606 for k, v in bad.iteritems():
1607 if m.name.endswith(k):
1608 error(clazz, m, None, "Expected method name units to be " + v)
1609 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1610 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1611 if m.name.endswith("Seconds"):
1612 error(clazz, m, None, "Returned time values must be in milliseconds")
1613
1614 for m in clazz.methods:
1615 typ = m.typ
1616 if typ == "void":
1617 if len(m.args) != 1: continue
1618 typ = m.args[0]
1619
1620 if m.name.endswith("Fraction") and typ != "float":
1621 error(clazz, m, None, "Fractions must use floats")
1622 if m.name.endswith("Percentage") and typ != "int":
1623 error(clazz, m, None, "Percentage must use ints")
1624
1625
1626def verify_closable(clazz):
1627 """Verifies that classes are AutoClosable."""
Adrian Roosb787c182019-01-03 18:54:33 +01001628 if clazz.implements == "java.lang.AutoCloseable": return
1629 if clazz.implements == "java.io.Closeable": return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001630
1631 for m in clazz.methods:
1632 if len(m.args) > 0: continue
1633 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1634 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1635 return
1636
1637
Jake Wharton9e6738f2017-08-23 11:59:55 -04001638def verify_member_name_not_kotlin_keyword(clazz):
1639 """Prevent method names which are keywords in Kotlin."""
1640
1641 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1642 # This list does not include Java keywords as those are already impossible to use.
1643 keywords = [
1644 'as',
1645 'fun',
1646 'in',
1647 'is',
1648 'object',
1649 'typealias',
1650 'val',
1651 'var',
1652 'when',
1653 ]
1654
1655 for m in clazz.methods:
1656 if m.name in keywords:
1657 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1658 for f in clazz.fields:
1659 if f.name in keywords:
1660 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1661
1662
1663def verify_method_name_not_kotlin_operator(clazz):
1664 """Warn about method names which become operators in Kotlin."""
1665
1666 binary = set()
1667
1668 def unique_binary_op(m, op):
1669 if op in binary:
1670 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1671 binary.add(op)
1672
1673 for m in clazz.methods:
Adrian Roosd1e38922019-01-14 15:44:15 +01001674 if 'static' in m.split or 'operator' in m.split:
Jake Wharton9e6738f2017-08-23 11:59:55 -04001675 continue
1676
1677 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1678 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1679 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1680
1681 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1682 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1683 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1684 # practical way of checking that relationship here.
1685 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1686
1687 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1688 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1689 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1690 unique_binary_op(m, m.name)
1691
1692 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1693 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1694 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1695
1696 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1697 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1698 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1699
1700 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1701 if m.name == 'invoke':
1702 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1703
1704 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1705 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1706 and len(m.args) == 1 \
1707 and m.typ == 'void':
1708 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1709 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1710
1711
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001712def verify_collections_over_arrays(clazz):
1713 """Warn that [] should be Collections."""
1714
Adrian Roosb787c182019-01-03 18:54:33 +01001715 if "@interface" in clazz.split:
1716 return
1717
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001718 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1719 for m in clazz.methods:
1720 if m.typ.endswith("[]") and m.typ not in safe:
1721 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1722 for arg in m.args:
1723 if arg.endswith("[]") and arg not in safe:
1724 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1725
1726
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001727def verify_user_handle(clazz):
1728 """Methods taking UserHandle should be ForUser or AsUser."""
1729 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1730 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1731 if clazz.fullname == "android.content.pm.LauncherApps": return
1732 if clazz.fullname == "android.os.UserHandle": return
1733 if clazz.fullname == "android.os.UserManager": return
1734
1735 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001736 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001737
1738 has_arg = "android.os.UserHandle" in m.args
1739 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1740
1741 if clazz.fullname.endswith("Manager") and has_arg:
1742 warn(clazz, m, None, "When a method overload is needed to target a specific "
1743 "UserHandle, callers should be directed to use "
1744 "Context.createPackageContextAsUser() and re-obtain the relevant "
1745 "Manager, and no new API should be added")
1746 elif has_arg and not has_name:
1747 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1748 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001749
1750
1751def verify_params(clazz):
1752 """Parameter classes should be 'Params'."""
1753 if clazz.name.endswith("Params"): return
1754 if clazz.fullname == "android.app.ActivityOptions": return
1755 if clazz.fullname == "android.app.BroadcastOptions": return
1756 if clazz.fullname == "android.os.Bundle": return
1757 if clazz.fullname == "android.os.BaseBundle": return
1758 if clazz.fullname == "android.os.PersistableBundle": return
1759
1760 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1761 for b in bad:
1762 if clazz.name.endswith(b):
1763 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1764
1765
1766def verify_services(clazz):
1767 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1768 if clazz.fullname != "android.content.Context": return
1769
1770 for f in clazz.fields:
1771 if f.typ != "java.lang.String": continue
1772 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1773 if found:
1774 expected = found.group(1).lower()
1775 if f.value != expected:
1776 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1777
1778
1779def verify_tense(clazz):
1780 """Verify tenses of method names."""
1781 if clazz.fullname.startswith("android.opengl"): return
1782
1783 for m in clazz.methods:
1784 if m.name.endswith("Enable"):
1785 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1786
1787
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001788def verify_icu(clazz):
1789 """Verifies that richer ICU replacements are used."""
1790 better = {
1791 "java.util.TimeZone": "android.icu.util.TimeZone",
1792 "java.util.Calendar": "android.icu.util.Calendar",
1793 "java.util.Locale": "android.icu.util.ULocale",
1794 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1795 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1796 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1797 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1798 "java.lang.Character": "android.icu.lang.UCharacter",
1799 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1800 "java.text.Collator": "android.icu.text.Collator",
1801 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1802 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1803 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1804 "java.text.DateFormat": "android.icu.text.DateFormat",
1805 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1806 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1807 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1808 }
1809
1810 for m in clazz.ctors + clazz.methods:
1811 types = []
1812 types.extend(m.typ)
1813 types.extend(m.args)
1814 for arg in types:
1815 if arg in better:
1816 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1817
1818
1819def verify_clone(clazz):
1820 """Verify that clone() isn't implemented; see EJ page 61."""
1821 for m in clazz.methods:
1822 if m.name == "clone":
1823 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1824
1825
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001826def verify_pfd(clazz):
1827 """Verify that android APIs use PFD over FD."""
1828 examine = clazz.ctors + clazz.methods
1829 for m in examine:
1830 if m.typ == "java.io.FileDescriptor":
1831 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1832 if m.typ == "int":
1833 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1834 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1835 for arg in m.args:
1836 if arg == "java.io.FileDescriptor":
1837 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1838
1839 for f in clazz.fields:
1840 if f.typ == "java.io.FileDescriptor":
1841 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1842
1843
1844def verify_numbers(clazz):
1845 """Discourage small numbers types like short and byte."""
1846
1847 discouraged = ["short","byte"]
1848
1849 for c in clazz.ctors:
1850 for arg in c.args:
1851 if arg in discouraged:
1852 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1853
1854 for f in clazz.fields:
1855 if f.typ in discouraged:
1856 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1857
1858 for m in clazz.methods:
1859 if m.typ in discouraged:
1860 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1861 for arg in m.args:
1862 if arg in discouraged:
1863 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1864
1865
1866def verify_singleton(clazz):
1867 """Catch singleton objects with constructors."""
1868
1869 singleton = False
1870 for m in clazz.methods:
1871 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1872 singleton = True
1873
1874 if singleton:
1875 for c in clazz.ctors:
1876 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1877
1878
1879
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001880def is_interesting(clazz):
1881 """Test if given class is interesting from an Android PoV."""
1882
1883 if clazz.pkg.name.startswith("java"): return False
1884 if clazz.pkg.name.startswith("junit"): return False
1885 if clazz.pkg.name.startswith("org.apache"): return False
1886 if clazz.pkg.name.startswith("org.xml"): return False
1887 if clazz.pkg.name.startswith("org.json"): return False
1888 if clazz.pkg.name.startswith("org.w3c"): return False
1889 if clazz.pkg.name.startswith("android.icu."): return False
1890 return True
1891
1892
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001893def examine_clazz(clazz):
1894 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001895
1896 notice(clazz)
1897
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001898 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001899
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001900 verify_constants(clazz)
1901 verify_enums(clazz)
1902 verify_class_names(clazz)
1903 verify_method_names(clazz)
1904 verify_callbacks(clazz)
1905 verify_listeners(clazz)
1906 verify_actions(clazz)
1907 verify_extras(clazz)
1908 verify_equals(clazz)
1909 verify_parcelable(clazz)
1910 verify_protected(clazz)
1911 verify_fields(clazz)
1912 verify_register(clazz)
1913 verify_sync(clazz)
1914 verify_intent_builder(clazz)
1915 verify_helper_classes(clazz)
1916 verify_builder(clazz)
1917 verify_aidl(clazz)
1918 verify_internal(clazz)
1919 verify_layering(clazz)
1920 verify_boolean(clazz)
1921 verify_collections(clazz)
1922 verify_flags(clazz)
1923 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001924 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001925 verify_bitset(clazz)
1926 verify_manager(clazz)
1927 verify_boxed(clazz)
1928 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001929 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001930 verify_callback_handlers(clazz)
1931 verify_context_first(clazz)
1932 verify_listener_last(clazz)
1933 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001934 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001935 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001936 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001937 verify_runtime_exceptions(clazz)
1938 verify_error(clazz)
1939 verify_units(clazz)
1940 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001941 verify_member_name_not_kotlin_keyword(clazz)
1942 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001943 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001944 verify_user_handle(clazz)
1945 verify_params(clazz)
1946 verify_services(clazz)
1947 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001948 verify_icu(clazz)
1949 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001950 verify_pfd(clazz)
1951 verify_numbers(clazz)
1952 verify_singleton(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001953
1954
Adrian Roos038a0292018-12-19 17:11:21 +01001955def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001956 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001957 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001958 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001959 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01001960 _parse_stream(stream, examine_clazz, base_f=base_stream,
1961 in_classes_with_base=in_classes_with_base,
1962 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001963 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001964
1965
1966def examine_api(api):
1967 """Find all style issues in the given parsed API."""
1968 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001969 failures = {}
1970 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001971 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001972 return failures
1973
1974
Jeff Sharkey037458a2014-09-04 15:46:20 -07001975def verify_compat(cur, prev):
1976 """Find any incompatible API changes between two levels."""
1977 global failures
1978
1979 def class_exists(api, test):
1980 return test.fullname in api
1981
1982 def ctor_exists(api, clazz, test):
1983 for m in clazz.ctors:
1984 if m.ident == test.ident: return True
1985 return False
1986
1987 def all_methods(api, clazz):
1988 methods = list(clazz.methods)
1989 if clazz.extends is not None:
1990 methods.extend(all_methods(api, api[clazz.extends]))
1991 return methods
1992
1993 def method_exists(api, clazz, test):
1994 methods = all_methods(api, clazz)
1995 for m in methods:
1996 if m.ident == test.ident: return True
1997 return False
1998
1999 def field_exists(api, clazz, test):
2000 for f in clazz.fields:
2001 if f.ident == test.ident: return True
2002 return False
2003
2004 failures = {}
2005 for key in sorted(prev.keys()):
2006 prev_clazz = prev[key]
2007
2008 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002009 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002010 continue
2011
2012 cur_clazz = cur[key]
2013
2014 for test in prev_clazz.ctors:
2015 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002016 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002017
2018 methods = all_methods(prev, prev_clazz)
2019 for test in methods:
2020 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002021 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002022
2023 for test in prev_clazz.fields:
2024 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002025 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002026
2027 return failures
2028
2029
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002030def show_deprecations_at_birth(cur, prev):
2031 """Show API deprecations at birth."""
2032 global failures
2033
2034 # Remove all existing things so we're left with new
2035 for prev_clazz in prev.values():
2036 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002037 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002038
2039 sigs = { i.ident: i for i in prev_clazz.ctors }
2040 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
2041 sigs = { i.ident: i for i in prev_clazz.methods }
2042 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
2043 sigs = { i.ident: i for i in prev_clazz.fields }
2044 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
2045
2046 # Forget about class entirely when nothing new
2047 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
2048 del cur[prev_clazz.fullname]
2049
2050 for clazz in cur.values():
Adrian Roosb787c182019-01-03 18:54:33 +01002051 if "deprecated" in clazz.split and not clazz.fullname in prev:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002052 error(clazz, None, None, "Found API deprecation at birth")
2053
2054 for i in clazz.ctors + clazz.methods + clazz.fields:
Adrian Roosb787c182019-01-03 18:54:33 +01002055 if "deprecated" in i.split:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002056 error(clazz, i, None, "Found API deprecation at birth")
2057
2058 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
2059 format(reset=True)))
2060 for f in sorted(failures):
2061 print failures[f]
2062 print
2063
2064
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002065def show_stats(cur, prev):
2066 """Show API stats."""
2067
2068 stats = collections.defaultdict(int)
2069 for cur_clazz in cur.values():
2070 if not is_interesting(cur_clazz): continue
2071
2072 if cur_clazz.fullname not in prev:
2073 stats['new_classes'] += 1
2074 stats['new_ctors'] += len(cur_clazz.ctors)
2075 stats['new_methods'] += len(cur_clazz.methods)
2076 stats['new_fields'] += len(cur_clazz.fields)
2077 else:
2078 prev_clazz = prev[cur_clazz.fullname]
2079
2080 sigs = { i.ident: i for i in prev_clazz.ctors }
2081 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
2082 sigs = { i.ident: i for i in prev_clazz.methods }
2083 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
2084 sigs = { i.ident: i for i in prev_clazz.fields }
2085 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
2086
2087 if ctors + methods + fields > 0:
2088 stats['extend_classes'] += 1
2089 stats['extend_ctors'] += ctors
2090 stats['extend_methods'] += methods
2091 stats['extend_fields'] += fields
2092
2093 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
2094 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
2095
2096
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002097if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002098 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
2099 patterns. It ignores lint messages from a previous API level, if provided.")
2100 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
2101 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
2102 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01002103 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
2104 help="The base current.txt to use when examining system-current.txt or"
2105 " test-current.txt")
2106 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
2107 help="The base previous.txt to use when examining system-previous.txt or"
2108 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002109 parser.add_argument("--no-color", action='store_const', const=True,
2110 help="Disable terminal colors")
2111 parser.add_argument("--allow-google", action='store_const', const=True,
2112 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002113 parser.add_argument("--show-noticed", action='store_const', const=True,
2114 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002115 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
2116 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002117 parser.add_argument("--show-stats", action='store_const', const=True,
2118 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002119 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002120
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002121 if args['no_color']:
2122 USE_COLOR = False
2123
2124 if args['allow_google']:
2125 ALLOW_GOOGLE = True
2126
2127 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002128 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002129 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002130 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002131
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002132 if args['show_deprecations_at_birth']:
2133 with current_file as f:
2134 cur = _parse_stream(f)
2135 with previous_file as f:
2136 prev = _parse_stream(f)
2137 show_deprecations_at_birth(cur, prev)
2138 sys.exit()
2139
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002140 if args['show_stats']:
2141 with current_file as f:
2142 cur = _parse_stream(f)
2143 with previous_file as f:
2144 prev = _parse_stream(f)
2145 show_stats(cur, prev)
2146 sys.exit()
2147
Adrian Roos038a0292018-12-19 17:11:21 +01002148 classes_with_base = []
2149
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002150 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002151 if base_current_file:
2152 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002153 cur_fail, cur_noticed = examine_stream(f, base_f,
2154 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002155 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002156 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
2157
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002158 if not previous_file is None:
2159 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002160 if base_previous_file:
2161 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002162 prev_fail, prev_noticed = examine_stream(f, base_f,
2163 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002164 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002165 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002166
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002167 # ignore errors from previous API level
2168 for p in prev_fail:
2169 if p in cur_fail:
2170 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002171
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002172 # ignore classes unchanged from previous API level
2173 for k, v in prev_noticed.iteritems():
2174 if k in cur_noticed and v == cur_noticed[k]:
2175 del cur_noticed[k]
2176
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002177 """
2178 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002179 # look for compatibility issues
2180 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002181
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002182 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2183 for f in sorted(compat_fail):
2184 print compat_fail[f]
2185 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002186 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002187
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002188 if args['show_noticed'] and len(cur_noticed) != 0:
2189 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2190 for f in sorted(cur_noticed.keys()):
2191 print f
2192 print
2193
Jason Monk53b2a732017-11-10 15:43:17 -05002194 if len(cur_fail) != 0:
2195 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2196 for f in sorted(cur_fail):
2197 print cur_fail[f]
2198 print
2199 sys.exit(77)