blob: f967c2fa6bcb8cc227fbe3bda6e24a1c1608718d [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
Adrian Roos80545ef2019-02-27 16:45:00 +010082 self.annotations = []
Adrian Roosb787c182019-01-03 18:54:33 +010083
84 self.ident = "-".join((self.typ, self.name, self.value or ""))
Jeff Sharkey037458a2014-09-04 15:46:20 -070085
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -070086 def __hash__(self):
87 return hash(self.raw)
88
Jeff Sharkey8190f4882014-08-28 12:24:07 -070089 def __repr__(self):
90 return self.raw
91
Adrian Roos80545ef2019-02-27 16:45:00 +010092
93class Argument(object):
94
95 __slots__ = ["type", "annotations", "name", "default"]
96
97 def __init__(self, type):
98 self.type = type
99 self.annotations = []
100 self.name = None
101 self.default = None
102
103
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700104class Method():
Adrian Roosb787c182019-01-03 18:54:33 +0100105 def __init__(self, clazz, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700106 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700107 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700108 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700109 self.blame = blame
110
Adrian Roosb787c182019-01-03 18:54:33 +0100111 if sig_format == 2:
112 V2LineParser(raw).parse_into_method(self)
113 elif sig_format == 1:
114 # drop generics for now; may need multiple passes
115 raw = re.sub("<[^<]+?>", "", raw)
116 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700117
Adrian Roosb787c182019-01-03 18:54:33 +0100118 # handle each clause differently
119 raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600120
Adrian Roosb787c182019-01-03 18:54:33 +0100121 # parse prefixes
122 raw = re.split("[\s]+", raw_prefix)
123 for r in ["", ";"]:
124 while r in raw: raw.remove(r)
125 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700126
Adrian Roosb787c182019-01-03 18:54:33 +0100127 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator", "synchronized"]:
128 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700129
Adrian Roosb787c182019-01-03 18:54:33 +0100130 self.typ = raw[0]
131 self.name = raw[1]
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600132
Adrian Roosb787c182019-01-03 18:54:33 +0100133 # parse args
Adrian Roos80545ef2019-02-27 16:45:00 +0100134 self.detailed_args = []
Adrian Roosb787c182019-01-03 18:54:33 +0100135 for arg in re.split(",\s*", raw_args):
136 arg = re.split("\s", arg)
137 # ignore annotations for now
138 arg = [ a for a in arg if not a.startswith("@") ]
139 if len(arg[0]) > 0:
Adrian Roos80545ef2019-02-27 16:45:00 +0100140 self.detailed_args.append(Argument(arg[0]))
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600141
Adrian Roosb787c182019-01-03 18:54:33 +0100142 # parse throws
143 self.throws = []
144 for throw in re.split(",\s*", raw_throws):
145 self.throws.append(throw)
Adrian Roos80545ef2019-02-27 16:45:00 +0100146
147 self.annotations = []
Adrian Roosb787c182019-01-03 18:54:33 +0100148 else:
149 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600150
Adrian Roos80545ef2019-02-27 16:45:00 +0100151 self.args = map(lambda a: a.type, self.detailed_args)
Adrian Roosb787c182019-01-03 18:54:33 +0100152 self.ident = "-".join((self.typ, self.name, "-".join(self.args)))
153
154 def sig_matches(self, typ, name, args):
155 return typ == self.typ and name == self.name and args == self.args
Jeff Sharkey037458a2014-09-04 15:46:20 -0700156
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700157 def __hash__(self):
158 return hash(self.raw)
159
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700160 def __repr__(self):
161 return self.raw
162
163
164class Class():
Adrian Roosb787c182019-01-03 18:54:33 +0100165 def __init__(self, pkg, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700166 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700167 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700168 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700169 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700170 self.ctors = []
171 self.fields = []
172 self.methods = []
Adrian Roos34782392019-03-06 13:38:10 +0100173 self.annotations = []
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700174
Adrian Roosb787c182019-01-03 18:54:33 +0100175 if sig_format == 2:
176 V2LineParser(raw).parse_into_class(self)
177 elif sig_format == 1:
178 # drop generics for now; may need multiple passes
179 raw = re.sub("<[^<]+?>", "", raw)
180 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600181
Adrian Roosb787c182019-01-03 18:54:33 +0100182 raw = raw.split()
183 self.split = list(raw)
184 if "class" in raw:
185 self.fullname = raw[raw.index("class")+1]
186 elif "interface" in raw:
187 self.fullname = raw[raw.index("interface")+1]
188 elif "@interface" in raw:
189 self.fullname = raw[raw.index("@interface")+1]
190 else:
191 raise ValueError("Funky class type %s" % (self.raw))
Jeff Sharkey037458a2014-09-04 15:46:20 -0700192
Adrian Roosb787c182019-01-03 18:54:33 +0100193 if "extends" in raw:
194 self.extends = raw[raw.index("extends")+1]
195 else:
196 self.extends = None
197
198 if "implements" in raw:
199 self.implements = raw[raw.index("implements")+1]
Adrian Roos02e18dd2019-02-28 12:41:48 +0100200 self.implements_all = [self.implements]
Adrian Roosb787c182019-01-03 18:54:33 +0100201 else:
202 self.implements = None
Adrian Roos02e18dd2019-02-28 12:41:48 +0100203 self.implements_all = []
Jeff Sharkey037458a2014-09-04 15:46:20 -0700204 else:
Adrian Roosb787c182019-01-03 18:54:33 +0100205 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700206
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700207 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800208 self.fullname_path = self.fullname.split(".")
209
Adrian Roosb787c182019-01-03 18:54:33 +0100210 if self.extends is not None:
211 self.extends_path = self.extends.split(".")
212 else:
213 self.extends_path = []
214
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700215 self.name = self.fullname[self.fullname.rindex(".")+1:]
216
Adrian Roos6eb57b02018-12-13 22:08:29 +0100217 def merge_from(self, other):
218 self.ctors.extend(other.ctors)
219 self.fields.extend(other.fields)
220 self.methods.extend(other.methods)
221
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700222 def __hash__(self):
223 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
224
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700225 def __repr__(self):
226 return self.raw
227
228
229class Package():
Adrian Roosd9871b12019-02-28 12:42:22 +0100230 NAME = re.compile("package(?: .*)? ([A-Za-z0-9.]+)")
Adrian Roosb1faa0b2019-02-26 11:54:40 +0100231
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700232 def __init__(self, line, raw, blame):
233 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700234 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700235 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700236
Adrian Roosb1faa0b2019-02-26 11:54:40 +0100237 self.name = Package.NAME.match(raw).group(1)
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800238 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700239
240 def __repr__(self):
241 return self.raw
242
Adrian Roose5eeae72019-01-04 20:10:06 +0100243class V2Tokenizer(object):
244 __slots__ = ["raw"]
245
Adrian Rooscf82e042019-01-29 15:01:28 +0100246 SIGNATURE_PREFIX = "// Signature format: "
Adrian Roos5cdfb692019-01-05 22:04:55 +0100247 DELIMITER = re.compile(r'\s+|[()@<>;,={}/"!?]|\[\]|\.\.\.')
Adrian Roosb787c182019-01-03 18:54:33 +0100248 STRING_SPECIAL = re.compile(r'["\\]')
249
250 def __init__(self, raw):
251 self.raw = raw
Adrian Roosb787c182019-01-03 18:54:33 +0100252
Adrian Roose5eeae72019-01-04 20:10:06 +0100253 def tokenize(self):
254 tokens = []
255 current = 0
256 raw = self.raw
257 length = len(raw)
Adrian Roosb787c182019-01-03 18:54:33 +0100258
Adrian Roose5eeae72019-01-04 20:10:06 +0100259 while current < length:
260 while current < length:
261 start = current
262 match = V2Tokenizer.DELIMITER.search(raw, start)
263 if match is not None:
264 match_start = match.start()
265 if match_start == current:
266 end = match.end()
267 else:
268 end = match_start
269 else:
270 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100271
Adrian Roose5eeae72019-01-04 20:10:06 +0100272 token = raw[start:end]
273 current = end
Adrian Roosb787c182019-01-03 18:54:33 +0100274
Adrian Roose5eeae72019-01-04 20:10:06 +0100275 if token == "" or token[0] == " ":
276 continue
277 else:
278 break
Adrian Roosb787c182019-01-03 18:54:33 +0100279
Adrian Roose5eeae72019-01-04 20:10:06 +0100280 if token == "@":
281 if raw[start:start+11] == "@interface ":
282 current = start + 11
283 tokens.append("@interface")
284 continue
285 elif token == '/':
286 if raw[start:start+2] == "//":
287 current = length
288 continue
289 elif token == '"':
290 current, string_token = self.tokenize_string(raw, length, current)
291 tokens.append(token + string_token)
292 continue
Adrian Roosb787c182019-01-03 18:54:33 +0100293
Adrian Roose5eeae72019-01-04 20:10:06 +0100294 tokens.append(token)
Adrian Roosb787c182019-01-03 18:54:33 +0100295
Adrian Roose5eeae72019-01-04 20:10:06 +0100296 return tokens
Adrian Roosb787c182019-01-03 18:54:33 +0100297
Adrian Roose5eeae72019-01-04 20:10:06 +0100298 def tokenize_string(self, raw, length, current):
299 start = current
300 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100301 while start < end:
Adrian Roose5eeae72019-01-04 20:10:06 +0100302 match = V2Tokenizer.STRING_SPECIAL.search(raw, start)
Adrian Roosb787c182019-01-03 18:54:33 +0100303 if match:
304 if match.group() == '"':
305 end = match.end()
306 break
307 elif match.group() == '\\':
308 # ignore whatever is after the slash
309 start += 2
310 else:
311 raise ValueError("Unexpected match: `%s`" % (match.group()))
312 else:
Adrian Roose5eeae72019-01-04 20:10:06 +0100313 raise ValueError("Unexpected EOF tokenizing string: `%s`" % (raw[current - 1:],))
Adrian Roosb787c182019-01-03 18:54:33 +0100314
Adrian Roose5eeae72019-01-04 20:10:06 +0100315 token = raw[current:end]
316 return end, token
Adrian Roosb787c182019-01-03 18:54:33 +0100317
Adrian Roose5eeae72019-01-04 20:10:06 +0100318class V2LineParser(object):
319 __slots__ = ["tokenized", "current", "len"]
320
Adrian Roos258c5722019-01-21 15:43:15 +0100321 FIELD_KINDS = ("field", "property", "enum_constant")
Adrian Roosd1e38922019-01-14 15:44:15 +0100322 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 +0100323 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())
324
325 def __init__(self, raw):
Adrian Roose5eeae72019-01-04 20:10:06 +0100326 self.tokenized = V2Tokenizer(raw).tokenize()
Adrian Roosb787c182019-01-03 18:54:33 +0100327 self.current = 0
Adrian Roose5eeae72019-01-04 20:10:06 +0100328 self.len = len(self.tokenized)
Adrian Roosb787c182019-01-03 18:54:33 +0100329
330 def parse_into_method(self, method):
331 method.split = []
332 kind = self.parse_one_of("ctor", "method")
333 method.split.append(kind)
Adrian Roos80545ef2019-02-27 16:45:00 +0100334 method.annotations = self.parse_annotations()
Adrian Roosb787c182019-01-03 18:54:33 +0100335 method.split.extend(self.parse_modifiers())
336 self.parse_matching_paren("<", ">")
Adrian Roos80545ef2019-02-27 16:45:00 +0100337 if "@Deprecated" in method.annotations:
Adrian Roosb787c182019-01-03 18:54:33 +0100338 method.split.append("deprecated")
339 if kind == "ctor":
340 method.typ = "ctor"
341 else:
342 method.typ = self.parse_type()
343 method.split.append(method.typ)
344 method.name = self.parse_name()
345 method.split.append(method.name)
346 self.parse_token("(")
Adrian Roos80545ef2019-02-27 16:45:00 +0100347 method.detailed_args = self.parse_args()
Adrian Roosb787c182019-01-03 18:54:33 +0100348 self.parse_token(")")
349 method.throws = self.parse_throws()
350 if "@interface" in method.clazz.split:
351 self.parse_annotation_default()
352 self.parse_token(";")
353 self.parse_eof()
354
355 def parse_into_class(self, clazz):
356 clazz.split = []
Adrian Roos34782392019-03-06 13:38:10 +0100357 clazz.annotations = self.parse_annotations()
358 if "@Deprecated" in clazz.annotations:
Adrian Roosb787c182019-01-03 18:54:33 +0100359 clazz.split.append("deprecated")
360 clazz.split.extend(self.parse_modifiers())
361 kind = self.parse_one_of("class", "interface", "@interface", "enum")
362 if kind == "enum":
363 # enums are implicitly final
364 clazz.split.append("final")
365 clazz.split.append(kind)
366 clazz.fullname = self.parse_name()
367 self.parse_matching_paren("<", ">")
368 extends = self.parse_extends()
369 clazz.extends = extends[0] if extends else None
Adrian Roos02e18dd2019-02-28 12:41:48 +0100370 clazz.implements_all = self.parse_implements()
Adrian Roosb787c182019-01-03 18:54:33 +0100371 # The checks assume that interfaces are always found in implements, which isn't true for
372 # subinterfaces.
Adrian Roos02e18dd2019-02-28 12:41:48 +0100373 if not clazz.implements_all and "interface" in clazz.split:
374 clazz.implements_all = [clazz.extends]
375 clazz.implements = clazz.implements_all[0] if clazz.implements_all else None
Adrian Roosb787c182019-01-03 18:54:33 +0100376 self.parse_token("{")
377 self.parse_eof()
378
379 def parse_into_field(self, field):
Adrian Roos258c5722019-01-21 15:43:15 +0100380 kind = self.parse_one_of(*V2LineParser.FIELD_KINDS)
Adrian Roosb787c182019-01-03 18:54:33 +0100381 field.split = [kind]
Adrian Roos80545ef2019-02-27 16:45:00 +0100382 field.annotations = self.parse_annotations()
383 if "@Deprecated" in field.annotations:
Adrian Roosb787c182019-01-03 18:54:33 +0100384 field.split.append("deprecated")
385 field.split.extend(self.parse_modifiers())
386 field.typ = self.parse_type()
387 field.split.append(field.typ)
388 field.name = self.parse_name()
389 field.split.append(field.name)
390 if self.parse_if("="):
391 field.value = self.parse_value_stripped()
392 else:
393 field.value = None
394
395 self.parse_token(";")
396 self.parse_eof()
397
398 def lookahead(self):
399 return self.tokenized[self.current]
400
401 def parse_one_of(self, *options):
402 found = self.lookahead()
403 if found not in options:
404 raise ValueError("Parsing failed, expected one of `%s` but found `%s` in %s" % (options, found, repr(self.tokenized)))
405 return self.parse_token()
406
407 def parse_token(self, tok = None):
408 found = self.lookahead()
409 if tok is not None and found != tok:
410 raise ValueError("Parsing failed, expected `%s` but found `%s` in %s" % (tok, found, repr(self.tokenized)))
411 self.current += 1
412 return found
413
414 def eof(self):
Adrian Roose5eeae72019-01-04 20:10:06 +0100415 return self.current == self.len
Adrian Roosb787c182019-01-03 18:54:33 +0100416
417 def parse_eof(self):
418 if not self.eof():
419 raise ValueError("Parsing failed, expected EOF, but %s has not been parsed in %s" % (self.tokenized[self.current:], self.tokenized))
420
421 def parse_if(self, tok):
422 if not self.eof() and self.lookahead() == tok:
423 self.parse_token()
424 return True
425 return False
426
427 def parse_annotations(self):
428 ret = []
429 while self.lookahead() == "@":
430 ret.append(self.parse_annotation())
431 return ret
432
433 def parse_annotation(self):
434 ret = self.parse_token("@") + self.parse_token()
435 self.parse_matching_paren("(", ")")
436 return ret
437
438 def parse_matching_paren(self, open, close):
439 start = self.current
440 if not self.parse_if(open):
441 return
442 length = len(self.tokenized)
443 count = 1
444 while count > 0:
445 if self.current == length:
446 raise ValueError("Unexpected EOF looking for closing paren: `%s`" % (self.tokenized[start:],))
447 t = self.parse_token()
448 if t == open:
449 count += 1
450 elif t == close:
451 count -= 1
452 return self.tokenized[start:self.current]
453
454 def parse_modifiers(self):
455 ret = []
456 while self.lookahead() in V2LineParser.MODIFIERS:
457 ret.append(self.parse_token())
458 return ret
459
Adrian Roos5cdfb692019-01-05 22:04:55 +0100460 def parse_kotlin_nullability(self):
461 t = self.lookahead()
462 if t == "?" or t == "!":
463 return self.parse_token()
464 return None
465
Adrian Roosb787c182019-01-03 18:54:33 +0100466 def parse_type(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100467 self.parse_annotations()
Adrian Roosb787c182019-01-03 18:54:33 +0100468 type = self.parse_token()
Adrian Roosd1e38922019-01-14 15:44:15 +0100469 if type[-1] == '.':
470 self.parse_annotations()
471 type += self.parse_token()
Adrian Roosb787c182019-01-03 18:54:33 +0100472 if type in V2LineParser.JAVA_LANG_TYPES:
473 type = "java.lang." + type
474 self.parse_matching_paren("<", ">")
Adrian Roos5cdfb692019-01-05 22:04:55 +0100475 while True:
476 t = self.lookahead()
Adrian Roosd1e38922019-01-14 15:44:15 +0100477 if t == "@":
478 self.parse_annotation()
479 elif t == "[]":
Adrian Roos5cdfb692019-01-05 22:04:55 +0100480 type += self.parse_token()
481 elif self.parse_kotlin_nullability() is not None:
482 pass # discard nullability for now
483 else:
484 break
Adrian Roosb787c182019-01-03 18:54:33 +0100485 return type
486
487 def parse_arg_type(self):
488 type = self.parse_type()
489 if self.parse_if("..."):
490 type += "..."
Adrian Roos5cdfb692019-01-05 22:04:55 +0100491 self.parse_kotlin_nullability() # discard nullability for now
Adrian Roosb787c182019-01-03 18:54:33 +0100492 return type
493
494 def parse_name(self):
495 return self.parse_token()
496
497 def parse_args(self):
498 args = []
499 if self.lookahead() == ")":
500 return args
501
502 while True:
503 args.append(self.parse_arg())
504 if self.lookahead() == ")":
505 return args
506 self.parse_token(",")
507
508 def parse_arg(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100509 self.parse_if("vararg") # kotlin vararg
Adrian Roos80545ef2019-02-27 16:45:00 +0100510 annotations = self.parse_annotations()
511 arg = Argument(self.parse_arg_type())
512 arg.annotations = annotations
Adrian Roos5cdfb692019-01-05 22:04:55 +0100513 l = self.lookahead()
514 if l != "," and l != ")":
Adrian Roosd1e38922019-01-14 15:44:15 +0100515 if self.lookahead() != '=':
Adrian Roos80545ef2019-02-27 16:45:00 +0100516 arg.name = self.parse_token() # kotlin argument name
Adrian Roos5cdfb692019-01-05 22:04:55 +0100517 if self.parse_if('='): # kotlin default value
Adrian Roos80545ef2019-02-27 16:45:00 +0100518 arg.default = self.parse_expression()
519 return arg
Adrian Roosb787c182019-01-03 18:54:33 +0100520
Adrian Roosd1e38922019-01-14 15:44:15 +0100521 def parse_expression(self):
522 while not self.lookahead() in [')', ',', ';']:
523 (self.parse_matching_paren('(', ')') or
524 self.parse_matching_paren('{', '}') or
525 self.parse_token())
526
Adrian Roosb787c182019-01-03 18:54:33 +0100527 def parse_throws(self):
528 ret = []
529 if self.parse_if("throws"):
530 ret.append(self.parse_type())
531 while self.parse_if(","):
532 ret.append(self.parse_type())
533 return ret
534
535 def parse_extends(self):
536 if self.parse_if("extends"):
537 return self.parse_space_delimited_type_list()
538 return []
539
540 def parse_implements(self):
541 if self.parse_if("implements"):
542 return self.parse_space_delimited_type_list()
543 return []
544
545 def parse_space_delimited_type_list(self, terminals = ["implements", "{"]):
546 types = []
547 while True:
548 types.append(self.parse_type())
549 if self.lookahead() in terminals:
550 return types
551
552 def parse_annotation_default(self):
553 if self.parse_if("default"):
Adrian Roosd1e38922019-01-14 15:44:15 +0100554 self.parse_expression()
Adrian Roosb787c182019-01-03 18:54:33 +0100555
556 def parse_value(self):
557 if self.lookahead() == "{":
558 return " ".join(self.parse_matching_paren("{", "}"))
559 elif self.lookahead() == "(":
560 return " ".join(self.parse_matching_paren("(", ")"))
561 else:
562 return self.parse_token()
563
564 def parse_value_stripped(self):
565 value = self.parse_value()
566 if value[0] == '"':
567 return value[1:-1]
568 return value
569
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700570
Adrian Roos038a0292018-12-19 17:11:21 +0100571def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
572 in_classes_with_base=[]):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700573 api = {}
Adrian Roos038a0292018-12-19 17:11:21 +0100574 in_classes_with_base = _retry_iterator(in_classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +0100575
576 if base_f:
Adrian Roos038a0292018-12-19 17:11:21 +0100577 base_classes = _retry_iterator(_parse_stream_to_generator(base_f))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100578 else:
579 base_classes = []
580
Adrian Roos038a0292018-12-19 17:11:21 +0100581 def handle_class(clazz):
Adrian Roos6eb57b02018-12-13 22:08:29 +0100582 if clazz_cb:
583 clazz_cb(clazz)
584 else: # In callback mode, don't keep track of the full API
585 api[clazz.fullname] = clazz
586
Adrian Roos038a0292018-12-19 17:11:21 +0100587 def handle_missed_classes_with_base(clazz):
588 for c in _yield_until_matching_class(in_classes_with_base, clazz):
589 base_class = _skip_to_matching_class(base_classes, c)
590 if base_class:
591 handle_class(base_class)
592
593 for clazz in _parse_stream_to_generator(f):
594 # Before looking at clazz, let's see if there's some classes that were not present, but
595 # may have an entry in the base stream.
596 handle_missed_classes_with_base(clazz)
597
598 base_class = _skip_to_matching_class(base_classes, clazz)
599 if base_class:
600 clazz.merge_from(base_class)
601 if out_classes_with_base is not None:
602 out_classes_with_base.append(clazz)
603 handle_class(clazz)
604
605 handle_missed_classes_with_base(None)
606
Adrian Roos6eb57b02018-12-13 22:08:29 +0100607 return api
608
609def _parse_stream_to_generator(f):
610 line = 0
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700611 pkg = None
612 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700613 blame = None
Adrian Roosb787c182019-01-03 18:54:33 +0100614 sig_format = 1
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700615
Adrian Roos80545ef2019-02-27 16:45:00 +0100616 re_blame = re.compile(r"^(\^?[a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Adrian Roos258c5722019-01-21 15:43:15 +0100617
618 field_prefixes = map(lambda kind: " %s" % (kind,), V2LineParser.FIELD_KINDS)
619 def startsWithFieldPrefix(raw):
620 for prefix in field_prefixes:
621 if raw.startswith(prefix):
622 return True
623 return False
624
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800625 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700626 line += 1
627 raw = raw.rstrip()
628 match = re_blame.match(raw)
629 if match is not None:
630 blame = match.groups()[0:2]
Adrian Roos80545ef2019-02-27 16:45:00 +0100631 if blame[0].startswith("^"): # Outside of blame range
632 blame = None
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700633 raw = match.groups()[2]
634 else:
635 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700636
Adrian Roos80545ef2019-02-27 16:45:00 +0100637 if line == 1 and V2Tokenizer.SIGNATURE_PREFIX in raw:
Adrian Rooscf82e042019-01-29 15:01:28 +0100638 sig_format_string = raw[len(V2Tokenizer.SIGNATURE_PREFIX):]
639 if sig_format_string in ["2.0", "3.0"]:
640 sig_format = 2
641 else:
642 raise ValueError("Unknown format: %s" % (sig_format_string,))
Adrian Roosb787c182019-01-03 18:54:33 +0100643 elif raw.startswith("package"):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700644 pkg = Package(line, raw, blame)
645 elif raw.startswith(" ") and raw.endswith("{"):
Adrian Roosb787c182019-01-03 18:54:33 +0100646 clazz = Class(pkg, line, raw, blame, sig_format=sig_format)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700647 elif raw.startswith(" ctor"):
Adrian Roosb787c182019-01-03 18:54:33 +0100648 clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700649 elif raw.startswith(" method"):
Adrian Roosb787c182019-01-03 18:54:33 +0100650 clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos258c5722019-01-21 15:43:15 +0100651 elif startsWithFieldPrefix(raw):
Adrian Roosb787c182019-01-03 18:54:33 +0100652 clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100653 elif raw.startswith(" }") and clazz:
Adrian Roos038a0292018-12-19 17:11:21 +0100654 yield clazz
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800655
Adrian Roos5ed42b62018-12-19 17:10:22 +0100656def _retry_iterator(it):
657 """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
658 for e in it:
659 while True:
660 retry = yield e
661 if not retry:
662 break
663 # send() was called, asking us to redeliver clazz on next(). Still need to yield
664 # a dummy value to the send() first though.
665 if (yield "Returning clazz on next()"):
666 raise TypeError("send() must be followed by next(), not send()")
667
Adrian Roos038a0292018-12-19 17:11:21 +0100668def _skip_to_matching_class(classes, needle):
669 """Takes a classes iterator and consumes entries until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700670
Adrian Roos6eb57b02018-12-13 22:08:29 +0100671 This relies on classes being sorted by package and class name."""
672
673 for clazz in classes:
674 if clazz.pkg.name < needle.pkg.name:
675 # We haven't reached the right package yet
676 continue
Adrian Roos5ed42b62018-12-19 17:10:22 +0100677 if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
678 # We're in the right package, but not the right class yet
Adrian Roos6eb57b02018-12-13 22:08:29 +0100679 continue
680 if clazz.fullname == needle.fullname:
681 return clazz
682 # We ran past the right class. Send it back into the generator, then report failure.
683 classes.send(clazz)
684 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700685
Adrian Roos038a0292018-12-19 17:11:21 +0100686def _yield_until_matching_class(classes, needle):
687 """Takes a class iterator and yields entries it until it reaches the class we're looking for.
688
689 This relies on classes being sorted by package and class name."""
690
691 for clazz in classes:
692 if needle is None:
693 yield clazz
694 elif clazz.pkg.name < needle.pkg.name:
695 # We haven't reached the right package yet
696 yield clazz
697 elif clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
698 # We're in the right package, but not the right class yet
699 yield clazz
700 elif clazz.fullname == needle.fullname:
701 # Class found, abort.
702 return
703 else:
704 # We ran past the right class. Send it back into the iterator, then abort.
705 classes.send(clazz)
706 return
707
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700708class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800709 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700710 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700711 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800712 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700713 self.msg = msg
714
715 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800716 self.head = "Error %s" % (rule) if rule else "Error"
717 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 -0700718 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800719 self.head = "Warning %s" % (rule) if rule else "Warning"
720 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 -0700721
722 self.line = clazz.line
723 blame = clazz.blame
724 if detail is not None:
725 dump += "\n in " + repr(detail)
726 self.line = detail.line
727 blame = detail.blame
728 dump += "\n in " + repr(clazz)
729 dump += "\n in " + repr(clazz.pkg)
730 dump += "\n at line " + repr(self.line)
731 if blame is not None:
732 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
733
734 self.dump = dump
735
736 def __repr__(self):
737 return self.dump
738
739
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700740failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700741
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800742def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700743 """Records an API failure to be processed later."""
744 global failures
745
Adrian Roosb787c182019-01-03 18:54:33 +0100746 sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700747 sig = sig.replace(" deprecated ", " ")
748
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800749 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700750
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700751
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800752def warn(clazz, detail, rule, msg):
753 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700754
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800755def error(clazz, detail, rule, msg):
756 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700757
758
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700759noticed = {}
760
761def notice(clazz):
762 global noticed
763
764 noticed[clazz.fullname] = hash(clazz)
765
766
Adrian Roos93bafc42019-03-05 18:31:37 +0100767verifiers = {}
768
769def verifier(f):
770 verifiers[f.__name__] = f
771 return f
772
773
774@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700775def verify_constants(clazz):
776 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700777 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600778 if clazz.fullname.startswith("android.os.Build"): return
779 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700780
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600781 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700782 for f in clazz.fields:
783 if "static" in f.split and "final" in f.split:
784 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800785 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600786 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700787 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
788 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600789 if f.typ in req and f.value is None:
790 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700791
Adrian Roos93bafc42019-03-05 18:31:37 +0100792@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700793def verify_enums(clazz):
794 """Enums are bad, mmkay?"""
Adrian Roosb787c182019-01-03 18:54:33 +0100795 if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800796 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700797
Adrian Roos93bafc42019-03-05 18:31:37 +0100798@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700799def verify_class_names(clazz):
800 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700801 if clazz.fullname.startswith("android.opengl"): return
802 if clazz.fullname.startswith("android.renderscript"): return
803 if re.match("android\.R\.[a-z]+", clazz.fullname): return
804
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700805 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800806 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700807 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800808 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700809 if clazz.name.endswith("Impl"):
810 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700811
812
Adrian Roos93bafc42019-03-05 18:31:37 +0100813@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700814def verify_method_names(clazz):
815 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700816 if clazz.fullname.startswith("android.opengl"): return
817 if clazz.fullname.startswith("android.renderscript"): return
818 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700819
820 for m in clazz.methods:
821 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800822 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700823 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800824 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700825
826
Adrian Roos93bafc42019-03-05 18:31:37 +0100827@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700828def verify_callbacks(clazz):
829 """Verify Callback classes.
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700830 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700831 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700832
833 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800834 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700835 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800836 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700837
838 if clazz.name.endswith("Callback"):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700839 for m in clazz.methods:
840 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800841 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700842
843
Adrian Roos93bafc42019-03-05 18:31:37 +0100844@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700845def verify_listeners(clazz):
846 """Verify Listener classes.
847 All Listener classes must be interface.
848 All methods must follow onFoo() naming style.
849 If only a single method, it must match class name:
850 interface OnFooListener { void onFoo() }"""
851
852 if clazz.name.endswith("Listener"):
Adrian Roosb787c182019-01-03 18:54:33 +0100853 if "abstract" in clazz.split and "class" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800854 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700855
856 for m in clazz.methods:
857 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800858 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700859
860 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
861 m = clazz.methods[0]
862 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800863 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700864
865
Adrian Roos93bafc42019-03-05 18:31:37 +0100866@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700867def verify_actions(clazz):
868 """Verify intent actions.
869 All action names must be named ACTION_FOO.
870 All action values must be scoped by package and match name:
871 package android.foo {
872 String ACTION_BAR = "android.foo.action.BAR";
873 }"""
874 for f in clazz.fields:
875 if f.value is None: continue
876 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700877 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600878 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700879
880 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
881 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
882 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800883 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700884 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700885 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700886 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700887 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700888 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700889 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
890 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700891 else:
892 prefix = clazz.pkg.name + ".action"
893 expected = prefix + "." + f.name[7:]
894 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700895 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700896
897
Adrian Roos93bafc42019-03-05 18:31:37 +0100898@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700899def verify_extras(clazz):
900 """Verify intent extras.
901 All extra names must be named EXTRA_FOO.
902 All extra values must be scoped by package and match name:
903 package android.foo {
904 String EXTRA_BAR = "android.foo.extra.BAR";
905 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700906 if clazz.fullname == "android.app.Notification": return
907 if clazz.fullname == "android.appwidget.AppWidgetManager": return
908
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700909 for f in clazz.fields:
910 if f.value is None: continue
911 if f.name.startswith("ACTION_"): continue
912
913 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
914 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
915 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800916 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700917 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700918 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700919 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700920 elif clazz.pkg.name == "android.app.admin":
921 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700922 else:
923 prefix = clazz.pkg.name + ".extra"
924 expected = prefix + "." + f.name[6:]
925 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700926 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700927
928
Adrian Roos93bafc42019-03-05 18:31:37 +0100929@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700930def verify_equals(clazz):
931 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700932 eq = False
933 hc = False
934 for m in clazz.methods:
Adrian Roosb787c182019-01-03 18:54:33 +0100935 if "static" in m.split: continue
936 if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
937 if m.sig_matches("int", "hashCode", []): hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700938 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800939 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700940
941
Adrian Roos93bafc42019-03-05 18:31:37 +0100942@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700943def verify_parcelable(clazz):
944 """Verify that Parcelable objects aren't hiding required bits."""
Adrian Roosb787c182019-01-03 18:54:33 +0100945 if clazz.implements == "android.os.Parcelable":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700946 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
947 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
948 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
949
950 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800951 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700952
Adrian Roosb787c182019-01-03 18:54:33 +0100953 if "final" not in clazz.split:
Jeff Sharkey331279b2016-02-29 16:02:02 -0700954 error(clazz, None, "FW8", "Parcelable classes must be final")
955
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700956 for c in clazz.ctors:
957 if c.args == ["android.os.Parcel"]:
958 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
959
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700960
Adrian Roos93bafc42019-03-05 18:31:37 +0100961@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700962def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800963 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700964 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600965 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700966 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800967 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700968 for f in clazz.fields:
969 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800970 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700971
972
Adrian Roos93bafc42019-03-05 18:31:37 +0100973@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700974def verify_fields(clazz):
975 """Verify that all exposed fields are final.
976 Exposed fields must follow myName style.
977 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700978
979 IGNORE_BARE_FIELDS = [
980 "android.app.ActivityManager.RecentTaskInfo",
981 "android.app.Notification",
982 "android.content.pm.ActivityInfo",
983 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600984 "android.content.pm.ComponentInfo",
985 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700986 "android.content.pm.FeatureGroupInfo",
987 "android.content.pm.InstrumentationInfo",
988 "android.content.pm.PackageInfo",
989 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600990 "android.content.res.Configuration",
991 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700992 "android.os.Message",
993 "android.system.StructPollfd",
994 ]
995
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700996 for f in clazz.fields:
997 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700998 if clazz.fullname in IGNORE_BARE_FIELDS:
999 pass
1000 elif clazz.fullname.endswith("LayoutParams"):
1001 pass
1002 elif clazz.fullname.startswith("android.util.Mutable"):
1003 pass
1004 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001005 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001006
Adrian Roosd1e38922019-01-14 15:44:15 +01001007 if "static" not in f.split and "property" not in f.split:
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001008 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001009 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001010
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001011 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001012 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001013
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001014 if re.match("[A-Z_]+", f.name):
1015 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001016 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001017
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001018
Adrian Roos93bafc42019-03-05 18:31:37 +01001019@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001020def verify_register(clazz):
1021 """Verify parity of registration methods.
1022 Callback objects use register/unregister methods.
1023 Listener objects use add/remove methods."""
1024 methods = [ m.name for m in clazz.methods ]
1025 for m in clazz.methods:
1026 if "Callback" in m.raw:
1027 if m.name.startswith("register"):
1028 other = "unregister" + m.name[8:]
1029 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001030 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001031 if m.name.startswith("unregister"):
1032 other = "register" + m.name[10:]
1033 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001034 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001035
1036 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001037 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001038
1039 if "Listener" in m.raw:
1040 if m.name.startswith("add"):
1041 other = "remove" + m.name[3:]
1042 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001043 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001044 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
1045 other = "add" + m.name[6:]
1046 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001047 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001048
1049 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001050 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001051
1052
Adrian Roos93bafc42019-03-05 18:31:37 +01001053@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001054def verify_sync(clazz):
1055 """Verify synchronized methods aren't exposed."""
1056 for m in clazz.methods:
1057 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001058 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001059
1060
Adrian Roos93bafc42019-03-05 18:31:37 +01001061@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001062def verify_intent_builder(clazz):
1063 """Verify that Intent builders are createFooIntent() style."""
1064 if clazz.name == "Intent": return
1065
1066 for m in clazz.methods:
1067 if m.typ == "android.content.Intent":
1068 if m.name.startswith("create") and m.name.endswith("Intent"):
1069 pass
1070 else:
Adam Powell539ea122015-04-10 13:01:37 -07001071 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001072
1073
Adrian Roos93bafc42019-03-05 18:31:37 +01001074@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001075def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001076 """Verify that helper classes are named consistently with what they extend.
1077 All developer extendable methods should be named onFoo()."""
1078 test_methods = False
Adrian Roosb787c182019-01-03 18:54:33 +01001079 if clazz.extends == "android.app.Service":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001080 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001081 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001082 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001083
1084 found = False
1085 for f in clazz.fields:
1086 if f.name == "SERVICE_INTERFACE":
1087 found = True
1088 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001089 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001090
Adrian Roosb787c182019-01-03 18:54:33 +01001091 if clazz.extends == "android.content.ContentProvider":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001092 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001093 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001094 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001095
1096 found = False
1097 for f in clazz.fields:
1098 if f.name == "PROVIDER_INTERFACE":
1099 found = True
1100 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001101 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001102
Adrian Roosb787c182019-01-03 18:54:33 +01001103 if clazz.extends == "android.content.BroadcastReceiver":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001104 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001105 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001106 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001107
Adrian Roosb787c182019-01-03 18:54:33 +01001108 if clazz.extends == "android.app.Activity":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001109 test_methods = True
1110 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001111 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001112
1113 if test_methods:
1114 for m in clazz.methods:
1115 if "final" in m.split: continue
1116 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001117 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001118 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001119 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001120 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001121
1122
Adrian Roos93bafc42019-03-05 18:31:37 +01001123@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001124def verify_builder(clazz):
1125 """Verify builder classes.
1126 Methods should return the builder to enable chaining."""
Adrian Roosb787c182019-01-03 18:54:33 +01001127 if clazz.extends: return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001128 if not clazz.name.endswith("Builder"): return
1129
1130 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001131 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001132
1133 has_build = False
1134 for m in clazz.methods:
1135 if m.name == "build":
1136 has_build = True
1137 continue
1138
1139 if m.name.startswith("get"): continue
1140 if m.name.startswith("clear"): continue
1141
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001142 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001143 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001144
1145 if m.name.startswith("set"):
1146 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001147 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001148
1149 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001150 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001151
Adrian Roosdeb0ff22019-02-27 23:58:13 +01001152 if "final" not in clazz.split:
1153 error(clazz, None, None, "Builder should be final")
1154
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001155
Adrian Roos93bafc42019-03-05 18:31:37 +01001156@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001157def verify_aidl(clazz):
1158 """Catch people exposing raw AIDL."""
Adrian Roosb787c182019-01-03 18:54:33 +01001159 if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001160 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001161
1162
Adrian Roos93bafc42019-03-05 18:31:37 +01001163@verifier
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001164def verify_internal(clazz):
1165 """Catch people exposing internal classes."""
1166 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001167 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001168
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001169def layering_build_ranking(ranking_list):
1170 r = {}
1171 for rank, ps in enumerate(ranking_list):
1172 if not isinstance(ps, list):
1173 ps = [ps]
1174 for p in ps:
1175 rs = r
1176 for n in p.split('.'):
1177 if n not in rs:
1178 rs[n] = {}
1179 rs = rs[n]
1180 rs['-rank'] = rank
1181 return r
1182
1183LAYERING_PACKAGE_RANKING = layering_build_ranking([
1184 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
1185 "android.app",
1186 "android.widget",
1187 "android.view",
1188 "android.animation",
1189 "android.provider",
1190 ["android.content","android.graphics.drawable"],
1191 "android.database",
1192 "android.text",
1193 "android.graphics",
1194 "android.os",
1195 "android.util"
1196])
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001197
Adrian Roos93bafc42019-03-05 18:31:37 +01001198@verifier
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001199def verify_layering(clazz):
1200 """Catch package layering violations.
1201 For example, something in android.os depending on android.app."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001202
1203 def rank(p):
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001204 r = None
1205 l = LAYERING_PACKAGE_RANKING
1206 for n in p.split('.'):
1207 if n in l:
1208 l = l[n]
1209 if '-rank' in l:
1210 r = l['-rank']
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001211 else:
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001212 break
1213 return r
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001214
1215 cr = rank(clazz.pkg.name)
1216 if cr is None: return
1217
1218 for f in clazz.fields:
1219 ir = rank(f.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001220 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001221 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001222
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001223 for m in itertools.chain(clazz.methods, clazz.ctors):
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001224 ir = rank(m.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001225 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001226 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001227 for arg in m.args:
1228 ir = rank(arg)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001229 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001230 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001231
1232
Adrian Roos93bafc42019-03-05 18:31:37 +01001233@verifier
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001234def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001235 """Verifies that boolean accessors are named correctly.
1236 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001237
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001238 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
1239 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001240
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001241 gets = [ m for m in clazz.methods if is_get(m) ]
1242 sets = [ m for m in clazz.methods if is_set(m) ]
1243
1244 def error_if_exists(methods, trigger, expected, actual):
1245 for m in methods:
1246 if m.name == actual:
1247 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001248
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001249 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001250 if is_get(m):
1251 if re.match("is[A-Z]", m.name):
1252 target = m.name[2:]
1253 expected = "setIs" + target
1254 error_if_exists(sets, m.name, expected, "setHas" + target)
1255 elif re.match("has[A-Z]", m.name):
1256 target = m.name[3:]
1257 expected = "setHas" + target
1258 error_if_exists(sets, m.name, expected, "setIs" + target)
1259 error_if_exists(sets, m.name, expected, "set" + target)
1260 elif re.match("get[A-Z]", m.name):
1261 target = m.name[3:]
1262 expected = "set" + target
1263 error_if_exists(sets, m.name, expected, "setIs" + target)
1264 error_if_exists(sets, m.name, expected, "setHas" + target)
1265
1266 if is_set(m):
1267 if re.match("set[A-Z]", m.name):
1268 target = m.name[3:]
1269 expected = "get" + target
1270 error_if_exists(sets, m.name, expected, "is" + target)
1271 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001272
1273
Adrian Roos93bafc42019-03-05 18:31:37 +01001274@verifier
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001275def verify_collections(clazz):
1276 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001277 if clazz.fullname == "android.os.Bundle": return
Adrian Roos02e18dd2019-02-28 12:41:48 +01001278 if clazz.fullname == "android.os.Parcel": return
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001279
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001280 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
1281 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
1282 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001283 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001284 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001285 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001286 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001287 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001288
Adrian Roos93bafc42019-03-05 18:31:37 +01001289
1290@verifier
Adrian Roos56ff7842019-02-28 12:45:00 +01001291def verify_uris(clazz):
1292 bad = ["java.net.URL", "java.net.URI", "android.net.URL"]
1293
1294 for f in clazz.fields:
1295 if f.typ in bad:
1296 error(clazz, f, None, "Field must be android.net.Uri instead of " + f.typ)
1297
1298 for m in clazz.methods + clazz.ctors:
1299 if m.typ in bad:
1300 error(clazz, m, None, "Must return android.net.Uri instead of " + m.typ)
1301 for arg in m.args:
1302 if arg in bad:
1303 error(clazz, m, None, "Argument must take android.net.Uri instead of " + arg)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001304
Adrian Roos93bafc42019-03-05 18:31:37 +01001305
1306@verifier
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001307def verify_flags(clazz):
1308 """Verifies that flags are non-overlapping."""
1309 known = collections.defaultdict(int)
1310 for f in clazz.fields:
1311 if "FLAG_" in f.name:
1312 try:
1313 val = int(f.value)
1314 except:
1315 continue
1316
1317 scope = f.name[0:f.name.index("FLAG_")]
1318 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001319 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001320 known[scope] |= val
1321
1322
Adrian Roos93bafc42019-03-05 18:31:37 +01001323@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001324def verify_exception(clazz):
1325 """Verifies that methods don't throw generic exceptions."""
1326 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001327 for t in m.throws:
1328 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
1329 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001330
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001331 if t in ["android.os.RemoteException"]:
Adrian Roos02e18dd2019-02-28 12:41:48 +01001332 if clazz.fullname == "android.content.ContentProviderClient": continue
1333 if clazz.fullname == "android.os.Binder": continue
1334 if clazz.fullname == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -07001335
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001336 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
1337
1338 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
1339 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -07001340
Adrian Roos93bafc42019-03-05 18:31:37 +01001341
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001342GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001343
Adrian Roos93bafc42019-03-05 18:31:37 +01001344# Not marked as @verifier, because it is only conditionally applied.
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001345def verify_google(clazz):
1346 """Verifies that APIs never reference Google."""
1347
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001348 if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001349 error(clazz, None, None, "Must never reference Google")
1350
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001351 for test in clazz.ctors, clazz.fields, clazz.methods:
1352 for t in test:
1353 if GOOGLE_IGNORECASE.search(t.raw) is not None:
1354 error(clazz, t, None, "Must never reference Google")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001355
1356
Adrian Roos93bafc42019-03-05 18:31:37 +01001357@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001358def verify_bitset(clazz):
1359 """Verifies that we avoid using heavy BitSet."""
1360
1361 for f in clazz.fields:
1362 if f.typ == "java.util.BitSet":
1363 error(clazz, f, None, "Field type must not be heavy BitSet")
1364
1365 for m in clazz.methods:
1366 if m.typ == "java.util.BitSet":
1367 error(clazz, m, None, "Return type must not be heavy BitSet")
1368 for arg in m.args:
1369 if arg == "java.util.BitSet":
1370 error(clazz, m, None, "Argument type must not be heavy BitSet")
1371
1372
Adrian Roos93bafc42019-03-05 18:31:37 +01001373@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001374def verify_manager(clazz):
1375 """Verifies that FooManager is only obtained from Context."""
1376
1377 if not clazz.name.endswith("Manager"): return
1378
1379 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001380 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001381
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001382 for m in clazz.methods:
1383 if m.typ == clazz.fullname:
1384 error(clazz, m, None, "Managers must always be obtained from Context")
1385
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001386
Adrian Roos93bafc42019-03-05 18:31:37 +01001387@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001388def verify_boxed(clazz):
1389 """Verifies that methods avoid boxed primitives."""
1390
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001391 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 -08001392
1393 for c in clazz.ctors:
1394 for arg in c.args:
1395 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001396 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001397
1398 for f in clazz.fields:
1399 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001400 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001401
1402 for m in clazz.methods:
1403 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001404 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001405 for arg in m.args:
1406 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001407 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001408
1409
Adrian Roos93bafc42019-03-05 18:31:37 +01001410@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001411def verify_static_utils(clazz):
1412 """Verifies that helper classes can't be constructed."""
1413 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001414 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001415
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001416 # Only care about classes with default constructors
1417 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
1418 test = []
1419 test.extend(clazz.fields)
1420 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001421
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001422 if len(test) == 0: return
1423 for t in test:
1424 if "static" not in t.split:
1425 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001426
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001427 error(clazz, None, None, "Fully-static utility classes must not have constructor")
1428
1429
Adrian Roos93bafc42019-03-05 18:31:37 +01001430# @verifier # Disabled for now
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001431def verify_overload_args(clazz):
1432 """Verifies that method overloads add new arguments at the end."""
1433 if clazz.fullname.startswith("android.opengl"): return
1434
1435 overloads = collections.defaultdict(list)
1436 for m in clazz.methods:
1437 if "deprecated" in m.split: continue
1438 overloads[m.name].append(m)
1439
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001440 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001441 if len(methods) <= 1: continue
1442
1443 # Look for arguments common across all overloads
1444 def cluster(args):
1445 count = collections.defaultdict(int)
1446 res = set()
1447 for i in range(len(args)):
1448 a = args[i]
1449 res.add("%s#%d" % (a, count[a]))
1450 count[a] += 1
1451 return res
1452
1453 common_args = cluster(methods[0].args)
1454 for m in methods:
1455 common_args = common_args & cluster(m.args)
1456
1457 if len(common_args) == 0: continue
1458
1459 # Require that all common arguments are present at start of signature
1460 locked_sig = None
1461 for m in methods:
1462 sig = m.args[0:len(common_args)]
1463 if not common_args.issubset(cluster(sig)):
1464 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1465 elif not locked_sig:
1466 locked_sig = sig
1467 elif locked_sig != sig:
1468 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1469
1470
Adrian Roos93bafc42019-03-05 18:31:37 +01001471@verifier
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001472def verify_callback_handlers(clazz):
1473 """Verifies that methods adding listener/callback have overload
1474 for specifying delivery thread."""
1475
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001476 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001477 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001478 "animation",
1479 "view",
1480 "graphics",
1481 "transition",
1482 "widget",
1483 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001484 ]
1485 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001486 if s in clazz.pkg.name_path: return
1487 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001488
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001489 # Ignore UI classes which assume main thread
1490 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1491 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1492 if s in clazz.fullname: return
1493 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1494 for s in ["Loader"]:
1495 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001496
1497 found = {}
1498 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001499 examine = clazz.ctors + clazz.methods
1500 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001501 if m.name.startswith("unregister"): continue
1502 if m.name.startswith("remove"): continue
1503 if re.match("on[A-Z]+", m.name): continue
1504
1505 by_name[m.name].append(m)
1506
1507 for a in m.args:
1508 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1509 found[m.name] = m
1510
1511 for f in found.values():
1512 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001513 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001514 for m in by_name[f.name]:
1515 if "android.os.Handler" in m.args:
1516 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001517 if "java.util.concurrent.Executor" in m.args:
1518 takes_exec = True
1519 if not takes_exec:
1520 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001521
1522
Adrian Roos93bafc42019-03-05 18:31:37 +01001523@verifier
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001524def verify_context_first(clazz):
1525 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001526 examine = clazz.ctors + clazz.methods
1527 for m in examine:
1528 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001529 if "android.content.Context" in m.args[1:]:
1530 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001531 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1532 if "android.content.ContentResolver" in m.args[1:]:
1533 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001534
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001535
Adrian Roos93bafc42019-03-05 18:31:37 +01001536@verifier
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001537def verify_listener_last(clazz):
1538 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1539 examine = clazz.ctors + clazz.methods
1540 for m in examine:
1541 if "Listener" in m.name or "Callback" in m.name: continue
1542 found = False
1543 for a in m.args:
1544 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1545 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001546 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001547 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1548
1549
Adrian Roos93bafc42019-03-05 18:31:37 +01001550@verifier
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001551def verify_resource_names(clazz):
1552 """Verifies that resource names have consistent case."""
1553 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1554
1555 # Resources defined by files are foo_bar_baz
1556 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1557 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001558 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1559 if f.name.startswith("config_"):
1560 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1561
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001562 if re.match("[a-z1-9_]+$", f.name): continue
1563 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1564
1565 # Resources defined inside files are fooBarBaz
1566 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1567 for f in clazz.fields:
1568 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1569 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1570 if re.match("state_[a-z_]*$", f.name): continue
1571
1572 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1573 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1574
1575 # Styles are FooBar_Baz
1576 if clazz.name in ["style"]:
1577 for f in clazz.fields:
1578 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1579 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001580
1581
Adrian Roos93bafc42019-03-05 18:31:37 +01001582@verifier
Jeff Sharkey331279b2016-02-29 16:02:02 -07001583def verify_files(clazz):
1584 """Verifies that methods accepting File also accept streams."""
1585
1586 has_file = set()
1587 has_stream = set()
1588
1589 test = []
1590 test.extend(clazz.ctors)
1591 test.extend(clazz.methods)
1592
1593 for m in test:
1594 if "java.io.File" in m.args:
1595 has_file.add(m)
1596 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:
1597 has_stream.add(m.name)
1598
1599 for m in has_file:
1600 if m.name not in has_stream:
1601 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1602
1603
Adrian Roos93bafc42019-03-05 18:31:37 +01001604@verifier
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001605def verify_manager_list(clazz):
1606 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1607
1608 if not clazz.name.endswith("Manager"): return
1609
1610 for m in clazz.methods:
1611 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1612 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1613
1614
Adrian Roos93bafc42019-03-05 18:31:37 +01001615@verifier
Jeff Sharkey26c80902016-12-21 13:41:17 -07001616def verify_abstract_inner(clazz):
1617 """Verifies that abstract inner classes are static."""
1618
1619 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
Adrian Roosb787c182019-01-03 18:54:33 +01001620 if "abstract" in clazz.split and "static" not in clazz.split:
Jeff Sharkey26c80902016-12-21 13:41:17 -07001621 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1622
1623
Adrian Roos93bafc42019-03-05 18:31:37 +01001624@verifier
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001625def verify_runtime_exceptions(clazz):
1626 """Verifies that runtime exceptions aren't listed in throws."""
1627
1628 banned = [
1629 "java.lang.NullPointerException",
1630 "java.lang.ClassCastException",
1631 "java.lang.IndexOutOfBoundsException",
1632 "java.lang.reflect.UndeclaredThrowableException",
1633 "java.lang.reflect.MalformedParametersException",
1634 "java.lang.reflect.MalformedParameterizedTypeException",
1635 "java.lang.invoke.WrongMethodTypeException",
1636 "java.lang.EnumConstantNotPresentException",
1637 "java.lang.IllegalMonitorStateException",
1638 "java.lang.SecurityException",
1639 "java.lang.UnsupportedOperationException",
1640 "java.lang.annotation.AnnotationTypeMismatchException",
1641 "java.lang.annotation.IncompleteAnnotationException",
1642 "java.lang.TypeNotPresentException",
1643 "java.lang.IllegalStateException",
1644 "java.lang.ArithmeticException",
1645 "java.lang.IllegalArgumentException",
1646 "java.lang.ArrayStoreException",
1647 "java.lang.NegativeArraySizeException",
1648 "java.util.MissingResourceException",
1649 "java.util.EmptyStackException",
1650 "java.util.concurrent.CompletionException",
1651 "java.util.concurrent.RejectedExecutionException",
1652 "java.util.IllformedLocaleException",
1653 "java.util.ConcurrentModificationException",
1654 "java.util.NoSuchElementException",
1655 "java.io.UncheckedIOException",
1656 "java.time.DateTimeException",
1657 "java.security.ProviderException",
1658 "java.nio.BufferUnderflowException",
1659 "java.nio.BufferOverflowException",
1660 ]
1661
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001662 examine = clazz.ctors + clazz.methods
1663 for m in examine:
1664 for t in m.throws:
1665 if t in banned:
1666 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001667
1668
Adrian Roos93bafc42019-03-05 18:31:37 +01001669@verifier
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001670def verify_error(clazz):
1671 """Verifies that we always use Exception instead of Error."""
1672 if not clazz.extends: return
1673 if clazz.extends.endswith("Error"):
1674 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1675 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1676 error(clazz, None, None, "Exceptions must be named FooException")
1677
1678
Adrian Roos93bafc42019-03-05 18:31:37 +01001679@verifier
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001680def verify_units(clazz):
1681 """Verifies that we use consistent naming for units."""
1682
1683 # If we find K, recommend replacing with V
1684 bad = {
1685 "Ns": "Nanos",
1686 "Ms": "Millis or Micros",
1687 "Sec": "Seconds", "Secs": "Seconds",
1688 "Hr": "Hours", "Hrs": "Hours",
1689 "Mo": "Months", "Mos": "Months",
1690 "Yr": "Years", "Yrs": "Years",
1691 "Byte": "Bytes", "Space": "Bytes",
1692 }
1693
1694 for m in clazz.methods:
1695 if m.typ not in ["short","int","long"]: continue
1696 for k, v in bad.iteritems():
1697 if m.name.endswith(k):
1698 error(clazz, m, None, "Expected method name units to be " + v)
1699 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1700 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1701 if m.name.endswith("Seconds"):
1702 error(clazz, m, None, "Returned time values must be in milliseconds")
1703
1704 for m in clazz.methods:
1705 typ = m.typ
1706 if typ == "void":
1707 if len(m.args) != 1: continue
1708 typ = m.args[0]
1709
1710 if m.name.endswith("Fraction") and typ != "float":
1711 error(clazz, m, None, "Fractions must use floats")
1712 if m.name.endswith("Percentage") and typ != "int":
1713 error(clazz, m, None, "Percentage must use ints")
1714
1715
Adrian Roos93bafc42019-03-05 18:31:37 +01001716@verifier
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001717def verify_closable(clazz):
1718 """Verifies that classes are AutoClosable."""
Adrian Roos02e18dd2019-02-28 12:41:48 +01001719 if "java.lang.AutoCloseable" in clazz.implements_all: return
1720 if "java.io.Closeable" in clazz.implements_all: return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001721
1722 for m in clazz.methods:
1723 if len(m.args) > 0: continue
1724 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1725 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1726 return
1727
1728
Adrian Roos93bafc42019-03-05 18:31:37 +01001729@verifier
Jake Wharton9e6738f2017-08-23 11:59:55 -04001730def verify_member_name_not_kotlin_keyword(clazz):
1731 """Prevent method names which are keywords in Kotlin."""
1732
1733 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1734 # This list does not include Java keywords as those are already impossible to use.
1735 keywords = [
1736 'as',
1737 'fun',
1738 'in',
1739 'is',
1740 'object',
1741 'typealias',
1742 'val',
1743 'var',
1744 'when',
1745 ]
1746
1747 for m in clazz.methods:
1748 if m.name in keywords:
1749 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1750 for f in clazz.fields:
1751 if f.name in keywords:
1752 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1753
1754
Adrian Roos93bafc42019-03-05 18:31:37 +01001755@verifier
Jake Wharton9e6738f2017-08-23 11:59:55 -04001756def verify_method_name_not_kotlin_operator(clazz):
1757 """Warn about method names which become operators in Kotlin."""
1758
1759 binary = set()
1760
1761 def unique_binary_op(m, op):
1762 if op in binary:
1763 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1764 binary.add(op)
1765
1766 for m in clazz.methods:
Adrian Roosd1e38922019-01-14 15:44:15 +01001767 if 'static' in m.split or 'operator' in m.split:
Jake Wharton9e6738f2017-08-23 11:59:55 -04001768 continue
1769
1770 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1771 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1772 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1773
1774 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1775 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1776 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1777 # practical way of checking that relationship here.
1778 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1779
1780 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1781 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1782 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1783 unique_binary_op(m, m.name)
1784
1785 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1786 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1787 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1788
1789 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1790 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1791 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1792
1793 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1794 if m.name == 'invoke':
1795 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1796
1797 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1798 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1799 and len(m.args) == 1 \
1800 and m.typ == 'void':
1801 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1802 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1803
1804
Adrian Roos93bafc42019-03-05 18:31:37 +01001805@verifier
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001806def verify_collections_over_arrays(clazz):
1807 """Warn that [] should be Collections."""
1808
Adrian Roosb787c182019-01-03 18:54:33 +01001809 if "@interface" in clazz.split:
1810 return
1811
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001812 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1813 for m in clazz.methods:
1814 if m.typ.endswith("[]") and m.typ not in safe:
1815 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1816 for arg in m.args:
1817 if arg.endswith("[]") and arg not in safe:
1818 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1819
1820
Adrian Roos93bafc42019-03-05 18:31:37 +01001821@verifier
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001822def verify_user_handle(clazz):
1823 """Methods taking UserHandle should be ForUser or AsUser."""
1824 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1825 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1826 if clazz.fullname == "android.content.pm.LauncherApps": return
1827 if clazz.fullname == "android.os.UserHandle": return
1828 if clazz.fullname == "android.os.UserManager": return
1829
1830 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001831 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001832
1833 has_arg = "android.os.UserHandle" in m.args
1834 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1835
1836 if clazz.fullname.endswith("Manager") and has_arg:
1837 warn(clazz, m, None, "When a method overload is needed to target a specific "
1838 "UserHandle, callers should be directed to use "
1839 "Context.createPackageContextAsUser() and re-obtain the relevant "
1840 "Manager, and no new API should be added")
1841 elif has_arg and not has_name:
1842 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1843 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001844
1845
Adrian Roos93bafc42019-03-05 18:31:37 +01001846@verifier
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001847def verify_params(clazz):
1848 """Parameter classes should be 'Params'."""
1849 if clazz.name.endswith("Params"): return
1850 if clazz.fullname == "android.app.ActivityOptions": return
1851 if clazz.fullname == "android.app.BroadcastOptions": return
1852 if clazz.fullname == "android.os.Bundle": return
1853 if clazz.fullname == "android.os.BaseBundle": return
1854 if clazz.fullname == "android.os.PersistableBundle": return
1855
1856 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1857 for b in bad:
1858 if clazz.name.endswith(b):
1859 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1860
1861
Adrian Roos93bafc42019-03-05 18:31:37 +01001862@verifier
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001863def verify_services(clazz):
1864 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1865 if clazz.fullname != "android.content.Context": return
1866
1867 for f in clazz.fields:
1868 if f.typ != "java.lang.String": continue
1869 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1870 if found:
1871 expected = found.group(1).lower()
1872 if f.value != expected:
1873 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1874
1875
Adrian Roos93bafc42019-03-05 18:31:37 +01001876@verifier
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001877def verify_tense(clazz):
1878 """Verify tenses of method names."""
1879 if clazz.fullname.startswith("android.opengl"): return
1880
1881 for m in clazz.methods:
1882 if m.name.endswith("Enable"):
1883 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1884
1885
Adrian Roos93bafc42019-03-05 18:31:37 +01001886@verifier
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001887def verify_icu(clazz):
1888 """Verifies that richer ICU replacements are used."""
1889 better = {
1890 "java.util.TimeZone": "android.icu.util.TimeZone",
1891 "java.util.Calendar": "android.icu.util.Calendar",
1892 "java.util.Locale": "android.icu.util.ULocale",
1893 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1894 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1895 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1896 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1897 "java.lang.Character": "android.icu.lang.UCharacter",
1898 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1899 "java.text.Collator": "android.icu.text.Collator",
1900 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1901 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1902 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1903 "java.text.DateFormat": "android.icu.text.DateFormat",
1904 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1905 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1906 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1907 }
1908
1909 for m in clazz.ctors + clazz.methods:
1910 types = []
1911 types.extend(m.typ)
1912 types.extend(m.args)
1913 for arg in types:
1914 if arg in better:
1915 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1916
1917
Adrian Roos93bafc42019-03-05 18:31:37 +01001918@verifier
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001919def verify_clone(clazz):
1920 """Verify that clone() isn't implemented; see EJ page 61."""
1921 for m in clazz.methods:
1922 if m.name == "clone":
1923 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1924
1925
Adrian Roos93bafc42019-03-05 18:31:37 +01001926@verifier
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001927def verify_pfd(clazz):
1928 """Verify that android APIs use PFD over FD."""
Adrian Roos02e18dd2019-02-28 12:41:48 +01001929 if clazz.fullname == "android.os.FileUtils" or clazz.fullname == "android.system.Os":
1930 return
1931
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001932 examine = clazz.ctors + clazz.methods
1933 for m in examine:
1934 if m.typ == "java.io.FileDescriptor":
1935 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1936 if m.typ == "int":
1937 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1938 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1939 for arg in m.args:
1940 if arg == "java.io.FileDescriptor":
1941 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1942
1943 for f in clazz.fields:
1944 if f.typ == "java.io.FileDescriptor":
1945 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1946
1947
Adrian Roos93bafc42019-03-05 18:31:37 +01001948@verifier
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001949def verify_numbers(clazz):
1950 """Discourage small numbers types like short and byte."""
1951
1952 discouraged = ["short","byte"]
1953
1954 for c in clazz.ctors:
1955 for arg in c.args:
1956 if arg in discouraged:
1957 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1958
1959 for f in clazz.fields:
1960 if f.typ in discouraged:
1961 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1962
1963 for m in clazz.methods:
1964 if m.typ in discouraged:
1965 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1966 for arg in m.args:
1967 if arg in discouraged:
1968 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1969
Adrian Roos93bafc42019-03-05 18:31:37 +01001970
Adrian Roos80545ef2019-02-27 16:45:00 +01001971PRIMITIVES = {"void", "int", "float", "boolean", "short", "char", "byte", "long", "double"}
1972
Adrian Roos93bafc42019-03-05 18:31:37 +01001973@verifier
Adrian Roos80545ef2019-02-27 16:45:00 +01001974def verify_nullability(clazz):
1975 """Catches missing nullability annotations"""
1976
1977 for f in clazz.fields:
1978 if f.value is not None and 'static' in f.split and 'final' in f.split:
1979 continue # Nullability of constants can be inferred.
1980 if f.typ not in PRIMITIVES and not has_nullability(f.annotations):
1981 error(clazz, f, "M12", "Field must be marked either @NonNull or @Nullable")
1982
1983 for c in clazz.ctors:
1984 verify_nullability_args(clazz, c)
1985
1986 for m in clazz.methods:
1987 if m.name == "writeToParcel" or m.name == "onReceive":
1988 continue # Parcelable.writeToParcel() and BroadcastReceiver.onReceive() are not yet annotated
1989
1990 if m.typ not in PRIMITIVES and not has_nullability(m.annotations):
1991 error(clazz, m, "M12", "Return value must be marked either @NonNull or @Nullable")
1992 verify_nullability_args(clazz, m)
1993
1994def verify_nullability_args(clazz, m):
1995 for i, arg in enumerate(m.detailed_args):
1996 if arg.type not in PRIMITIVES and not has_nullability(arg.annotations):
1997 error(clazz, m, "M12", "Argument %d must be marked either @NonNull or @Nullable" % (i+1,))
1998
1999def has_nullability(annotations):
2000 return "@NonNull" in annotations or "@Nullable" in annotations
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06002001
Adrian Roos93bafc42019-03-05 18:31:37 +01002002
2003@verifier
Adrian Roos34782392019-03-06 13:38:10 +01002004def verify_intdef(clazz):
2005 """intdefs must be @hide, because the constant names cannot be stored in
2006 the stubs (only the values are, which is not useful)"""
2007 if "@interface" not in clazz.split:
2008 return
2009 if "@IntDef" in clazz.annotations or "@LongDef" in clazz.annotations:
2010 error(clazz, None, None, "@IntDef and @LongDef annotations must be @hide")
2011
2012
2013@verifier
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06002014def verify_singleton(clazz):
2015 """Catch singleton objects with constructors."""
2016
2017 singleton = False
2018 for m in clazz.methods:
2019 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
2020 singleton = True
2021
2022 if singleton:
2023 for c in clazz.ctors:
2024 error(clazz, c, None, "Singleton classes should use getInstance() methods")
2025
2026
2027
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002028def is_interesting(clazz):
2029 """Test if given class is interesting from an Android PoV."""
2030
2031 if clazz.pkg.name.startswith("java"): return False
2032 if clazz.pkg.name.startswith("junit"): return False
2033 if clazz.pkg.name.startswith("org.apache"): return False
2034 if clazz.pkg.name.startswith("org.xml"): return False
2035 if clazz.pkg.name.startswith("org.json"): return False
2036 if clazz.pkg.name.startswith("org.w3c"): return False
2037 if clazz.pkg.name.startswith("android.icu."): return False
2038 return True
2039
2040
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002041def examine_clazz(clazz):
2042 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002043
2044 notice(clazz)
2045
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002046 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002047
Adrian Roos93bafc42019-03-05 18:31:37 +01002048 for v in verifiers.itervalues():
2049 v(clazz)
2050
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002051 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002052
2053
Adrian Roos038a0292018-12-19 17:11:21 +01002054def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002055 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002056 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002057 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002058 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01002059 _parse_stream(stream, examine_clazz, base_f=base_stream,
2060 in_classes_with_base=in_classes_with_base,
2061 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002062 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002063
2064
2065def examine_api(api):
2066 """Find all style issues in the given parsed API."""
2067 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07002068 failures = {}
2069 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002070 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002071 return failures
2072
2073
Jeff Sharkey037458a2014-09-04 15:46:20 -07002074def verify_compat(cur, prev):
2075 """Find any incompatible API changes between two levels."""
2076 global failures
2077
2078 def class_exists(api, test):
2079 return test.fullname in api
2080
2081 def ctor_exists(api, clazz, test):
2082 for m in clazz.ctors:
2083 if m.ident == test.ident: return True
2084 return False
2085
2086 def all_methods(api, clazz):
2087 methods = list(clazz.methods)
2088 if clazz.extends is not None:
2089 methods.extend(all_methods(api, api[clazz.extends]))
2090 return methods
2091
2092 def method_exists(api, clazz, test):
2093 methods = all_methods(api, clazz)
2094 for m in methods:
2095 if m.ident == test.ident: return True
2096 return False
2097
2098 def field_exists(api, clazz, test):
2099 for f in clazz.fields:
2100 if f.ident == test.ident: return True
2101 return False
2102
2103 failures = {}
2104 for key in sorted(prev.keys()):
2105 prev_clazz = prev[key]
2106
2107 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002108 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002109 continue
2110
2111 cur_clazz = cur[key]
2112
2113 for test in prev_clazz.ctors:
2114 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002115 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002116
2117 methods = all_methods(prev, prev_clazz)
2118 for test in methods:
2119 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002120 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002121
2122 for test in prev_clazz.fields:
2123 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002124 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002125
2126 return failures
2127
2128
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002129def show_deprecations_at_birth(cur, prev):
2130 """Show API deprecations at birth."""
2131 global failures
2132
2133 # Remove all existing things so we're left with new
2134 for prev_clazz in prev.values():
2135 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002136 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002137
2138 sigs = { i.ident: i for i in prev_clazz.ctors }
2139 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
2140 sigs = { i.ident: i for i in prev_clazz.methods }
2141 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
2142 sigs = { i.ident: i for i in prev_clazz.fields }
2143 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
2144
2145 # Forget about class entirely when nothing new
2146 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
2147 del cur[prev_clazz.fullname]
2148
2149 for clazz in cur.values():
Adrian Roosb787c182019-01-03 18:54:33 +01002150 if "deprecated" in clazz.split and not clazz.fullname in prev:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002151 error(clazz, None, None, "Found API deprecation at birth")
2152
2153 for i in clazz.ctors + clazz.methods + clazz.fields:
Adrian Roosb787c182019-01-03 18:54:33 +01002154 if "deprecated" in i.split:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002155 error(clazz, i, None, "Found API deprecation at birth")
2156
2157 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
2158 format(reset=True)))
2159 for f in sorted(failures):
2160 print failures[f]
2161 print
2162
2163
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002164def show_stats(cur, prev):
2165 """Show API stats."""
2166
2167 stats = collections.defaultdict(int)
2168 for cur_clazz in cur.values():
2169 if not is_interesting(cur_clazz): continue
2170
2171 if cur_clazz.fullname not in prev:
2172 stats['new_classes'] += 1
2173 stats['new_ctors'] += len(cur_clazz.ctors)
2174 stats['new_methods'] += len(cur_clazz.methods)
2175 stats['new_fields'] += len(cur_clazz.fields)
2176 else:
2177 prev_clazz = prev[cur_clazz.fullname]
2178
2179 sigs = { i.ident: i for i in prev_clazz.ctors }
2180 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
2181 sigs = { i.ident: i for i in prev_clazz.methods }
2182 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
2183 sigs = { i.ident: i for i in prev_clazz.fields }
2184 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
2185
2186 if ctors + methods + fields > 0:
2187 stats['extend_classes'] += 1
2188 stats['extend_ctors'] += ctors
2189 stats['extend_methods'] += methods
2190 stats['extend_fields'] += fields
2191
2192 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
2193 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
2194
2195
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002196if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002197 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
2198 patterns. It ignores lint messages from a previous API level, if provided.")
2199 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
2200 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
2201 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01002202 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
2203 help="The base current.txt to use when examining system-current.txt or"
2204 " test-current.txt")
2205 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
2206 help="The base previous.txt to use when examining system-previous.txt or"
2207 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002208 parser.add_argument("--no-color", action='store_const', const=True,
2209 help="Disable terminal colors")
2210 parser.add_argument("--allow-google", action='store_const', const=True,
2211 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002212 parser.add_argument("--show-noticed", action='store_const', const=True,
2213 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002214 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
2215 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002216 parser.add_argument("--show-stats", action='store_const', const=True,
2217 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002218 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002219
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002220 if args['no_color']:
2221 USE_COLOR = False
2222
2223 if args['allow_google']:
2224 ALLOW_GOOGLE = True
2225
2226 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002227 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002228 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002229 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002230
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002231 if args['show_deprecations_at_birth']:
2232 with current_file as f:
2233 cur = _parse_stream(f)
2234 with previous_file as f:
2235 prev = _parse_stream(f)
2236 show_deprecations_at_birth(cur, prev)
2237 sys.exit()
2238
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002239 if args['show_stats']:
2240 with current_file as f:
2241 cur = _parse_stream(f)
2242 with previous_file as f:
2243 prev = _parse_stream(f)
2244 show_stats(cur, prev)
2245 sys.exit()
2246
Adrian Roos038a0292018-12-19 17:11:21 +01002247 classes_with_base = []
2248
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002249 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002250 if base_current_file:
2251 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002252 cur_fail, cur_noticed = examine_stream(f, base_f,
2253 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002254 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002255 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
2256
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002257 if not previous_file is None:
2258 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002259 if base_previous_file:
2260 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002261 prev_fail, prev_noticed = examine_stream(f, base_f,
2262 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002263 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002264 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002265
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002266 # ignore errors from previous API level
2267 for p in prev_fail:
2268 if p in cur_fail:
2269 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002270
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002271 # ignore classes unchanged from previous API level
2272 for k, v in prev_noticed.iteritems():
2273 if k in cur_noticed and v == cur_noticed[k]:
2274 del cur_noticed[k]
2275
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002276 """
2277 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002278 # look for compatibility issues
2279 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002280
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002281 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2282 for f in sorted(compat_fail):
2283 print compat_fail[f]
2284 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002285 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002286
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002287 if args['show_noticed'] and len(cur_noticed) != 0:
2288 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2289 for f in sorted(cur_noticed.keys()):
2290 print f
2291 print
2292
Jason Monk53b2a732017-11-10 15:43:17 -05002293 if len(cur_fail) != 0:
2294 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2295 for f in sorted(cur_fail):
2296 print cur_fail[f]
2297 print
2298 sys.exit(77)