blob: 9e42c044e20954996f6675d2a4d6529dbbca4fe3 [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):
Joe Onorato680d4fc2019-05-07 16:22:16 -0700710 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700711 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700712 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800713 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700714 self.msg = msg
715
716 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800717 self.head = "Error %s" % (rule) if rule else "Error"
718 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 -0700719 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800720 self.head = "Warning %s" % (rule) if rule else "Warning"
721 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 -0700722
723 self.line = clazz.line
724 blame = clazz.blame
725 if detail is not None:
726 dump += "\n in " + repr(detail)
727 self.line = detail.line
728 blame = detail.blame
729 dump += "\n in " + repr(clazz)
730 dump += "\n in " + repr(clazz.pkg)
731 dump += "\n at line " + repr(self.line)
732 if blame is not None:
733 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
734
735 self.dump = dump
736
737 def __repr__(self):
738 return self.dump
739
740
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700741failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700742
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800743def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700744 """Records an API failure to be processed later."""
745 global failures
746
Adrian Roosb787c182019-01-03 18:54:33 +0100747 sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700748 sig = sig.replace(" deprecated ", " ")
749
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800750 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700751
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700752
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800753def warn(clazz, detail, rule, msg):
754 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700755
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800756def error(clazz, detail, rule, msg):
757 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700758
759
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700760noticed = {}
761
762def notice(clazz):
763 global noticed
764
765 noticed[clazz.fullname] = hash(clazz)
766
767
Adrian Roos93bafc42019-03-05 18:31:37 +0100768verifiers = {}
769
770def verifier(f):
771 verifiers[f.__name__] = f
772 return f
773
774
775@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700776def verify_constants(clazz):
777 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700778 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600779 if clazz.fullname.startswith("android.os.Build"): return
780 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700781
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600782 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700783 for f in clazz.fields:
784 if "static" in f.split and "final" in f.split:
785 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800786 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600787 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700788 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
789 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600790 if f.typ in req and f.value is None:
791 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700792
Adrian Roos93bafc42019-03-05 18:31:37 +0100793@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700794def verify_enums(clazz):
795 """Enums are bad, mmkay?"""
Adrian Roosb787c182019-01-03 18:54:33 +0100796 if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800797 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700798
Adrian Roos93bafc42019-03-05 18:31:37 +0100799@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700800def verify_class_names(clazz):
801 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700802 if clazz.fullname.startswith("android.opengl"): return
803 if clazz.fullname.startswith("android.renderscript"): return
804 if re.match("android\.R\.[a-z]+", clazz.fullname): return
805
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700806 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800807 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700808 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800809 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700810 if clazz.name.endswith("Impl"):
811 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700812
813
Adrian Roos93bafc42019-03-05 18:31:37 +0100814@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700815def verify_method_names(clazz):
816 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700817 if clazz.fullname.startswith("android.opengl"): return
818 if clazz.fullname.startswith("android.renderscript"): return
819 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700820
821 for m in clazz.methods:
822 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800823 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700824 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800825 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700826
827
Adrian Roos93bafc42019-03-05 18:31:37 +0100828@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700829def verify_callbacks(clazz):
830 """Verify Callback classes.
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700831 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700832 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700833
834 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800835 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700836 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800837 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700838
839 if clazz.name.endswith("Callback"):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700840 for m in clazz.methods:
841 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800842 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700843
844
Adrian Roos93bafc42019-03-05 18:31:37 +0100845@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700846def verify_listeners(clazz):
847 """Verify Listener classes.
848 All Listener classes must be interface.
849 All methods must follow onFoo() naming style.
850 If only a single method, it must match class name:
851 interface OnFooListener { void onFoo() }"""
852
853 if clazz.name.endswith("Listener"):
Adrian Roosb787c182019-01-03 18:54:33 +0100854 if "abstract" in clazz.split and "class" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800855 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700856
857 for m in clazz.methods:
858 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800859 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700860
861 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
862 m = clazz.methods[0]
863 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800864 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700865
866
Adrian Roos93bafc42019-03-05 18:31:37 +0100867@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700868def verify_actions(clazz):
869 """Verify intent actions.
870 All action names must be named ACTION_FOO.
871 All action values must be scoped by package and match name:
872 package android.foo {
873 String ACTION_BAR = "android.foo.action.BAR";
874 }"""
875 for f in clazz.fields:
876 if f.value is None: continue
877 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700878 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600879 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700880
881 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
882 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
883 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800884 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700885 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700886 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700887 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700888 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700889 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700890 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
891 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700892 else:
893 prefix = clazz.pkg.name + ".action"
894 expected = prefix + "." + f.name[7:]
895 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700896 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700897
898
Adrian Roos93bafc42019-03-05 18:31:37 +0100899@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700900def verify_extras(clazz):
901 """Verify intent extras.
902 All extra names must be named EXTRA_FOO.
903 All extra values must be scoped by package and match name:
904 package android.foo {
905 String EXTRA_BAR = "android.foo.extra.BAR";
906 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700907 if clazz.fullname == "android.app.Notification": return
908 if clazz.fullname == "android.appwidget.AppWidgetManager": return
909
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700910 for f in clazz.fields:
911 if f.value is None: continue
912 if f.name.startswith("ACTION_"): continue
913
914 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
915 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
916 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800917 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700918 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700919 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700920 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700921 elif clazz.pkg.name == "android.app.admin":
922 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700923 else:
924 prefix = clazz.pkg.name + ".extra"
925 expected = prefix + "." + f.name[6:]
926 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700927 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700928
929
Adrian Roos93bafc42019-03-05 18:31:37 +0100930@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700931def verify_equals(clazz):
932 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e62016-12-21 13:46:33 -0700933 eq = False
934 hc = False
935 for m in clazz.methods:
Adrian Roosb787c182019-01-03 18:54:33 +0100936 if "static" in m.split: continue
937 if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
938 if m.sig_matches("int", "hashCode", []): hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700939 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800940 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700941
942
Adrian Roos93bafc42019-03-05 18:31:37 +0100943@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700944def verify_parcelable(clazz):
945 """Verify that Parcelable objects aren't hiding required bits."""
Adrian Roosb787c182019-01-03 18:54:33 +0100946 if clazz.implements == "android.os.Parcelable":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700947 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
948 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
949 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
950
951 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800952 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700953
Adrian Roosb787c182019-01-03 18:54:33 +0100954 if "final" not in clazz.split:
Jeff Sharkey331279b2016-02-29 16:02:02 -0700955 error(clazz, None, "FW8", "Parcelable classes must be final")
956
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700957 for c in clazz.ctors:
958 if c.args == ["android.os.Parcel"]:
959 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
960
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700961
Adrian Roos93bafc42019-03-05 18:31:37 +0100962@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700963def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800964 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700965 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600966 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700967 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800968 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700969 for f in clazz.fields:
970 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800971 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700972
973
Adrian Roos93bafc42019-03-05 18:31:37 +0100974@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700975def verify_fields(clazz):
976 """Verify that all exposed fields are final.
977 Exposed fields must follow myName style.
978 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700979
980 IGNORE_BARE_FIELDS = [
981 "android.app.ActivityManager.RecentTaskInfo",
982 "android.app.Notification",
983 "android.content.pm.ActivityInfo",
984 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600985 "android.content.pm.ComponentInfo",
986 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700987 "android.content.pm.FeatureGroupInfo",
988 "android.content.pm.InstrumentationInfo",
989 "android.content.pm.PackageInfo",
990 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600991 "android.content.res.Configuration",
992 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700993 "android.os.Message",
994 "android.system.StructPollfd",
995 ]
996
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700997 for f in clazz.fields:
998 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700999 if clazz.fullname in IGNORE_BARE_FIELDS:
1000 pass
1001 elif clazz.fullname.endswith("LayoutParams"):
1002 pass
1003 elif clazz.fullname.startswith("android.util.Mutable"):
1004 pass
1005 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001006 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001007
Adrian Roosd1e38922019-01-14 15:44:15 +01001008 if "static" not in f.split and "property" not in f.split:
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001009 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001010 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001011
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001012 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001013 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001014
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001015 if re.match("[A-Z_]+", f.name):
1016 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001017 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001018
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001019
Adrian Roos93bafc42019-03-05 18:31:37 +01001020@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001021def verify_register(clazz):
1022 """Verify parity of registration methods.
1023 Callback objects use register/unregister methods.
1024 Listener objects use add/remove methods."""
1025 methods = [ m.name for m in clazz.methods ]
1026 for m in clazz.methods:
1027 if "Callback" in m.raw:
1028 if m.name.startswith("register"):
1029 other = "unregister" + m.name[8:]
1030 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001031 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001032 if m.name.startswith("unregister"):
1033 other = "register" + m.name[10:]
1034 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001035 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001036
1037 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001038 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001039
1040 if "Listener" in m.raw:
1041 if m.name.startswith("add"):
1042 other = "remove" + m.name[3:]
1043 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001044 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001045 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
1046 other = "add" + m.name[6:]
1047 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001048 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001049
1050 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001051 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001052
1053
Adrian Roos93bafc42019-03-05 18:31:37 +01001054@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001055def verify_sync(clazz):
1056 """Verify synchronized methods aren't exposed."""
1057 for m in clazz.methods:
1058 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001059 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001060
1061
Adrian Roos93bafc42019-03-05 18:31:37 +01001062@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001063def verify_intent_builder(clazz):
1064 """Verify that Intent builders are createFooIntent() style."""
1065 if clazz.name == "Intent": return
1066
1067 for m in clazz.methods:
1068 if m.typ == "android.content.Intent":
1069 if m.name.startswith("create") and m.name.endswith("Intent"):
1070 pass
1071 else:
Adam Powell539ea122015-04-10 13:01:37 -07001072 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001073
1074
Adrian Roos93bafc42019-03-05 18:31:37 +01001075@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001076def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001077 """Verify that helper classes are named consistently with what they extend.
1078 All developer extendable methods should be named onFoo()."""
1079 test_methods = False
Adrian Roosb787c182019-01-03 18:54:33 +01001080 if clazz.extends == "android.app.Service":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001081 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001082 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001083 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001084
1085 found = False
1086 for f in clazz.fields:
1087 if f.name == "SERVICE_INTERFACE":
1088 found = True
1089 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001090 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001091
Adrian Roosb787c182019-01-03 18:54:33 +01001092 if clazz.extends == "android.content.ContentProvider":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001093 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001094 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001095 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001096
1097 found = False
1098 for f in clazz.fields:
1099 if f.name == "PROVIDER_INTERFACE":
1100 found = True
1101 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001102 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001103
Adrian Roosb787c182019-01-03 18:54:33 +01001104 if clazz.extends == "android.content.BroadcastReceiver":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001105 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001106 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001107 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001108
Adrian Roosb787c182019-01-03 18:54:33 +01001109 if clazz.extends == "android.app.Activity":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001110 test_methods = True
1111 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001112 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001113
1114 if test_methods:
1115 for m in clazz.methods:
1116 if "final" in m.split: continue
1117 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001118 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001119 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001120 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001121 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001122
1123
Adrian Roos93bafc42019-03-05 18:31:37 +01001124@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001125def verify_builder(clazz):
1126 """Verify builder classes.
1127 Methods should return the builder to enable chaining."""
Adrian Roosb787c182019-01-03 18:54:33 +01001128 if clazz.extends: return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001129 if not clazz.name.endswith("Builder"): return
1130
1131 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001132 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001133
1134 has_build = False
1135 for m in clazz.methods:
1136 if m.name == "build":
1137 has_build = True
1138 continue
1139
1140 if m.name.startswith("get"): continue
1141 if m.name.startswith("clear"): continue
1142
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001143 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001144 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001145
1146 if m.name.startswith("set"):
1147 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001148 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001149
1150 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001151 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001152
Adrian Roosdeb0ff22019-02-27 23:58:13 +01001153 if "final" not in clazz.split:
1154 error(clazz, None, None, "Builder should be final")
1155
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001156
Adrian Roos93bafc42019-03-05 18:31:37 +01001157@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001158def verify_aidl(clazz):
1159 """Catch people exposing raw AIDL."""
Adrian Roosb787c182019-01-03 18:54:33 +01001160 if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001161 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001162
1163
Adrian Roos93bafc42019-03-05 18:31:37 +01001164@verifier
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001165def verify_internal(clazz):
1166 """Catch people exposing internal classes."""
1167 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001168 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001169
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001170def layering_build_ranking(ranking_list):
1171 r = {}
1172 for rank, ps in enumerate(ranking_list):
1173 if not isinstance(ps, list):
1174 ps = [ps]
1175 for p in ps:
1176 rs = r
1177 for n in p.split('.'):
1178 if n not in rs:
1179 rs[n] = {}
1180 rs = rs[n]
1181 rs['-rank'] = rank
1182 return r
1183
1184LAYERING_PACKAGE_RANKING = layering_build_ranking([
1185 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
1186 "android.app",
1187 "android.widget",
1188 "android.view",
1189 "android.animation",
1190 "android.provider",
1191 ["android.content","android.graphics.drawable"],
1192 "android.database",
1193 "android.text",
1194 "android.graphics",
1195 "android.os",
1196 "android.util"
1197])
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001198
Adrian Roos93bafc42019-03-05 18:31:37 +01001199@verifier
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001200def verify_layering(clazz):
1201 """Catch package layering violations.
1202 For example, something in android.os depending on android.app."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001203
1204 def rank(p):
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001205 r = None
1206 l = LAYERING_PACKAGE_RANKING
1207 for n in p.split('.'):
1208 if n in l:
1209 l = l[n]
1210 if '-rank' in l:
1211 r = l['-rank']
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001212 else:
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001213 break
1214 return r
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001215
1216 cr = rank(clazz.pkg.name)
1217 if cr is None: return
1218
1219 for f in clazz.fields:
1220 ir = rank(f.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001221 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001222 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001223
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001224 for m in itertools.chain(clazz.methods, clazz.ctors):
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001225 ir = rank(m.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001226 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001227 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001228 for arg in m.args:
1229 ir = rank(arg)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001230 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001231 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001232
1233
Adrian Roos93bafc42019-03-05 18:31:37 +01001234@verifier
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001235def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001236 """Verifies that boolean accessors are named correctly.
1237 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001238
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001239 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
1240 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001241
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001242 gets = [ m for m in clazz.methods if is_get(m) ]
1243 sets = [ m for m in clazz.methods if is_set(m) ]
1244
1245 def error_if_exists(methods, trigger, expected, actual):
1246 for m in methods:
1247 if m.name == actual:
1248 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001249
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001250 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001251 if is_get(m):
1252 if re.match("is[A-Z]", m.name):
1253 target = m.name[2:]
1254 expected = "setIs" + target
1255 error_if_exists(sets, m.name, expected, "setHas" + target)
1256 elif re.match("has[A-Z]", m.name):
1257 target = m.name[3:]
1258 expected = "setHas" + target
1259 error_if_exists(sets, m.name, expected, "setIs" + target)
1260 error_if_exists(sets, m.name, expected, "set" + target)
1261 elif re.match("get[A-Z]", m.name):
1262 target = m.name[3:]
1263 expected = "set" + target
1264 error_if_exists(sets, m.name, expected, "setIs" + target)
1265 error_if_exists(sets, m.name, expected, "setHas" + target)
1266
1267 if is_set(m):
1268 if re.match("set[A-Z]", m.name):
1269 target = m.name[3:]
1270 expected = "get" + target
1271 error_if_exists(sets, m.name, expected, "is" + target)
1272 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001273
1274
Adrian Roos93bafc42019-03-05 18:31:37 +01001275@verifier
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001276def verify_collections(clazz):
1277 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001278 if clazz.fullname == "android.os.Bundle": return
Adrian Roos02e18dd2019-02-28 12:41:48 +01001279 if clazz.fullname == "android.os.Parcel": return
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001280
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001281 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
1282 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
1283 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001284 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001285 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001286 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001287 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001288 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001289
Adrian Roos93bafc42019-03-05 18:31:37 +01001290
1291@verifier
Adrian Roos56ff7842019-02-28 12:45:00 +01001292def verify_uris(clazz):
1293 bad = ["java.net.URL", "java.net.URI", "android.net.URL"]
1294
1295 for f in clazz.fields:
1296 if f.typ in bad:
1297 error(clazz, f, None, "Field must be android.net.Uri instead of " + f.typ)
1298
1299 for m in clazz.methods + clazz.ctors:
1300 if m.typ in bad:
1301 error(clazz, m, None, "Must return android.net.Uri instead of " + m.typ)
1302 for arg in m.args:
1303 if arg in bad:
1304 error(clazz, m, None, "Argument must take android.net.Uri instead of " + arg)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001305
Adrian Roos93bafc42019-03-05 18:31:37 +01001306
1307@verifier
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001308def verify_flags(clazz):
1309 """Verifies that flags are non-overlapping."""
1310 known = collections.defaultdict(int)
1311 for f in clazz.fields:
1312 if "FLAG_" in f.name:
1313 try:
1314 val = int(f.value)
1315 except:
1316 continue
1317
1318 scope = f.name[0:f.name.index("FLAG_")]
1319 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001320 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001321 known[scope] |= val
1322
1323
Adrian Roos93bafc42019-03-05 18:31:37 +01001324@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001325def verify_exception(clazz):
1326 """Verifies that methods don't throw generic exceptions."""
1327 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001328 for t in m.throws:
1329 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
1330 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001331
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001332 if t in ["android.os.RemoteException"]:
Adrian Roos02e18dd2019-02-28 12:41:48 +01001333 if clazz.fullname == "android.content.ContentProviderClient": continue
1334 if clazz.fullname == "android.os.Binder": continue
1335 if clazz.fullname == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -07001336
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001337 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
1338
1339 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
1340 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -07001341
Adrian Roos93bafc42019-03-05 18:31:37 +01001342
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001343GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001344
Adrian Roos93bafc42019-03-05 18:31:37 +01001345# Not marked as @verifier, because it is only conditionally applied.
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001346def verify_google(clazz):
1347 """Verifies that APIs never reference Google."""
1348
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001349 if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001350 error(clazz, None, None, "Must never reference Google")
1351
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001352 for test in clazz.ctors, clazz.fields, clazz.methods:
1353 for t in test:
1354 if GOOGLE_IGNORECASE.search(t.raw) is not None:
1355 error(clazz, t, None, "Must never reference Google")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001356
1357
Adrian Roos93bafc42019-03-05 18:31:37 +01001358@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001359def verify_bitset(clazz):
1360 """Verifies that we avoid using heavy BitSet."""
1361
1362 for f in clazz.fields:
1363 if f.typ == "java.util.BitSet":
1364 error(clazz, f, None, "Field type must not be heavy BitSet")
1365
1366 for m in clazz.methods:
1367 if m.typ == "java.util.BitSet":
1368 error(clazz, m, None, "Return type must not be heavy BitSet")
1369 for arg in m.args:
1370 if arg == "java.util.BitSet":
1371 error(clazz, m, None, "Argument type must not be heavy BitSet")
1372
1373
Adrian Roos93bafc42019-03-05 18:31:37 +01001374@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001375def verify_manager(clazz):
1376 """Verifies that FooManager is only obtained from Context."""
1377
1378 if not clazz.name.endswith("Manager"): return
1379
1380 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001381 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001382
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001383 for m in clazz.methods:
1384 if m.typ == clazz.fullname:
1385 error(clazz, m, None, "Managers must always be obtained from Context")
1386
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001387
Adrian Roos93bafc42019-03-05 18:31:37 +01001388@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001389def verify_boxed(clazz):
1390 """Verifies that methods avoid boxed primitives."""
1391
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001392 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 -08001393
1394 for c in clazz.ctors:
1395 for arg in c.args:
1396 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001397 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001398
1399 for f in clazz.fields:
1400 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001401 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001402
1403 for m in clazz.methods:
1404 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001405 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001406 for arg in m.args:
1407 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001408 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001409
1410
Adrian Roos93bafc42019-03-05 18:31:37 +01001411@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001412def verify_static_utils(clazz):
1413 """Verifies that helper classes can't be constructed."""
1414 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001415 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001416
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001417 # Only care about classes with default constructors
1418 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
1419 test = []
1420 test.extend(clazz.fields)
1421 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001422
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001423 if len(test) == 0: return
1424 for t in test:
1425 if "static" not in t.split:
1426 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001427
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001428 error(clazz, None, None, "Fully-static utility classes must not have constructor")
1429
1430
Adrian Roos93bafc42019-03-05 18:31:37 +01001431# @verifier # Disabled for now
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001432def verify_overload_args(clazz):
1433 """Verifies that method overloads add new arguments at the end."""
1434 if clazz.fullname.startswith("android.opengl"): return
1435
1436 overloads = collections.defaultdict(list)
1437 for m in clazz.methods:
1438 if "deprecated" in m.split: continue
1439 overloads[m.name].append(m)
1440
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001441 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001442 if len(methods) <= 1: continue
1443
1444 # Look for arguments common across all overloads
1445 def cluster(args):
1446 count = collections.defaultdict(int)
1447 res = set()
1448 for i in range(len(args)):
1449 a = args[i]
1450 res.add("%s#%d" % (a, count[a]))
1451 count[a] += 1
1452 return res
1453
1454 common_args = cluster(methods[0].args)
1455 for m in methods:
1456 common_args = common_args & cluster(m.args)
1457
1458 if len(common_args) == 0: continue
1459
1460 # Require that all common arguments are present at start of signature
1461 locked_sig = None
1462 for m in methods:
1463 sig = m.args[0:len(common_args)]
1464 if not common_args.issubset(cluster(sig)):
1465 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1466 elif not locked_sig:
1467 locked_sig = sig
1468 elif locked_sig != sig:
1469 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1470
1471
Adrian Roos93bafc42019-03-05 18:31:37 +01001472@verifier
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001473def verify_callback_handlers(clazz):
1474 """Verifies that methods adding listener/callback have overload
1475 for specifying delivery thread."""
1476
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001477 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001478 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001479 "animation",
1480 "view",
1481 "graphics",
1482 "transition",
1483 "widget",
1484 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001485 ]
1486 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001487 if s in clazz.pkg.name_path: return
1488 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001489
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001490 # Ignore UI classes which assume main thread
1491 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1492 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1493 if s in clazz.fullname: return
1494 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1495 for s in ["Loader"]:
1496 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001497
1498 found = {}
1499 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001500 examine = clazz.ctors + clazz.methods
1501 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001502 if m.name.startswith("unregister"): continue
1503 if m.name.startswith("remove"): continue
1504 if re.match("on[A-Z]+", m.name): continue
1505
1506 by_name[m.name].append(m)
1507
1508 for a in m.args:
1509 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1510 found[m.name] = m
1511
1512 for f in found.values():
1513 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001514 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001515 for m in by_name[f.name]:
1516 if "android.os.Handler" in m.args:
1517 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001518 if "java.util.concurrent.Executor" in m.args:
1519 takes_exec = True
1520 if not takes_exec:
1521 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001522
1523
Adrian Roos93bafc42019-03-05 18:31:37 +01001524@verifier
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001525def verify_context_first(clazz):
1526 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001527 examine = clazz.ctors + clazz.methods
1528 for m in examine:
1529 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001530 if "android.content.Context" in m.args[1:]:
1531 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001532 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1533 if "android.content.ContentResolver" in m.args[1:]:
1534 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001535
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001536
Adrian Roos93bafc42019-03-05 18:31:37 +01001537@verifier
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001538def verify_listener_last(clazz):
1539 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1540 examine = clazz.ctors + clazz.methods
1541 for m in examine:
1542 if "Listener" in m.name or "Callback" in m.name: continue
1543 found = False
1544 for a in m.args:
1545 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1546 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001547 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001548 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1549
1550
Adrian Roos93bafc42019-03-05 18:31:37 +01001551@verifier
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001552def verify_resource_names(clazz):
1553 """Verifies that resource names have consistent case."""
1554 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1555
1556 # Resources defined by files are foo_bar_baz
1557 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1558 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001559 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1560 if f.name.startswith("config_"):
1561 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1562
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001563 if re.match("[a-z1-9_]+$", f.name): continue
1564 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1565
1566 # Resources defined inside files are fooBarBaz
1567 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1568 for f in clazz.fields:
1569 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1570 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1571 if re.match("state_[a-z_]*$", f.name): continue
1572
1573 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1574 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1575
1576 # Styles are FooBar_Baz
1577 if clazz.name in ["style"]:
1578 for f in clazz.fields:
1579 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1580 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001581
1582
Adrian Roos93bafc42019-03-05 18:31:37 +01001583@verifier
Jeff Sharkey331279b2016-02-29 16:02:02 -07001584def verify_files(clazz):
1585 """Verifies that methods accepting File also accept streams."""
1586
1587 has_file = set()
1588 has_stream = set()
1589
1590 test = []
1591 test.extend(clazz.ctors)
1592 test.extend(clazz.methods)
1593
1594 for m in test:
1595 if "java.io.File" in m.args:
1596 has_file.add(m)
1597 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:
1598 has_stream.add(m.name)
1599
1600 for m in has_file:
1601 if m.name not in has_stream:
1602 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1603
1604
Adrian Roos93bafc42019-03-05 18:31:37 +01001605@verifier
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001606def verify_manager_list(clazz):
1607 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1608
1609 if not clazz.name.endswith("Manager"): return
1610
1611 for m in clazz.methods:
1612 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1613 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1614
1615
Adrian Roos93bafc42019-03-05 18:31:37 +01001616@verifier
Jeff Sharkey26c80902016-12-21 13:41:17 -07001617def verify_abstract_inner(clazz):
1618 """Verifies that abstract inner classes are static."""
1619
1620 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
Adrian Roosb787c182019-01-03 18:54:33 +01001621 if "abstract" in clazz.split and "static" not in clazz.split:
Jeff Sharkey26c80902016-12-21 13:41:17 -07001622 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1623
1624
Adrian Roos93bafc42019-03-05 18:31:37 +01001625@verifier
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001626def verify_runtime_exceptions(clazz):
1627 """Verifies that runtime exceptions aren't listed in throws."""
1628
1629 banned = [
1630 "java.lang.NullPointerException",
1631 "java.lang.ClassCastException",
1632 "java.lang.IndexOutOfBoundsException",
1633 "java.lang.reflect.UndeclaredThrowableException",
1634 "java.lang.reflect.MalformedParametersException",
1635 "java.lang.reflect.MalformedParameterizedTypeException",
1636 "java.lang.invoke.WrongMethodTypeException",
1637 "java.lang.EnumConstantNotPresentException",
1638 "java.lang.IllegalMonitorStateException",
1639 "java.lang.SecurityException",
1640 "java.lang.UnsupportedOperationException",
1641 "java.lang.annotation.AnnotationTypeMismatchException",
1642 "java.lang.annotation.IncompleteAnnotationException",
1643 "java.lang.TypeNotPresentException",
1644 "java.lang.IllegalStateException",
1645 "java.lang.ArithmeticException",
1646 "java.lang.IllegalArgumentException",
1647 "java.lang.ArrayStoreException",
1648 "java.lang.NegativeArraySizeException",
1649 "java.util.MissingResourceException",
1650 "java.util.EmptyStackException",
1651 "java.util.concurrent.CompletionException",
1652 "java.util.concurrent.RejectedExecutionException",
1653 "java.util.IllformedLocaleException",
1654 "java.util.ConcurrentModificationException",
1655 "java.util.NoSuchElementException",
1656 "java.io.UncheckedIOException",
1657 "java.time.DateTimeException",
1658 "java.security.ProviderException",
1659 "java.nio.BufferUnderflowException",
1660 "java.nio.BufferOverflowException",
1661 ]
1662
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001663 examine = clazz.ctors + clazz.methods
1664 for m in examine:
1665 for t in m.throws:
1666 if t in banned:
1667 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001668
1669
Adrian Roos93bafc42019-03-05 18:31:37 +01001670@verifier
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001671def verify_error(clazz):
1672 """Verifies that we always use Exception instead of Error."""
1673 if not clazz.extends: return
1674 if clazz.extends.endswith("Error"):
1675 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1676 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1677 error(clazz, None, None, "Exceptions must be named FooException")
1678
1679
Adrian Roos93bafc42019-03-05 18:31:37 +01001680@verifier
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001681def verify_units(clazz):
1682 """Verifies that we use consistent naming for units."""
1683
1684 # If we find K, recommend replacing with V
1685 bad = {
1686 "Ns": "Nanos",
1687 "Ms": "Millis or Micros",
1688 "Sec": "Seconds", "Secs": "Seconds",
1689 "Hr": "Hours", "Hrs": "Hours",
1690 "Mo": "Months", "Mos": "Months",
1691 "Yr": "Years", "Yrs": "Years",
1692 "Byte": "Bytes", "Space": "Bytes",
1693 }
1694
1695 for m in clazz.methods:
1696 if m.typ not in ["short","int","long"]: continue
1697 for k, v in bad.iteritems():
1698 if m.name.endswith(k):
1699 error(clazz, m, None, "Expected method name units to be " + v)
1700 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1701 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1702 if m.name.endswith("Seconds"):
1703 error(clazz, m, None, "Returned time values must be in milliseconds")
1704
1705 for m in clazz.methods:
1706 typ = m.typ
1707 if typ == "void":
1708 if len(m.args) != 1: continue
1709 typ = m.args[0]
1710
1711 if m.name.endswith("Fraction") and typ != "float":
1712 error(clazz, m, None, "Fractions must use floats")
1713 if m.name.endswith("Percentage") and typ != "int":
1714 error(clazz, m, None, "Percentage must use ints")
1715
1716
Adrian Roos93bafc42019-03-05 18:31:37 +01001717@verifier
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001718def verify_closable(clazz):
1719 """Verifies that classes are AutoClosable."""
Adrian Roos02e18dd2019-02-28 12:41:48 +01001720 if "java.lang.AutoCloseable" in clazz.implements_all: return
1721 if "java.io.Closeable" in clazz.implements_all: return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001722
1723 for m in clazz.methods:
1724 if len(m.args) > 0: continue
1725 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1726 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1727 return
1728
1729
Adrian Roos93bafc42019-03-05 18:31:37 +01001730@verifier
Jake Wharton9e6738f2017-08-23 11:59:55 -04001731def verify_member_name_not_kotlin_keyword(clazz):
1732 """Prevent method names which are keywords in Kotlin."""
1733
1734 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1735 # This list does not include Java keywords as those are already impossible to use.
1736 keywords = [
1737 'as',
1738 'fun',
1739 'in',
1740 'is',
1741 'object',
1742 'typealias',
1743 'val',
1744 'var',
1745 'when',
1746 ]
1747
1748 for m in clazz.methods:
1749 if m.name in keywords:
1750 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1751 for f in clazz.fields:
1752 if f.name in keywords:
1753 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1754
1755
Adrian Roos93bafc42019-03-05 18:31:37 +01001756@verifier
Jake Wharton9e6738f2017-08-23 11:59:55 -04001757def verify_method_name_not_kotlin_operator(clazz):
1758 """Warn about method names which become operators in Kotlin."""
1759
1760 binary = set()
1761
1762 def unique_binary_op(m, op):
1763 if op in binary:
1764 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1765 binary.add(op)
1766
1767 for m in clazz.methods:
Adrian Roosd1e38922019-01-14 15:44:15 +01001768 if 'static' in m.split or 'operator' in m.split:
Jake Wharton9e6738f2017-08-23 11:59:55 -04001769 continue
1770
1771 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1772 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1773 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1774
1775 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1776 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1777 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1778 # practical way of checking that relationship here.
1779 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1780
1781 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1782 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1783 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1784 unique_binary_op(m, m.name)
1785
1786 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1787 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1788 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1789
1790 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1791 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1792 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1793
1794 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1795 if m.name == 'invoke':
1796 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1797
1798 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1799 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1800 and len(m.args) == 1 \
1801 and m.typ == 'void':
1802 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1803 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1804
1805
Adrian Roos93bafc42019-03-05 18:31:37 +01001806@verifier
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001807def verify_collections_over_arrays(clazz):
1808 """Warn that [] should be Collections."""
1809
Adrian Roosb787c182019-01-03 18:54:33 +01001810 if "@interface" in clazz.split:
1811 return
1812
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001813 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1814 for m in clazz.methods:
1815 if m.typ.endswith("[]") and m.typ not in safe:
1816 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1817 for arg in m.args:
1818 if arg.endswith("[]") and arg not in safe:
1819 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1820
1821
Adrian Roos93bafc42019-03-05 18:31:37 +01001822@verifier
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001823def verify_user_handle(clazz):
1824 """Methods taking UserHandle should be ForUser or AsUser."""
1825 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1826 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1827 if clazz.fullname == "android.content.pm.LauncherApps": return
1828 if clazz.fullname == "android.os.UserHandle": return
1829 if clazz.fullname == "android.os.UserManager": return
1830
1831 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001832 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001833
1834 has_arg = "android.os.UserHandle" in m.args
1835 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1836
1837 if clazz.fullname.endswith("Manager") and has_arg:
1838 warn(clazz, m, None, "When a method overload is needed to target a specific "
1839 "UserHandle, callers should be directed to use "
1840 "Context.createPackageContextAsUser() and re-obtain the relevant "
1841 "Manager, and no new API should be added")
1842 elif has_arg and not has_name:
1843 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1844 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001845
1846
Adrian Roos93bafc42019-03-05 18:31:37 +01001847@verifier
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001848def verify_params(clazz):
1849 """Parameter classes should be 'Params'."""
1850 if clazz.name.endswith("Params"): return
1851 if clazz.fullname == "android.app.ActivityOptions": return
1852 if clazz.fullname == "android.app.BroadcastOptions": return
1853 if clazz.fullname == "android.os.Bundle": return
1854 if clazz.fullname == "android.os.BaseBundle": return
1855 if clazz.fullname == "android.os.PersistableBundle": return
1856
1857 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1858 for b in bad:
1859 if clazz.name.endswith(b):
1860 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1861
1862
Adrian Roos93bafc42019-03-05 18:31:37 +01001863@verifier
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001864def verify_services(clazz):
1865 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1866 if clazz.fullname != "android.content.Context": return
1867
1868 for f in clazz.fields:
1869 if f.typ != "java.lang.String": continue
1870 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1871 if found:
1872 expected = found.group(1).lower()
1873 if f.value != expected:
1874 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1875
1876
Adrian Roos93bafc42019-03-05 18:31:37 +01001877@verifier
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001878def verify_tense(clazz):
1879 """Verify tenses of method names."""
1880 if clazz.fullname.startswith("android.opengl"): return
1881
1882 for m in clazz.methods:
1883 if m.name.endswith("Enable"):
1884 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1885
1886
Adrian Roos93bafc42019-03-05 18:31:37 +01001887@verifier
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001888def verify_icu(clazz):
1889 """Verifies that richer ICU replacements are used."""
1890 better = {
1891 "java.util.TimeZone": "android.icu.util.TimeZone",
1892 "java.util.Calendar": "android.icu.util.Calendar",
1893 "java.util.Locale": "android.icu.util.ULocale",
1894 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1895 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1896 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1897 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1898 "java.lang.Character": "android.icu.lang.UCharacter",
1899 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1900 "java.text.Collator": "android.icu.text.Collator",
1901 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1902 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1903 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1904 "java.text.DateFormat": "android.icu.text.DateFormat",
1905 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1906 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1907 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1908 }
1909
1910 for m in clazz.ctors + clazz.methods:
1911 types = []
1912 types.extend(m.typ)
1913 types.extend(m.args)
1914 for arg in types:
1915 if arg in better:
1916 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1917
1918
Adrian Roos93bafc42019-03-05 18:31:37 +01001919@verifier
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001920def verify_clone(clazz):
1921 """Verify that clone() isn't implemented; see EJ page 61."""
1922 for m in clazz.methods:
1923 if m.name == "clone":
1924 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1925
1926
Adrian Roos93bafc42019-03-05 18:31:37 +01001927@verifier
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001928def verify_pfd(clazz):
1929 """Verify that android APIs use PFD over FD."""
Adrian Roos02e18dd2019-02-28 12:41:48 +01001930 if clazz.fullname == "android.os.FileUtils" or clazz.fullname == "android.system.Os":
1931 return
1932
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001933 examine = clazz.ctors + clazz.methods
1934 for m in examine:
1935 if m.typ == "java.io.FileDescriptor":
1936 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1937 if m.typ == "int":
1938 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1939 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1940 for arg in m.args:
1941 if arg == "java.io.FileDescriptor":
1942 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1943
1944 for f in clazz.fields:
1945 if f.typ == "java.io.FileDescriptor":
1946 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1947
1948
Adrian Roos93bafc42019-03-05 18:31:37 +01001949@verifier
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001950def verify_numbers(clazz):
1951 """Discourage small numbers types like short and byte."""
1952
1953 discouraged = ["short","byte"]
1954
1955 for c in clazz.ctors:
1956 for arg in c.args:
1957 if arg in discouraged:
1958 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1959
1960 for f in clazz.fields:
1961 if f.typ in discouraged:
1962 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1963
1964 for m in clazz.methods:
1965 if m.typ in discouraged:
1966 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1967 for arg in m.args:
1968 if arg in discouraged:
1969 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1970
Adrian Roos93bafc42019-03-05 18:31:37 +01001971
Adrian Roos80545ef2019-02-27 16:45:00 +01001972PRIMITIVES = {"void", "int", "float", "boolean", "short", "char", "byte", "long", "double"}
1973
Adrian Roos93bafc42019-03-05 18:31:37 +01001974@verifier
Adrian Roos80545ef2019-02-27 16:45:00 +01001975def verify_nullability(clazz):
1976 """Catches missing nullability annotations"""
1977
1978 for f in clazz.fields:
1979 if f.value is not None and 'static' in f.split and 'final' in f.split:
1980 continue # Nullability of constants can be inferred.
1981 if f.typ not in PRIMITIVES and not has_nullability(f.annotations):
1982 error(clazz, f, "M12", "Field must be marked either @NonNull or @Nullable")
1983
1984 for c in clazz.ctors:
1985 verify_nullability_args(clazz, c)
1986
1987 for m in clazz.methods:
1988 if m.name == "writeToParcel" or m.name == "onReceive":
1989 continue # Parcelable.writeToParcel() and BroadcastReceiver.onReceive() are not yet annotated
1990
1991 if m.typ not in PRIMITIVES and not has_nullability(m.annotations):
1992 error(clazz, m, "M12", "Return value must be marked either @NonNull or @Nullable")
1993 verify_nullability_args(clazz, m)
1994
1995def verify_nullability_args(clazz, m):
1996 for i, arg in enumerate(m.detailed_args):
1997 if arg.type not in PRIMITIVES and not has_nullability(arg.annotations):
1998 error(clazz, m, "M12", "Argument %d must be marked either @NonNull or @Nullable" % (i+1,))
1999
2000def has_nullability(annotations):
2001 return "@NonNull" in annotations or "@Nullable" in annotations
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06002002
Adrian Roos93bafc42019-03-05 18:31:37 +01002003
2004@verifier
Adrian Roos34782392019-03-06 13:38:10 +01002005def verify_intdef(clazz):
2006 """intdefs must be @hide, because the constant names cannot be stored in
2007 the stubs (only the values are, which is not useful)"""
2008 if "@interface" not in clazz.split:
2009 return
2010 if "@IntDef" in clazz.annotations or "@LongDef" in clazz.annotations:
2011 error(clazz, None, None, "@IntDef and @LongDef annotations must be @hide")
2012
2013
2014@verifier
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06002015def verify_singleton(clazz):
2016 """Catch singleton objects with constructors."""
2017
2018 singleton = False
2019 for m in clazz.methods:
2020 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
2021 singleton = True
2022
2023 if singleton:
2024 for c in clazz.ctors:
2025 error(clazz, c, None, "Singleton classes should use getInstance() methods")
2026
2027
2028
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002029def is_interesting(clazz):
2030 """Test if given class is interesting from an Android PoV."""
2031
2032 if clazz.pkg.name.startswith("java"): return False
2033 if clazz.pkg.name.startswith("junit"): return False
2034 if clazz.pkg.name.startswith("org.apache"): return False
2035 if clazz.pkg.name.startswith("org.xml"): return False
2036 if clazz.pkg.name.startswith("org.json"): return False
2037 if clazz.pkg.name.startswith("org.w3c"): return False
2038 if clazz.pkg.name.startswith("android.icu."): return False
2039 return True
2040
2041
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002042def examine_clazz(clazz):
2043 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002044
2045 notice(clazz)
2046
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002047 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002048
Adrian Roos93bafc42019-03-05 18:31:37 +01002049 for v in verifiers.itervalues():
2050 v(clazz)
2051
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002052 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002053
2054
Adrian Roos038a0292018-12-19 17:11:21 +01002055def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002056 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002057 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002058 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002059 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01002060 _parse_stream(stream, examine_clazz, base_f=base_stream,
2061 in_classes_with_base=in_classes_with_base,
2062 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002063 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002064
2065
2066def examine_api(api):
2067 """Find all style issues in the given parsed API."""
2068 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07002069 failures = {}
2070 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002071 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002072 return failures
2073
2074
Jeff Sharkey037458a2014-09-04 15:46:20 -07002075def verify_compat(cur, prev):
2076 """Find any incompatible API changes between two levels."""
2077 global failures
2078
2079 def class_exists(api, test):
2080 return test.fullname in api
2081
2082 def ctor_exists(api, clazz, test):
2083 for m in clazz.ctors:
2084 if m.ident == test.ident: return True
2085 return False
2086
2087 def all_methods(api, clazz):
2088 methods = list(clazz.methods)
2089 if clazz.extends is not None:
2090 methods.extend(all_methods(api, api[clazz.extends]))
2091 return methods
2092
2093 def method_exists(api, clazz, test):
2094 methods = all_methods(api, clazz)
2095 for m in methods:
2096 if m.ident == test.ident: return True
2097 return False
2098
2099 def field_exists(api, clazz, test):
2100 for f in clazz.fields:
2101 if f.ident == test.ident: return True
2102 return False
2103
2104 failures = {}
2105 for key in sorted(prev.keys()):
2106 prev_clazz = prev[key]
2107
2108 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002109 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002110 continue
2111
2112 cur_clazz = cur[key]
2113
2114 for test in prev_clazz.ctors:
2115 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002116 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002117
2118 methods = all_methods(prev, prev_clazz)
2119 for test in methods:
2120 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002121 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002122
2123 for test in prev_clazz.fields:
2124 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002125 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002126
2127 return failures
2128
2129
Joe Onorato680d4fc2019-05-07 16:22:16 -07002130def match_filter(filters, fullname):
2131 for f in filters:
2132 if fullname == f:
2133 return True
2134 if fullname.startswith(f + '.'):
2135 return True
2136 return False
2137
2138
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002139def show_deprecations_at_birth(cur, prev):
2140 """Show API deprecations at birth."""
2141 global failures
2142
2143 # Remove all existing things so we're left with new
2144 for prev_clazz in prev.values():
Michael Wright42dcfb82019-04-29 22:37:00 +01002145 if prev_clazz.fullname not in cur:
2146 # The class was removed this release; we can safely ignore it.
2147 continue
2148
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002149 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002150 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002151
2152 sigs = { i.ident: i for i in prev_clazz.ctors }
2153 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
2154 sigs = { i.ident: i for i in prev_clazz.methods }
2155 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
2156 sigs = { i.ident: i for i in prev_clazz.fields }
2157 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
2158
2159 # Forget about class entirely when nothing new
2160 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
2161 del cur[prev_clazz.fullname]
2162
2163 for clazz in cur.values():
Jeff Sharkeyb4e3d692019-03-26 09:59:32 -06002164 if not is_interesting(clazz): continue
2165
Adrian Roosb787c182019-01-03 18:54:33 +01002166 if "deprecated" in clazz.split and not clazz.fullname in prev:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002167 error(clazz, None, None, "Found API deprecation at birth")
2168
2169 for i in clazz.ctors + clazz.methods + clazz.fields:
Adrian Roosb787c182019-01-03 18:54:33 +01002170 if "deprecated" in i.split:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002171 error(clazz, i, None, "Found API deprecation at birth")
2172
2173 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
2174 format(reset=True)))
2175 for f in sorted(failures):
2176 print failures[f]
2177 print
2178
2179
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002180def show_stats(cur, prev):
2181 """Show API stats."""
2182
2183 stats = collections.defaultdict(int)
2184 for cur_clazz in cur.values():
2185 if not is_interesting(cur_clazz): continue
2186
2187 if cur_clazz.fullname not in prev:
2188 stats['new_classes'] += 1
2189 stats['new_ctors'] += len(cur_clazz.ctors)
2190 stats['new_methods'] += len(cur_clazz.methods)
2191 stats['new_fields'] += len(cur_clazz.fields)
2192 else:
2193 prev_clazz = prev[cur_clazz.fullname]
2194
2195 sigs = { i.ident: i for i in prev_clazz.ctors }
2196 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
2197 sigs = { i.ident: i for i in prev_clazz.methods }
2198 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
2199 sigs = { i.ident: i for i in prev_clazz.fields }
2200 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
2201
2202 if ctors + methods + fields > 0:
2203 stats['extend_classes'] += 1
2204 stats['extend_ctors'] += ctors
2205 stats['extend_methods'] += methods
2206 stats['extend_fields'] += fields
2207
2208 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
2209 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
2210
2211
Joe Onorato680d4fc2019-05-07 16:22:16 -07002212def main():
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002213 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
2214 patterns. It ignores lint messages from a previous API level, if provided.")
Adrian Roos6eb57b02018-12-13 22:08:29 +01002215 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
2216 help="The base current.txt to use when examining system-current.txt or"
2217 " test-current.txt")
2218 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
2219 help="The base previous.txt to use when examining system-previous.txt or"
2220 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002221 parser.add_argument("--no-color", action='store_const', const=True,
2222 help="Disable terminal colors")
Joe Onorato680d4fc2019-05-07 16:22:16 -07002223 parser.add_argument("--color", action='store_const', const=True,
2224 help="Use terminal colors")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002225 parser.add_argument("--allow-google", action='store_const', const=True,
2226 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002227 parser.add_argument("--show-noticed", action='store_const', const=True,
2228 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002229 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
2230 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002231 parser.add_argument("--show-stats", action='store_const', const=True,
2232 help="Show API stats")
Joe Onorato680d4fc2019-05-07 16:22:16 -07002233 parser.add_argument("--title", action='store', default=None,
2234 help="Title to put in for display purposes")
2235 parser.add_argument("--filter", action="append",
2236 help="If provided, only show lint for the given packages or classes.")
2237 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
2238 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
2239 help="previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002240 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002241
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002242 if args['no_color']:
2243 USE_COLOR = False
Joe Onorato680d4fc2019-05-07 16:22:16 -07002244 elif args['color']:
2245 USE_COLOR = True
2246 else:
2247 USE_COLOR = sys.stdout.isatty()
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002248
2249 if args['allow_google']:
2250 ALLOW_GOOGLE = True
2251
2252 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002253 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002254 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002255 base_previous_file = args['base_previous']
Joe Onorato680d4fc2019-05-07 16:22:16 -07002256 filters = args['filter']
2257 if not filters:
2258 filters = []
2259 title = args['title']
2260 if not title:
2261 title = current_file.name
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002262
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002263 if args['show_deprecations_at_birth']:
2264 with current_file as f:
2265 cur = _parse_stream(f)
2266 with previous_file as f:
2267 prev = _parse_stream(f)
2268 show_deprecations_at_birth(cur, prev)
2269 sys.exit()
2270
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002271 if args['show_stats']:
2272 with current_file as f:
2273 cur = _parse_stream(f)
2274 with previous_file as f:
2275 prev = _parse_stream(f)
2276 show_stats(cur, prev)
2277 sys.exit()
2278
Adrian Roos038a0292018-12-19 17:11:21 +01002279 classes_with_base = []
2280
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002281 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002282 if base_current_file:
2283 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002284 cur_fail, cur_noticed = examine_stream(f, base_f,
2285 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002286 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002287 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
2288
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002289 if not previous_file is None:
2290 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002291 if base_previous_file:
2292 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002293 prev_fail, prev_noticed = examine_stream(f, base_f,
2294 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002295 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002296 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002297
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002298 # ignore errors from previous API level
2299 for p in prev_fail:
2300 if p in cur_fail:
2301 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002302
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002303 # ignore classes unchanged from previous API level
2304 for k, v in prev_noticed.iteritems():
2305 if k in cur_noticed and v == cur_noticed[k]:
2306 del cur_noticed[k]
2307
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002308 """
2309 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002310 # look for compatibility issues
2311 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002312
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002313 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2314 for f in sorted(compat_fail):
2315 print compat_fail[f]
2316 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002317 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002318
Joe Onorato680d4fc2019-05-07 16:22:16 -07002319 # ignore everything but the given filters, if provided
2320 if filters:
2321 cur_fail = dict([(key, failure) for key, failure in cur_fail.iteritems()
2322 if match_filter(filters, failure.clazz.fullname)])
2323
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002324 if args['show_noticed'] and len(cur_noticed) != 0:
2325 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2326 for f in sorted(cur_noticed.keys()):
2327 print f
2328 print
2329
Jason Monk53b2a732017-11-10 15:43:17 -05002330 if len(cur_fail) != 0:
Joe Onorato680d4fc2019-05-07 16:22:16 -07002331 print "%s API style issues: %s %s" % ((format(fg=WHITE, bg=BLUE, bold=True),
2332 title, format(reset=True)))
2333 for f in filters:
2334 print "%s filter: %s %s" % ((format(fg=WHITE, bg=BLUE, bold=True),
2335 f, format(reset=True)))
2336 print
Jason Monk53b2a732017-11-10 15:43:17 -05002337 for f in sorted(cur_fail):
2338 print cur_fail[f]
2339 print
Joe Onorato680d4fc2019-05-07 16:22:16 -07002340 print "%d errors" % len(cur_fail)
Jason Monk53b2a732017-11-10 15:43:17 -05002341 sys.exit(77)
Joe Onorato680d4fc2019-05-07 16:22:16 -07002342
2343if __name__ == "__main__":
2344 try:
2345 main()
2346 except KeyboardInterrupt:
2347 sys.exit(1)