blob: a91fbe76be8dd37f4caf6e32d829e55e455ed10e [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 = []
173
Adrian Roosb787c182019-01-03 18:54:33 +0100174 if sig_format == 2:
175 V2LineParser(raw).parse_into_class(self)
176 elif sig_format == 1:
177 # drop generics for now; may need multiple passes
178 raw = re.sub("<[^<]+?>", "", raw)
179 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600180
Adrian Roosb787c182019-01-03 18:54:33 +0100181 raw = raw.split()
182 self.split = list(raw)
183 if "class" in raw:
184 self.fullname = raw[raw.index("class")+1]
185 elif "interface" in raw:
186 self.fullname = raw[raw.index("interface")+1]
187 elif "@interface" in raw:
188 self.fullname = raw[raw.index("@interface")+1]
189 else:
190 raise ValueError("Funky class type %s" % (self.raw))
Jeff Sharkey037458a2014-09-04 15:46:20 -0700191
Adrian Roosb787c182019-01-03 18:54:33 +0100192 if "extends" in raw:
193 self.extends = raw[raw.index("extends")+1]
194 else:
195 self.extends = None
196
197 if "implements" in raw:
198 self.implements = raw[raw.index("implements")+1]
Adrian Roos02e18dd2019-02-28 12:41:48 +0100199 self.implements_all = [self.implements]
Adrian Roosb787c182019-01-03 18:54:33 +0100200 else:
201 self.implements = None
Adrian Roos02e18dd2019-02-28 12:41:48 +0100202 self.implements_all = []
Jeff Sharkey037458a2014-09-04 15:46:20 -0700203 else:
Adrian Roosb787c182019-01-03 18:54:33 +0100204 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700205
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700206 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800207 self.fullname_path = self.fullname.split(".")
208
Adrian Roosb787c182019-01-03 18:54:33 +0100209 if self.extends is not None:
210 self.extends_path = self.extends.split(".")
211 else:
212 self.extends_path = []
213
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700214 self.name = self.fullname[self.fullname.rindex(".")+1:]
215
Adrian Roos6eb57b02018-12-13 22:08:29 +0100216 def merge_from(self, other):
217 self.ctors.extend(other.ctors)
218 self.fields.extend(other.fields)
219 self.methods.extend(other.methods)
220
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700221 def __hash__(self):
222 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
223
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700224 def __repr__(self):
225 return self.raw
226
227
228class Package():
Adrian Roosd9871b12019-02-28 12:42:22 +0100229 NAME = re.compile("package(?: .*)? ([A-Za-z0-9.]+)")
Adrian Roosb1faa0b2019-02-26 11:54:40 +0100230
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700231 def __init__(self, line, raw, blame):
232 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700233 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700234 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700235
Adrian Roosb1faa0b2019-02-26 11:54:40 +0100236 self.name = Package.NAME.match(raw).group(1)
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800237 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700238
239 def __repr__(self):
240 return self.raw
241
Adrian Roose5eeae72019-01-04 20:10:06 +0100242class V2Tokenizer(object):
243 __slots__ = ["raw"]
244
Adrian Rooscf82e042019-01-29 15:01:28 +0100245 SIGNATURE_PREFIX = "// Signature format: "
Adrian Roos5cdfb692019-01-05 22:04:55 +0100246 DELIMITER = re.compile(r'\s+|[()@<>;,={}/"!?]|\[\]|\.\.\.')
Adrian Roosb787c182019-01-03 18:54:33 +0100247 STRING_SPECIAL = re.compile(r'["\\]')
248
249 def __init__(self, raw):
250 self.raw = raw
Adrian Roosb787c182019-01-03 18:54:33 +0100251
Adrian Roose5eeae72019-01-04 20:10:06 +0100252 def tokenize(self):
253 tokens = []
254 current = 0
255 raw = self.raw
256 length = len(raw)
Adrian Roosb787c182019-01-03 18:54:33 +0100257
Adrian Roose5eeae72019-01-04 20:10:06 +0100258 while current < length:
259 while current < length:
260 start = current
261 match = V2Tokenizer.DELIMITER.search(raw, start)
262 if match is not None:
263 match_start = match.start()
264 if match_start == current:
265 end = match.end()
266 else:
267 end = match_start
268 else:
269 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100270
Adrian Roose5eeae72019-01-04 20:10:06 +0100271 token = raw[start:end]
272 current = end
Adrian Roosb787c182019-01-03 18:54:33 +0100273
Adrian Roose5eeae72019-01-04 20:10:06 +0100274 if token == "" or token[0] == " ":
275 continue
276 else:
277 break
Adrian Roosb787c182019-01-03 18:54:33 +0100278
Adrian Roose5eeae72019-01-04 20:10:06 +0100279 if token == "@":
280 if raw[start:start+11] == "@interface ":
281 current = start + 11
282 tokens.append("@interface")
283 continue
284 elif token == '/':
285 if raw[start:start+2] == "//":
286 current = length
287 continue
288 elif token == '"':
289 current, string_token = self.tokenize_string(raw, length, current)
290 tokens.append(token + string_token)
291 continue
Adrian Roosb787c182019-01-03 18:54:33 +0100292
Adrian Roose5eeae72019-01-04 20:10:06 +0100293 tokens.append(token)
Adrian Roosb787c182019-01-03 18:54:33 +0100294
Adrian Roose5eeae72019-01-04 20:10:06 +0100295 return tokens
Adrian Roosb787c182019-01-03 18:54:33 +0100296
Adrian Roose5eeae72019-01-04 20:10:06 +0100297 def tokenize_string(self, raw, length, current):
298 start = current
299 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100300 while start < end:
Adrian Roose5eeae72019-01-04 20:10:06 +0100301 match = V2Tokenizer.STRING_SPECIAL.search(raw, start)
Adrian Roosb787c182019-01-03 18:54:33 +0100302 if match:
303 if match.group() == '"':
304 end = match.end()
305 break
306 elif match.group() == '\\':
307 # ignore whatever is after the slash
308 start += 2
309 else:
310 raise ValueError("Unexpected match: `%s`" % (match.group()))
311 else:
Adrian Roose5eeae72019-01-04 20:10:06 +0100312 raise ValueError("Unexpected EOF tokenizing string: `%s`" % (raw[current - 1:],))
Adrian Roosb787c182019-01-03 18:54:33 +0100313
Adrian Roose5eeae72019-01-04 20:10:06 +0100314 token = raw[current:end]
315 return end, token
Adrian Roosb787c182019-01-03 18:54:33 +0100316
Adrian Roose5eeae72019-01-04 20:10:06 +0100317class V2LineParser(object):
318 __slots__ = ["tokenized", "current", "len"]
319
Adrian Roos258c5722019-01-21 15:43:15 +0100320 FIELD_KINDS = ("field", "property", "enum_constant")
Adrian Roosd1e38922019-01-14 15:44:15 +0100321 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 +0100322 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())
323
324 def __init__(self, raw):
Adrian Roose5eeae72019-01-04 20:10:06 +0100325 self.tokenized = V2Tokenizer(raw).tokenize()
Adrian Roosb787c182019-01-03 18:54:33 +0100326 self.current = 0
Adrian Roose5eeae72019-01-04 20:10:06 +0100327 self.len = len(self.tokenized)
Adrian Roosb787c182019-01-03 18:54:33 +0100328
329 def parse_into_method(self, method):
330 method.split = []
331 kind = self.parse_one_of("ctor", "method")
332 method.split.append(kind)
Adrian Roos80545ef2019-02-27 16:45:00 +0100333 method.annotations = self.parse_annotations()
Adrian Roosb787c182019-01-03 18:54:33 +0100334 method.split.extend(self.parse_modifiers())
335 self.parse_matching_paren("<", ">")
Adrian Roos80545ef2019-02-27 16:45:00 +0100336 if "@Deprecated" in method.annotations:
Adrian Roosb787c182019-01-03 18:54:33 +0100337 method.split.append("deprecated")
338 if kind == "ctor":
339 method.typ = "ctor"
340 else:
341 method.typ = self.parse_type()
342 method.split.append(method.typ)
343 method.name = self.parse_name()
344 method.split.append(method.name)
345 self.parse_token("(")
Adrian Roos80545ef2019-02-27 16:45:00 +0100346 method.detailed_args = self.parse_args()
Adrian Roosb787c182019-01-03 18:54:33 +0100347 self.parse_token(")")
348 method.throws = self.parse_throws()
349 if "@interface" in method.clazz.split:
350 self.parse_annotation_default()
351 self.parse_token(";")
352 self.parse_eof()
353
354 def parse_into_class(self, clazz):
355 clazz.split = []
356 annotations = self.parse_annotations()
357 if "@Deprecated" in annotations:
358 clazz.split.append("deprecated")
359 clazz.split.extend(self.parse_modifiers())
360 kind = self.parse_one_of("class", "interface", "@interface", "enum")
361 if kind == "enum":
362 # enums are implicitly final
363 clazz.split.append("final")
364 clazz.split.append(kind)
365 clazz.fullname = self.parse_name()
366 self.parse_matching_paren("<", ">")
367 extends = self.parse_extends()
368 clazz.extends = extends[0] if extends else None
Adrian Roos02e18dd2019-02-28 12:41:48 +0100369 clazz.implements_all = self.parse_implements()
Adrian Roosb787c182019-01-03 18:54:33 +0100370 # The checks assume that interfaces are always found in implements, which isn't true for
371 # subinterfaces.
Adrian Roos02e18dd2019-02-28 12:41:48 +0100372 if not clazz.implements_all and "interface" in clazz.split:
373 clazz.implements_all = [clazz.extends]
374 clazz.implements = clazz.implements_all[0] if clazz.implements_all else None
Adrian Roosb787c182019-01-03 18:54:33 +0100375 self.parse_token("{")
376 self.parse_eof()
377
378 def parse_into_field(self, field):
Adrian Roos258c5722019-01-21 15:43:15 +0100379 kind = self.parse_one_of(*V2LineParser.FIELD_KINDS)
Adrian Roosb787c182019-01-03 18:54:33 +0100380 field.split = [kind]
Adrian Roos80545ef2019-02-27 16:45:00 +0100381 field.annotations = self.parse_annotations()
382 if "@Deprecated" in field.annotations:
Adrian Roosb787c182019-01-03 18:54:33 +0100383 field.split.append("deprecated")
384 field.split.extend(self.parse_modifiers())
385 field.typ = self.parse_type()
386 field.split.append(field.typ)
387 field.name = self.parse_name()
388 field.split.append(field.name)
389 if self.parse_if("="):
390 field.value = self.parse_value_stripped()
391 else:
392 field.value = None
393
394 self.parse_token(";")
395 self.parse_eof()
396
397 def lookahead(self):
398 return self.tokenized[self.current]
399
400 def parse_one_of(self, *options):
401 found = self.lookahead()
402 if found not in options:
403 raise ValueError("Parsing failed, expected one of `%s` but found `%s` in %s" % (options, found, repr(self.tokenized)))
404 return self.parse_token()
405
406 def parse_token(self, tok = None):
407 found = self.lookahead()
408 if tok is not None and found != tok:
409 raise ValueError("Parsing failed, expected `%s` but found `%s` in %s" % (tok, found, repr(self.tokenized)))
410 self.current += 1
411 return found
412
413 def eof(self):
Adrian Roose5eeae72019-01-04 20:10:06 +0100414 return self.current == self.len
Adrian Roosb787c182019-01-03 18:54:33 +0100415
416 def parse_eof(self):
417 if not self.eof():
418 raise ValueError("Parsing failed, expected EOF, but %s has not been parsed in %s" % (self.tokenized[self.current:], self.tokenized))
419
420 def parse_if(self, tok):
421 if not self.eof() and self.lookahead() == tok:
422 self.parse_token()
423 return True
424 return False
425
426 def parse_annotations(self):
427 ret = []
428 while self.lookahead() == "@":
429 ret.append(self.parse_annotation())
430 return ret
431
432 def parse_annotation(self):
433 ret = self.parse_token("@") + self.parse_token()
434 self.parse_matching_paren("(", ")")
435 return ret
436
437 def parse_matching_paren(self, open, close):
438 start = self.current
439 if not self.parse_if(open):
440 return
441 length = len(self.tokenized)
442 count = 1
443 while count > 0:
444 if self.current == length:
445 raise ValueError("Unexpected EOF looking for closing paren: `%s`" % (self.tokenized[start:],))
446 t = self.parse_token()
447 if t == open:
448 count += 1
449 elif t == close:
450 count -= 1
451 return self.tokenized[start:self.current]
452
453 def parse_modifiers(self):
454 ret = []
455 while self.lookahead() in V2LineParser.MODIFIERS:
456 ret.append(self.parse_token())
457 return ret
458
Adrian Roos5cdfb692019-01-05 22:04:55 +0100459 def parse_kotlin_nullability(self):
460 t = self.lookahead()
461 if t == "?" or t == "!":
462 return self.parse_token()
463 return None
464
Adrian Roosb787c182019-01-03 18:54:33 +0100465 def parse_type(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100466 self.parse_annotations()
Adrian Roosb787c182019-01-03 18:54:33 +0100467 type = self.parse_token()
Adrian Roosd1e38922019-01-14 15:44:15 +0100468 if type[-1] == '.':
469 self.parse_annotations()
470 type += self.parse_token()
Adrian Roosb787c182019-01-03 18:54:33 +0100471 if type in V2LineParser.JAVA_LANG_TYPES:
472 type = "java.lang." + type
473 self.parse_matching_paren("<", ">")
Adrian Roos5cdfb692019-01-05 22:04:55 +0100474 while True:
475 t = self.lookahead()
Adrian Roosd1e38922019-01-14 15:44:15 +0100476 if t == "@":
477 self.parse_annotation()
478 elif t == "[]":
Adrian Roos5cdfb692019-01-05 22:04:55 +0100479 type += self.parse_token()
480 elif self.parse_kotlin_nullability() is not None:
481 pass # discard nullability for now
482 else:
483 break
Adrian Roosb787c182019-01-03 18:54:33 +0100484 return type
485
486 def parse_arg_type(self):
487 type = self.parse_type()
488 if self.parse_if("..."):
489 type += "..."
Adrian Roos5cdfb692019-01-05 22:04:55 +0100490 self.parse_kotlin_nullability() # discard nullability for now
Adrian Roosb787c182019-01-03 18:54:33 +0100491 return type
492
493 def parse_name(self):
494 return self.parse_token()
495
496 def parse_args(self):
497 args = []
498 if self.lookahead() == ")":
499 return args
500
501 while True:
502 args.append(self.parse_arg())
503 if self.lookahead() == ")":
504 return args
505 self.parse_token(",")
506
507 def parse_arg(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100508 self.parse_if("vararg") # kotlin vararg
Adrian Roos80545ef2019-02-27 16:45:00 +0100509 annotations = self.parse_annotations()
510 arg = Argument(self.parse_arg_type())
511 arg.annotations = annotations
Adrian Roos5cdfb692019-01-05 22:04:55 +0100512 l = self.lookahead()
513 if l != "," and l != ")":
Adrian Roosd1e38922019-01-14 15:44:15 +0100514 if self.lookahead() != '=':
Adrian Roos80545ef2019-02-27 16:45:00 +0100515 arg.name = self.parse_token() # kotlin argument name
Adrian Roos5cdfb692019-01-05 22:04:55 +0100516 if self.parse_if('='): # kotlin default value
Adrian Roos80545ef2019-02-27 16:45:00 +0100517 arg.default = self.parse_expression()
518 return arg
Adrian Roosb787c182019-01-03 18:54:33 +0100519
Adrian Roosd1e38922019-01-14 15:44:15 +0100520 def parse_expression(self):
521 while not self.lookahead() in [')', ',', ';']:
522 (self.parse_matching_paren('(', ')') or
523 self.parse_matching_paren('{', '}') or
524 self.parse_token())
525
Adrian Roosb787c182019-01-03 18:54:33 +0100526 def parse_throws(self):
527 ret = []
528 if self.parse_if("throws"):
529 ret.append(self.parse_type())
530 while self.parse_if(","):
531 ret.append(self.parse_type())
532 return ret
533
534 def parse_extends(self):
535 if self.parse_if("extends"):
536 return self.parse_space_delimited_type_list()
537 return []
538
539 def parse_implements(self):
540 if self.parse_if("implements"):
541 return self.parse_space_delimited_type_list()
542 return []
543
544 def parse_space_delimited_type_list(self, terminals = ["implements", "{"]):
545 types = []
546 while True:
547 types.append(self.parse_type())
548 if self.lookahead() in terminals:
549 return types
550
551 def parse_annotation_default(self):
552 if self.parse_if("default"):
Adrian Roosd1e38922019-01-14 15:44:15 +0100553 self.parse_expression()
Adrian Roosb787c182019-01-03 18:54:33 +0100554
555 def parse_value(self):
556 if self.lookahead() == "{":
557 return " ".join(self.parse_matching_paren("{", "}"))
558 elif self.lookahead() == "(":
559 return " ".join(self.parse_matching_paren("(", ")"))
560 else:
561 return self.parse_token()
562
563 def parse_value_stripped(self):
564 value = self.parse_value()
565 if value[0] == '"':
566 return value[1:-1]
567 return value
568
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700569
Adrian Roos038a0292018-12-19 17:11:21 +0100570def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
571 in_classes_with_base=[]):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700572 api = {}
Adrian Roos038a0292018-12-19 17:11:21 +0100573 in_classes_with_base = _retry_iterator(in_classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +0100574
575 if base_f:
Adrian Roos038a0292018-12-19 17:11:21 +0100576 base_classes = _retry_iterator(_parse_stream_to_generator(base_f))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100577 else:
578 base_classes = []
579
Adrian Roos038a0292018-12-19 17:11:21 +0100580 def handle_class(clazz):
Adrian Roos6eb57b02018-12-13 22:08:29 +0100581 if clazz_cb:
582 clazz_cb(clazz)
583 else: # In callback mode, don't keep track of the full API
584 api[clazz.fullname] = clazz
585
Adrian Roos038a0292018-12-19 17:11:21 +0100586 def handle_missed_classes_with_base(clazz):
587 for c in _yield_until_matching_class(in_classes_with_base, clazz):
588 base_class = _skip_to_matching_class(base_classes, c)
589 if base_class:
590 handle_class(base_class)
591
592 for clazz in _parse_stream_to_generator(f):
593 # Before looking at clazz, let's see if there's some classes that were not present, but
594 # may have an entry in the base stream.
595 handle_missed_classes_with_base(clazz)
596
597 base_class = _skip_to_matching_class(base_classes, clazz)
598 if base_class:
599 clazz.merge_from(base_class)
600 if out_classes_with_base is not None:
601 out_classes_with_base.append(clazz)
602 handle_class(clazz)
603
604 handle_missed_classes_with_base(None)
605
Adrian Roos6eb57b02018-12-13 22:08:29 +0100606 return api
607
608def _parse_stream_to_generator(f):
609 line = 0
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700610 pkg = None
611 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700612 blame = None
Adrian Roosb787c182019-01-03 18:54:33 +0100613 sig_format = 1
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700614
Adrian Roos80545ef2019-02-27 16:45:00 +0100615 re_blame = re.compile(r"^(\^?[a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Adrian Roos258c5722019-01-21 15:43:15 +0100616
617 field_prefixes = map(lambda kind: " %s" % (kind,), V2LineParser.FIELD_KINDS)
618 def startsWithFieldPrefix(raw):
619 for prefix in field_prefixes:
620 if raw.startswith(prefix):
621 return True
622 return False
623
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800624 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700625 line += 1
626 raw = raw.rstrip()
627 match = re_blame.match(raw)
628 if match is not None:
629 blame = match.groups()[0:2]
Adrian Roos80545ef2019-02-27 16:45:00 +0100630 if blame[0].startswith("^"): # Outside of blame range
631 blame = None
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700632 raw = match.groups()[2]
633 else:
634 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700635
Adrian Roos80545ef2019-02-27 16:45:00 +0100636 if line == 1 and V2Tokenizer.SIGNATURE_PREFIX in raw:
Adrian Rooscf82e042019-01-29 15:01:28 +0100637 sig_format_string = raw[len(V2Tokenizer.SIGNATURE_PREFIX):]
638 if sig_format_string in ["2.0", "3.0"]:
639 sig_format = 2
640 else:
641 raise ValueError("Unknown format: %s" % (sig_format_string,))
Adrian Roosb787c182019-01-03 18:54:33 +0100642 elif raw.startswith("package"):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700643 pkg = Package(line, raw, blame)
644 elif raw.startswith(" ") and raw.endswith("{"):
Adrian Roosb787c182019-01-03 18:54:33 +0100645 clazz = Class(pkg, line, raw, blame, sig_format=sig_format)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700646 elif raw.startswith(" ctor"):
Adrian Roosb787c182019-01-03 18:54:33 +0100647 clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700648 elif raw.startswith(" method"):
Adrian Roosb787c182019-01-03 18:54:33 +0100649 clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos258c5722019-01-21 15:43:15 +0100650 elif startsWithFieldPrefix(raw):
Adrian Roosb787c182019-01-03 18:54:33 +0100651 clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100652 elif raw.startswith(" }") and clazz:
Adrian Roos038a0292018-12-19 17:11:21 +0100653 yield clazz
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800654
Adrian Roos5ed42b62018-12-19 17:10:22 +0100655def _retry_iterator(it):
656 """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
657 for e in it:
658 while True:
659 retry = yield e
660 if not retry:
661 break
662 # send() was called, asking us to redeliver clazz on next(). Still need to yield
663 # a dummy value to the send() first though.
664 if (yield "Returning clazz on next()"):
665 raise TypeError("send() must be followed by next(), not send()")
666
Adrian Roos038a0292018-12-19 17:11:21 +0100667def _skip_to_matching_class(classes, needle):
668 """Takes a classes iterator and consumes entries until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700669
Adrian Roos6eb57b02018-12-13 22:08:29 +0100670 This relies on classes being sorted by package and class name."""
671
672 for clazz in classes:
673 if clazz.pkg.name < needle.pkg.name:
674 # We haven't reached the right package yet
675 continue
Adrian Roos5ed42b62018-12-19 17:10:22 +0100676 if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
677 # We're in the right package, but not the right class yet
Adrian Roos6eb57b02018-12-13 22:08:29 +0100678 continue
679 if clazz.fullname == needle.fullname:
680 return clazz
681 # We ran past the right class. Send it back into the generator, then report failure.
682 classes.send(clazz)
683 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700684
Adrian Roos038a0292018-12-19 17:11:21 +0100685def _yield_until_matching_class(classes, needle):
686 """Takes a class iterator and yields entries it until it reaches the class we're looking for.
687
688 This relies on classes being sorted by package and class name."""
689
690 for clazz in classes:
691 if needle is None:
692 yield clazz
693 elif clazz.pkg.name < needle.pkg.name:
694 # We haven't reached the right package yet
695 yield clazz
696 elif clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
697 # We're in the right package, but not the right class yet
698 yield clazz
699 elif clazz.fullname == needle.fullname:
700 # Class found, abort.
701 return
702 else:
703 # We ran past the right class. Send it back into the iterator, then abort.
704 classes.send(clazz)
705 return
706
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700707class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800708 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700709 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700710 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800711 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700712 self.msg = msg
713
714 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800715 self.head = "Error %s" % (rule) if rule else "Error"
716 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 -0700717 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800718 self.head = "Warning %s" % (rule) if rule else "Warning"
719 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 -0700720
721 self.line = clazz.line
722 blame = clazz.blame
723 if detail is not None:
724 dump += "\n in " + repr(detail)
725 self.line = detail.line
726 blame = detail.blame
727 dump += "\n in " + repr(clazz)
728 dump += "\n in " + repr(clazz.pkg)
729 dump += "\n at line " + repr(self.line)
730 if blame is not None:
731 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
732
733 self.dump = dump
734
735 def __repr__(self):
736 return self.dump
737
738
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700739failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700740
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800741def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700742 """Records an API failure to be processed later."""
743 global failures
744
Adrian Roosb787c182019-01-03 18:54:33 +0100745 sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700746 sig = sig.replace(" deprecated ", " ")
747
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800748 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700749
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700750
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800751def warn(clazz, detail, rule, msg):
752 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700753
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800754def error(clazz, detail, rule, msg):
755 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700756
757
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700758noticed = {}
759
760def notice(clazz):
761 global noticed
762
763 noticed[clazz.fullname] = hash(clazz)
764
765
Adrian Roos93bafc42019-03-05 18:31:37 +0100766verifiers = {}
767
768def verifier(f):
769 verifiers[f.__name__] = f
770 return f
771
772
773@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700774def verify_constants(clazz):
775 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700776 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600777 if clazz.fullname.startswith("android.os.Build"): return
778 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700779
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600780 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700781 for f in clazz.fields:
782 if "static" in f.split and "final" in f.split:
783 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800784 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600785 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700786 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
787 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600788 if f.typ in req and f.value is None:
789 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700790
Adrian Roos93bafc42019-03-05 18:31:37 +0100791@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700792def verify_enums(clazz):
793 """Enums are bad, mmkay?"""
Adrian Roosb787c182019-01-03 18:54:33 +0100794 if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800795 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700796
Adrian Roos93bafc42019-03-05 18:31:37 +0100797@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700798def verify_class_names(clazz):
799 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700800 if clazz.fullname.startswith("android.opengl"): return
801 if clazz.fullname.startswith("android.renderscript"): return
802 if re.match("android\.R\.[a-z]+", clazz.fullname): return
803
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700804 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800805 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700806 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800807 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700808 if clazz.name.endswith("Impl"):
809 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700810
811
Adrian Roos93bafc42019-03-05 18:31:37 +0100812@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700813def verify_method_names(clazz):
814 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700815 if clazz.fullname.startswith("android.opengl"): return
816 if clazz.fullname.startswith("android.renderscript"): return
817 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700818
819 for m in clazz.methods:
820 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800821 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700822 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800823 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700824
825
Adrian Roos93bafc42019-03-05 18:31:37 +0100826@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700827def verify_callbacks(clazz):
828 """Verify Callback classes.
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700829 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700830 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700831
832 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800833 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700834 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800835 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700836
837 if clazz.name.endswith("Callback"):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700838 for m in clazz.methods:
839 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800840 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700841
842
Adrian Roos93bafc42019-03-05 18:31:37 +0100843@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700844def verify_listeners(clazz):
845 """Verify Listener classes.
846 All Listener classes must be interface.
847 All methods must follow onFoo() naming style.
848 If only a single method, it must match class name:
849 interface OnFooListener { void onFoo() }"""
850
851 if clazz.name.endswith("Listener"):
Adrian Roosb787c182019-01-03 18:54:33 +0100852 if "abstract" in clazz.split and "class" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800853 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700854
855 for m in clazz.methods:
856 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800857 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700858
859 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
860 m = clazz.methods[0]
861 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800862 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700863
864
Adrian Roos93bafc42019-03-05 18:31:37 +0100865@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700866def verify_actions(clazz):
867 """Verify intent actions.
868 All action names must be named ACTION_FOO.
869 All action values must be scoped by package and match name:
870 package android.foo {
871 String ACTION_BAR = "android.foo.action.BAR";
872 }"""
873 for f in clazz.fields:
874 if f.value is None: continue
875 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700876 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600877 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700878
879 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
880 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
881 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800882 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700883 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700884 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700885 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700886 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700887 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700888 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
889 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700890 else:
891 prefix = clazz.pkg.name + ".action"
892 expected = prefix + "." + f.name[7:]
893 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700894 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700895
896
Adrian Roos93bafc42019-03-05 18:31:37 +0100897@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700898def verify_extras(clazz):
899 """Verify intent extras.
900 All extra names must be named EXTRA_FOO.
901 All extra values must be scoped by package and match name:
902 package android.foo {
903 String EXTRA_BAR = "android.foo.extra.BAR";
904 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700905 if clazz.fullname == "android.app.Notification": return
906 if clazz.fullname == "android.appwidget.AppWidgetManager": return
907
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700908 for f in clazz.fields:
909 if f.value is None: continue
910 if f.name.startswith("ACTION_"): continue
911
912 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
913 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
914 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800915 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700916 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700917 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700918 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700919 elif clazz.pkg.name == "android.app.admin":
920 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700921 else:
922 prefix = clazz.pkg.name + ".extra"
923 expected = prefix + "." + f.name[6:]
924 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700925 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700926
927
Adrian Roos93bafc42019-03-05 18:31:37 +0100928@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700929def verify_equals(clazz):
930 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700931 eq = False
932 hc = False
933 for m in clazz.methods:
Adrian Roosb787c182019-01-03 18:54:33 +0100934 if "static" in m.split: continue
935 if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
936 if m.sig_matches("int", "hashCode", []): hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700937 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800938 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700939
940
Adrian Roos93bafc42019-03-05 18:31:37 +0100941@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700942def verify_parcelable(clazz):
943 """Verify that Parcelable objects aren't hiding required bits."""
Adrian Roosb787c182019-01-03 18:54:33 +0100944 if clazz.implements == "android.os.Parcelable":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700945 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
946 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
947 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
948
949 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800950 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700951
Adrian Roosb787c182019-01-03 18:54:33 +0100952 if "final" not in clazz.split:
Jeff Sharkey331279b2016-02-29 16:02:02 -0700953 error(clazz, None, "FW8", "Parcelable classes must be final")
954
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700955 for c in clazz.ctors:
956 if c.args == ["android.os.Parcel"]:
957 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
958
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700959
Adrian Roos93bafc42019-03-05 18:31:37 +0100960@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700961def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800962 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700963 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600964 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700965 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800966 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700967 for f in clazz.fields:
968 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800969 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700970
971
Adrian Roos93bafc42019-03-05 18:31:37 +0100972@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700973def verify_fields(clazz):
974 """Verify that all exposed fields are final.
975 Exposed fields must follow myName style.
976 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700977
978 IGNORE_BARE_FIELDS = [
979 "android.app.ActivityManager.RecentTaskInfo",
980 "android.app.Notification",
981 "android.content.pm.ActivityInfo",
982 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600983 "android.content.pm.ComponentInfo",
984 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700985 "android.content.pm.FeatureGroupInfo",
986 "android.content.pm.InstrumentationInfo",
987 "android.content.pm.PackageInfo",
988 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600989 "android.content.res.Configuration",
990 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700991 "android.os.Message",
992 "android.system.StructPollfd",
993 ]
994
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700995 for f in clazz.fields:
996 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700997 if clazz.fullname in IGNORE_BARE_FIELDS:
998 pass
999 elif clazz.fullname.endswith("LayoutParams"):
1000 pass
1001 elif clazz.fullname.startswith("android.util.Mutable"):
1002 pass
1003 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001004 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001005
Adrian Roosd1e38922019-01-14 15:44:15 +01001006 if "static" not in f.split and "property" not in f.split:
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001007 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001008 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001009
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001010 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001011 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001012
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001013 if re.match("[A-Z_]+", f.name):
1014 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001015 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001016
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001017
Adrian Roos93bafc42019-03-05 18:31:37 +01001018@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001019def verify_register(clazz):
1020 """Verify parity of registration methods.
1021 Callback objects use register/unregister methods.
1022 Listener objects use add/remove methods."""
1023 methods = [ m.name for m in clazz.methods ]
1024 for m in clazz.methods:
1025 if "Callback" in m.raw:
1026 if m.name.startswith("register"):
1027 other = "unregister" + m.name[8:]
1028 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001029 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001030 if m.name.startswith("unregister"):
1031 other = "register" + m.name[10:]
1032 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001033 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001034
1035 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001036 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001037
1038 if "Listener" in m.raw:
1039 if m.name.startswith("add"):
1040 other = "remove" + m.name[3:]
1041 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001042 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001043 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
1044 other = "add" + m.name[6:]
1045 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001046 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001047
1048 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001049 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001050
1051
Adrian Roos93bafc42019-03-05 18:31:37 +01001052@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001053def verify_sync(clazz):
1054 """Verify synchronized methods aren't exposed."""
1055 for m in clazz.methods:
1056 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001057 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001058
1059
Adrian Roos93bafc42019-03-05 18:31:37 +01001060@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001061def verify_intent_builder(clazz):
1062 """Verify that Intent builders are createFooIntent() style."""
1063 if clazz.name == "Intent": return
1064
1065 for m in clazz.methods:
1066 if m.typ == "android.content.Intent":
1067 if m.name.startswith("create") and m.name.endswith("Intent"):
1068 pass
1069 else:
Adam Powell539ea122015-04-10 13:01:37 -07001070 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001071
1072
Adrian Roos93bafc42019-03-05 18:31:37 +01001073@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001074def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001075 """Verify that helper classes are named consistently with what they extend.
1076 All developer extendable methods should be named onFoo()."""
1077 test_methods = False
Adrian Roosb787c182019-01-03 18:54:33 +01001078 if clazz.extends == "android.app.Service":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001079 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001080 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001081 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001082
1083 found = False
1084 for f in clazz.fields:
1085 if f.name == "SERVICE_INTERFACE":
1086 found = True
1087 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001088 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001089
Adrian Roosb787c182019-01-03 18:54:33 +01001090 if clazz.extends == "android.content.ContentProvider":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001091 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001092 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001093 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001094
1095 found = False
1096 for f in clazz.fields:
1097 if f.name == "PROVIDER_INTERFACE":
1098 found = True
1099 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001100 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001101
Adrian Roosb787c182019-01-03 18:54:33 +01001102 if clazz.extends == "android.content.BroadcastReceiver":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001103 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001104 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001105 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001106
Adrian Roosb787c182019-01-03 18:54:33 +01001107 if clazz.extends == "android.app.Activity":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001108 test_methods = True
1109 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001110 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001111
1112 if test_methods:
1113 for m in clazz.methods:
1114 if "final" in m.split: continue
1115 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001116 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001117 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001118 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001119 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001120
1121
Adrian Roos93bafc42019-03-05 18:31:37 +01001122@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001123def verify_builder(clazz):
1124 """Verify builder classes.
1125 Methods should return the builder to enable chaining."""
Adrian Roosb787c182019-01-03 18:54:33 +01001126 if clazz.extends: return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001127 if not clazz.name.endswith("Builder"): return
1128
1129 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001130 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001131
1132 has_build = False
1133 for m in clazz.methods:
1134 if m.name == "build":
1135 has_build = True
1136 continue
1137
1138 if m.name.startswith("get"): continue
1139 if m.name.startswith("clear"): continue
1140
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001141 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001142 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001143
1144 if m.name.startswith("set"):
1145 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001146 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001147
1148 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001149 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001150
Adrian Roosdeb0ff22019-02-27 23:58:13 +01001151 if "final" not in clazz.split:
1152 error(clazz, None, None, "Builder should be final")
1153
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001154
Adrian Roos93bafc42019-03-05 18:31:37 +01001155@verifier
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001156def verify_aidl(clazz):
1157 """Catch people exposing raw AIDL."""
Adrian Roosb787c182019-01-03 18:54:33 +01001158 if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001159 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001160
1161
Adrian Roos93bafc42019-03-05 18:31:37 +01001162@verifier
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001163def verify_internal(clazz):
1164 """Catch people exposing internal classes."""
1165 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001166 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001167
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001168def layering_build_ranking(ranking_list):
1169 r = {}
1170 for rank, ps in enumerate(ranking_list):
1171 if not isinstance(ps, list):
1172 ps = [ps]
1173 for p in ps:
1174 rs = r
1175 for n in p.split('.'):
1176 if n not in rs:
1177 rs[n] = {}
1178 rs = rs[n]
1179 rs['-rank'] = rank
1180 return r
1181
1182LAYERING_PACKAGE_RANKING = layering_build_ranking([
1183 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
1184 "android.app",
1185 "android.widget",
1186 "android.view",
1187 "android.animation",
1188 "android.provider",
1189 ["android.content","android.graphics.drawable"],
1190 "android.database",
1191 "android.text",
1192 "android.graphics",
1193 "android.os",
1194 "android.util"
1195])
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001196
Adrian Roos93bafc42019-03-05 18:31:37 +01001197@verifier
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001198def verify_layering(clazz):
1199 """Catch package layering violations.
1200 For example, something in android.os depending on android.app."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001201
1202 def rank(p):
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001203 r = None
1204 l = LAYERING_PACKAGE_RANKING
1205 for n in p.split('.'):
1206 if n in l:
1207 l = l[n]
1208 if '-rank' in l:
1209 r = l['-rank']
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001210 else:
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001211 break
1212 return r
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001213
1214 cr = rank(clazz.pkg.name)
1215 if cr is None: return
1216
1217 for f in clazz.fields:
1218 ir = rank(f.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001219 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001220 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001221
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001222 for m in itertools.chain(clazz.methods, clazz.ctors):
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001223 ir = rank(m.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001224 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001225 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001226 for arg in m.args:
1227 ir = rank(arg)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001228 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001229 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001230
1231
Adrian Roos93bafc42019-03-05 18:31:37 +01001232@verifier
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001233def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001234 """Verifies that boolean accessors are named correctly.
1235 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001236
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001237 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
1238 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001239
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001240 gets = [ m for m in clazz.methods if is_get(m) ]
1241 sets = [ m for m in clazz.methods if is_set(m) ]
1242
1243 def error_if_exists(methods, trigger, expected, actual):
1244 for m in methods:
1245 if m.name == actual:
1246 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001247
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001248 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001249 if is_get(m):
1250 if re.match("is[A-Z]", m.name):
1251 target = m.name[2:]
1252 expected = "setIs" + target
1253 error_if_exists(sets, m.name, expected, "setHas" + target)
1254 elif re.match("has[A-Z]", m.name):
1255 target = m.name[3:]
1256 expected = "setHas" + target
1257 error_if_exists(sets, m.name, expected, "setIs" + target)
1258 error_if_exists(sets, m.name, expected, "set" + target)
1259 elif re.match("get[A-Z]", m.name):
1260 target = m.name[3:]
1261 expected = "set" + target
1262 error_if_exists(sets, m.name, expected, "setIs" + target)
1263 error_if_exists(sets, m.name, expected, "setHas" + target)
1264
1265 if is_set(m):
1266 if re.match("set[A-Z]", m.name):
1267 target = m.name[3:]
1268 expected = "get" + target
1269 error_if_exists(sets, m.name, expected, "is" + target)
1270 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001271
1272
Adrian Roos93bafc42019-03-05 18:31:37 +01001273@verifier
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001274def verify_collections(clazz):
1275 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001276 if clazz.fullname == "android.os.Bundle": return
Adrian Roos02e18dd2019-02-28 12:41:48 +01001277 if clazz.fullname == "android.os.Parcel": return
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001278
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001279 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
1280 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
1281 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001282 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001283 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001284 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001285 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001286 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001287
Adrian Roos93bafc42019-03-05 18:31:37 +01001288
1289@verifier
Adrian Roos56ff7842019-02-28 12:45:00 +01001290def verify_uris(clazz):
1291 bad = ["java.net.URL", "java.net.URI", "android.net.URL"]
1292
1293 for f in clazz.fields:
1294 if f.typ in bad:
1295 error(clazz, f, None, "Field must be android.net.Uri instead of " + f.typ)
1296
1297 for m in clazz.methods + clazz.ctors:
1298 if m.typ in bad:
1299 error(clazz, m, None, "Must return android.net.Uri instead of " + m.typ)
1300 for arg in m.args:
1301 if arg in bad:
1302 error(clazz, m, None, "Argument must take android.net.Uri instead of " + arg)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001303
Adrian Roos93bafc42019-03-05 18:31:37 +01001304
1305@verifier
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001306def verify_flags(clazz):
1307 """Verifies that flags are non-overlapping."""
1308 known = collections.defaultdict(int)
1309 for f in clazz.fields:
1310 if "FLAG_" in f.name:
1311 try:
1312 val = int(f.value)
1313 except:
1314 continue
1315
1316 scope = f.name[0:f.name.index("FLAG_")]
1317 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001318 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001319 known[scope] |= val
1320
1321
Adrian Roos93bafc42019-03-05 18:31:37 +01001322@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001323def verify_exception(clazz):
1324 """Verifies that methods don't throw generic exceptions."""
1325 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001326 for t in m.throws:
1327 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
1328 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001329
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001330 if t in ["android.os.RemoteException"]:
Adrian Roos02e18dd2019-02-28 12:41:48 +01001331 if clazz.fullname == "android.content.ContentProviderClient": continue
1332 if clazz.fullname == "android.os.Binder": continue
1333 if clazz.fullname == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -07001334
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001335 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
1336
1337 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
1338 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -07001339
Adrian Roos93bafc42019-03-05 18:31:37 +01001340
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001341GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001342
Adrian Roos93bafc42019-03-05 18:31:37 +01001343# Not marked as @verifier, because it is only conditionally applied.
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001344def verify_google(clazz):
1345 """Verifies that APIs never reference Google."""
1346
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001347 if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001348 error(clazz, None, None, "Must never reference Google")
1349
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001350 for test in clazz.ctors, clazz.fields, clazz.methods:
1351 for t in test:
1352 if GOOGLE_IGNORECASE.search(t.raw) is not None:
1353 error(clazz, t, None, "Must never reference Google")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001354
1355
Adrian Roos93bafc42019-03-05 18:31:37 +01001356@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001357def verify_bitset(clazz):
1358 """Verifies that we avoid using heavy BitSet."""
1359
1360 for f in clazz.fields:
1361 if f.typ == "java.util.BitSet":
1362 error(clazz, f, None, "Field type must not be heavy BitSet")
1363
1364 for m in clazz.methods:
1365 if m.typ == "java.util.BitSet":
1366 error(clazz, m, None, "Return type must not be heavy BitSet")
1367 for arg in m.args:
1368 if arg == "java.util.BitSet":
1369 error(clazz, m, None, "Argument type must not be heavy BitSet")
1370
1371
Adrian Roos93bafc42019-03-05 18:31:37 +01001372@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001373def verify_manager(clazz):
1374 """Verifies that FooManager is only obtained from Context."""
1375
1376 if not clazz.name.endswith("Manager"): return
1377
1378 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001379 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001380
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001381 for m in clazz.methods:
1382 if m.typ == clazz.fullname:
1383 error(clazz, m, None, "Managers must always be obtained from Context")
1384
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001385
Adrian Roos93bafc42019-03-05 18:31:37 +01001386@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001387def verify_boxed(clazz):
1388 """Verifies that methods avoid boxed primitives."""
1389
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001390 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 -08001391
1392 for c in clazz.ctors:
1393 for arg in c.args:
1394 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001395 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001396
1397 for f in clazz.fields:
1398 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001399 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001400
1401 for m in clazz.methods:
1402 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001403 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001404 for arg in m.args:
1405 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001406 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001407
1408
Adrian Roos93bafc42019-03-05 18:31:37 +01001409@verifier
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001410def verify_static_utils(clazz):
1411 """Verifies that helper classes can't be constructed."""
1412 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001413 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001414
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001415 # Only care about classes with default constructors
1416 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
1417 test = []
1418 test.extend(clazz.fields)
1419 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001420
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001421 if len(test) == 0: return
1422 for t in test:
1423 if "static" not in t.split:
1424 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001425
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001426 error(clazz, None, None, "Fully-static utility classes must not have constructor")
1427
1428
Adrian Roos93bafc42019-03-05 18:31:37 +01001429# @verifier # Disabled for now
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001430def verify_overload_args(clazz):
1431 """Verifies that method overloads add new arguments at the end."""
1432 if clazz.fullname.startswith("android.opengl"): return
1433
1434 overloads = collections.defaultdict(list)
1435 for m in clazz.methods:
1436 if "deprecated" in m.split: continue
1437 overloads[m.name].append(m)
1438
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001439 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001440 if len(methods) <= 1: continue
1441
1442 # Look for arguments common across all overloads
1443 def cluster(args):
1444 count = collections.defaultdict(int)
1445 res = set()
1446 for i in range(len(args)):
1447 a = args[i]
1448 res.add("%s#%d" % (a, count[a]))
1449 count[a] += 1
1450 return res
1451
1452 common_args = cluster(methods[0].args)
1453 for m in methods:
1454 common_args = common_args & cluster(m.args)
1455
1456 if len(common_args) == 0: continue
1457
1458 # Require that all common arguments are present at start of signature
1459 locked_sig = None
1460 for m in methods:
1461 sig = m.args[0:len(common_args)]
1462 if not common_args.issubset(cluster(sig)):
1463 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1464 elif not locked_sig:
1465 locked_sig = sig
1466 elif locked_sig != sig:
1467 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1468
1469
Adrian Roos93bafc42019-03-05 18:31:37 +01001470@verifier
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001471def verify_callback_handlers(clazz):
1472 """Verifies that methods adding listener/callback have overload
1473 for specifying delivery thread."""
1474
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001475 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001476 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001477 "animation",
1478 "view",
1479 "graphics",
1480 "transition",
1481 "widget",
1482 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001483 ]
1484 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001485 if s in clazz.pkg.name_path: return
1486 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001487
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001488 # Ignore UI classes which assume main thread
1489 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1490 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1491 if s in clazz.fullname: return
1492 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1493 for s in ["Loader"]:
1494 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001495
1496 found = {}
1497 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001498 examine = clazz.ctors + clazz.methods
1499 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001500 if m.name.startswith("unregister"): continue
1501 if m.name.startswith("remove"): continue
1502 if re.match("on[A-Z]+", m.name): continue
1503
1504 by_name[m.name].append(m)
1505
1506 for a in m.args:
1507 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1508 found[m.name] = m
1509
1510 for f in found.values():
1511 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001512 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001513 for m in by_name[f.name]:
1514 if "android.os.Handler" in m.args:
1515 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001516 if "java.util.concurrent.Executor" in m.args:
1517 takes_exec = True
1518 if not takes_exec:
1519 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001520
1521
Adrian Roos93bafc42019-03-05 18:31:37 +01001522@verifier
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001523def verify_context_first(clazz):
1524 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001525 examine = clazz.ctors + clazz.methods
1526 for m in examine:
1527 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001528 if "android.content.Context" in m.args[1:]:
1529 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001530 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1531 if "android.content.ContentResolver" in m.args[1:]:
1532 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001533
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001534
Adrian Roos93bafc42019-03-05 18:31:37 +01001535@verifier
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001536def verify_listener_last(clazz):
1537 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1538 examine = clazz.ctors + clazz.methods
1539 for m in examine:
1540 if "Listener" in m.name or "Callback" in m.name: continue
1541 found = False
1542 for a in m.args:
1543 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1544 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001545 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001546 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1547
1548
Adrian Roos93bafc42019-03-05 18:31:37 +01001549@verifier
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001550def verify_resource_names(clazz):
1551 """Verifies that resource names have consistent case."""
1552 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1553
1554 # Resources defined by files are foo_bar_baz
1555 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1556 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001557 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1558 if f.name.startswith("config_"):
1559 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1560
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001561 if re.match("[a-z1-9_]+$", f.name): continue
1562 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1563
1564 # Resources defined inside files are fooBarBaz
1565 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1566 for f in clazz.fields:
1567 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1568 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1569 if re.match("state_[a-z_]*$", f.name): continue
1570
1571 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1572 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1573
1574 # Styles are FooBar_Baz
1575 if clazz.name in ["style"]:
1576 for f in clazz.fields:
1577 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1578 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001579
1580
Adrian Roos93bafc42019-03-05 18:31:37 +01001581@verifier
Jeff Sharkey331279b2016-02-29 16:02:02 -07001582def verify_files(clazz):
1583 """Verifies that methods accepting File also accept streams."""
1584
1585 has_file = set()
1586 has_stream = set()
1587
1588 test = []
1589 test.extend(clazz.ctors)
1590 test.extend(clazz.methods)
1591
1592 for m in test:
1593 if "java.io.File" in m.args:
1594 has_file.add(m)
1595 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:
1596 has_stream.add(m.name)
1597
1598 for m in has_file:
1599 if m.name not in has_stream:
1600 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1601
1602
Adrian Roos93bafc42019-03-05 18:31:37 +01001603@verifier
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001604def verify_manager_list(clazz):
1605 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1606
1607 if not clazz.name.endswith("Manager"): return
1608
1609 for m in clazz.methods:
1610 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1611 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1612
1613
Adrian Roos93bafc42019-03-05 18:31:37 +01001614@verifier
Jeff Sharkey26c80902016-12-21 13:41:17 -07001615def verify_abstract_inner(clazz):
1616 """Verifies that abstract inner classes are static."""
1617
1618 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
Adrian Roosb787c182019-01-03 18:54:33 +01001619 if "abstract" in clazz.split and "static" not in clazz.split:
Jeff Sharkey26c80902016-12-21 13:41:17 -07001620 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1621
1622
Adrian Roos93bafc42019-03-05 18:31:37 +01001623@verifier
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001624def verify_runtime_exceptions(clazz):
1625 """Verifies that runtime exceptions aren't listed in throws."""
1626
1627 banned = [
1628 "java.lang.NullPointerException",
1629 "java.lang.ClassCastException",
1630 "java.lang.IndexOutOfBoundsException",
1631 "java.lang.reflect.UndeclaredThrowableException",
1632 "java.lang.reflect.MalformedParametersException",
1633 "java.lang.reflect.MalformedParameterizedTypeException",
1634 "java.lang.invoke.WrongMethodTypeException",
1635 "java.lang.EnumConstantNotPresentException",
1636 "java.lang.IllegalMonitorStateException",
1637 "java.lang.SecurityException",
1638 "java.lang.UnsupportedOperationException",
1639 "java.lang.annotation.AnnotationTypeMismatchException",
1640 "java.lang.annotation.IncompleteAnnotationException",
1641 "java.lang.TypeNotPresentException",
1642 "java.lang.IllegalStateException",
1643 "java.lang.ArithmeticException",
1644 "java.lang.IllegalArgumentException",
1645 "java.lang.ArrayStoreException",
1646 "java.lang.NegativeArraySizeException",
1647 "java.util.MissingResourceException",
1648 "java.util.EmptyStackException",
1649 "java.util.concurrent.CompletionException",
1650 "java.util.concurrent.RejectedExecutionException",
1651 "java.util.IllformedLocaleException",
1652 "java.util.ConcurrentModificationException",
1653 "java.util.NoSuchElementException",
1654 "java.io.UncheckedIOException",
1655 "java.time.DateTimeException",
1656 "java.security.ProviderException",
1657 "java.nio.BufferUnderflowException",
1658 "java.nio.BufferOverflowException",
1659 ]
1660
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001661 examine = clazz.ctors + clazz.methods
1662 for m in examine:
1663 for t in m.throws:
1664 if t in banned:
1665 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001666
1667
Adrian Roos93bafc42019-03-05 18:31:37 +01001668@verifier
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001669def verify_error(clazz):
1670 """Verifies that we always use Exception instead of Error."""
1671 if not clazz.extends: return
1672 if clazz.extends.endswith("Error"):
1673 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1674 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1675 error(clazz, None, None, "Exceptions must be named FooException")
1676
1677
Adrian Roos93bafc42019-03-05 18:31:37 +01001678@verifier
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001679def verify_units(clazz):
1680 """Verifies that we use consistent naming for units."""
1681
1682 # If we find K, recommend replacing with V
1683 bad = {
1684 "Ns": "Nanos",
1685 "Ms": "Millis or Micros",
1686 "Sec": "Seconds", "Secs": "Seconds",
1687 "Hr": "Hours", "Hrs": "Hours",
1688 "Mo": "Months", "Mos": "Months",
1689 "Yr": "Years", "Yrs": "Years",
1690 "Byte": "Bytes", "Space": "Bytes",
1691 }
1692
1693 for m in clazz.methods:
1694 if m.typ not in ["short","int","long"]: continue
1695 for k, v in bad.iteritems():
1696 if m.name.endswith(k):
1697 error(clazz, m, None, "Expected method name units to be " + v)
1698 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1699 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1700 if m.name.endswith("Seconds"):
1701 error(clazz, m, None, "Returned time values must be in milliseconds")
1702
1703 for m in clazz.methods:
1704 typ = m.typ
1705 if typ == "void":
1706 if len(m.args) != 1: continue
1707 typ = m.args[0]
1708
1709 if m.name.endswith("Fraction") and typ != "float":
1710 error(clazz, m, None, "Fractions must use floats")
1711 if m.name.endswith("Percentage") and typ != "int":
1712 error(clazz, m, None, "Percentage must use ints")
1713
1714
Adrian Roos93bafc42019-03-05 18:31:37 +01001715@verifier
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001716def verify_closable(clazz):
1717 """Verifies that classes are AutoClosable."""
Adrian Roos02e18dd2019-02-28 12:41:48 +01001718 if "java.lang.AutoCloseable" in clazz.implements_all: return
1719 if "java.io.Closeable" in clazz.implements_all: return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001720
1721 for m in clazz.methods:
1722 if len(m.args) > 0: continue
1723 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1724 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1725 return
1726
1727
Adrian Roos93bafc42019-03-05 18:31:37 +01001728@verifier
Jake Wharton9e6738f2017-08-23 11:59:55 -04001729def verify_member_name_not_kotlin_keyword(clazz):
1730 """Prevent method names which are keywords in Kotlin."""
1731
1732 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1733 # This list does not include Java keywords as those are already impossible to use.
1734 keywords = [
1735 'as',
1736 'fun',
1737 'in',
1738 'is',
1739 'object',
1740 'typealias',
1741 'val',
1742 'var',
1743 'when',
1744 ]
1745
1746 for m in clazz.methods:
1747 if m.name in keywords:
1748 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1749 for f in clazz.fields:
1750 if f.name in keywords:
1751 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1752
1753
Adrian Roos93bafc42019-03-05 18:31:37 +01001754@verifier
Jake Wharton9e6738f2017-08-23 11:59:55 -04001755def verify_method_name_not_kotlin_operator(clazz):
1756 """Warn about method names which become operators in Kotlin."""
1757
1758 binary = set()
1759
1760 def unique_binary_op(m, op):
1761 if op in binary:
1762 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1763 binary.add(op)
1764
1765 for m in clazz.methods:
Adrian Roosd1e38922019-01-14 15:44:15 +01001766 if 'static' in m.split or 'operator' in m.split:
Jake Wharton9e6738f2017-08-23 11:59:55 -04001767 continue
1768
1769 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1770 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1771 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1772
1773 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1774 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1775 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1776 # practical way of checking that relationship here.
1777 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1778
1779 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1780 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1781 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1782 unique_binary_op(m, m.name)
1783
1784 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1785 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1786 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1787
1788 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1789 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1790 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1791
1792 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1793 if m.name == 'invoke':
1794 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1795
1796 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1797 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1798 and len(m.args) == 1 \
1799 and m.typ == 'void':
1800 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1801 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1802
1803
Adrian Roos93bafc42019-03-05 18:31:37 +01001804@verifier
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001805def verify_collections_over_arrays(clazz):
1806 """Warn that [] should be Collections."""
1807
Adrian Roosb787c182019-01-03 18:54:33 +01001808 if "@interface" in clazz.split:
1809 return
1810
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001811 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1812 for m in clazz.methods:
1813 if m.typ.endswith("[]") and m.typ not in safe:
1814 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1815 for arg in m.args:
1816 if arg.endswith("[]") and arg not in safe:
1817 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1818
1819
Adrian Roos93bafc42019-03-05 18:31:37 +01001820@verifier
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001821def verify_user_handle(clazz):
1822 """Methods taking UserHandle should be ForUser or AsUser."""
1823 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1824 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1825 if clazz.fullname == "android.content.pm.LauncherApps": return
1826 if clazz.fullname == "android.os.UserHandle": return
1827 if clazz.fullname == "android.os.UserManager": return
1828
1829 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001830 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001831
1832 has_arg = "android.os.UserHandle" in m.args
1833 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1834
1835 if clazz.fullname.endswith("Manager") and has_arg:
1836 warn(clazz, m, None, "When a method overload is needed to target a specific "
1837 "UserHandle, callers should be directed to use "
1838 "Context.createPackageContextAsUser() and re-obtain the relevant "
1839 "Manager, and no new API should be added")
1840 elif has_arg and not has_name:
1841 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1842 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001843
1844
Adrian Roos93bafc42019-03-05 18:31:37 +01001845@verifier
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001846def verify_params(clazz):
1847 """Parameter classes should be 'Params'."""
1848 if clazz.name.endswith("Params"): return
1849 if clazz.fullname == "android.app.ActivityOptions": return
1850 if clazz.fullname == "android.app.BroadcastOptions": return
1851 if clazz.fullname == "android.os.Bundle": return
1852 if clazz.fullname == "android.os.BaseBundle": return
1853 if clazz.fullname == "android.os.PersistableBundle": return
1854
1855 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1856 for b in bad:
1857 if clazz.name.endswith(b):
1858 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1859
1860
Adrian Roos93bafc42019-03-05 18:31:37 +01001861@verifier
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001862def verify_services(clazz):
1863 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1864 if clazz.fullname != "android.content.Context": return
1865
1866 for f in clazz.fields:
1867 if f.typ != "java.lang.String": continue
1868 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1869 if found:
1870 expected = found.group(1).lower()
1871 if f.value != expected:
1872 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1873
1874
Adrian Roos93bafc42019-03-05 18:31:37 +01001875@verifier
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001876def verify_tense(clazz):
1877 """Verify tenses of method names."""
1878 if clazz.fullname.startswith("android.opengl"): return
1879
1880 for m in clazz.methods:
1881 if m.name.endswith("Enable"):
1882 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1883
1884
Adrian Roos93bafc42019-03-05 18:31:37 +01001885@verifier
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001886def verify_icu(clazz):
1887 """Verifies that richer ICU replacements are used."""
1888 better = {
1889 "java.util.TimeZone": "android.icu.util.TimeZone",
1890 "java.util.Calendar": "android.icu.util.Calendar",
1891 "java.util.Locale": "android.icu.util.ULocale",
1892 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1893 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1894 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1895 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1896 "java.lang.Character": "android.icu.lang.UCharacter",
1897 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1898 "java.text.Collator": "android.icu.text.Collator",
1899 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1900 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1901 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1902 "java.text.DateFormat": "android.icu.text.DateFormat",
1903 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1904 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1905 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1906 }
1907
1908 for m in clazz.ctors + clazz.methods:
1909 types = []
1910 types.extend(m.typ)
1911 types.extend(m.args)
1912 for arg in types:
1913 if arg in better:
1914 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1915
1916
Adrian Roos93bafc42019-03-05 18:31:37 +01001917@verifier
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001918def verify_clone(clazz):
1919 """Verify that clone() isn't implemented; see EJ page 61."""
1920 for m in clazz.methods:
1921 if m.name == "clone":
1922 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1923
1924
Adrian Roos93bafc42019-03-05 18:31:37 +01001925@verifier
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001926def verify_pfd(clazz):
1927 """Verify that android APIs use PFD over FD."""
Adrian Roos02e18dd2019-02-28 12:41:48 +01001928 if clazz.fullname == "android.os.FileUtils" or clazz.fullname == "android.system.Os":
1929 return
1930
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001931 examine = clazz.ctors + clazz.methods
1932 for m in examine:
1933 if m.typ == "java.io.FileDescriptor":
1934 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1935 if m.typ == "int":
1936 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1937 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1938 for arg in m.args:
1939 if arg == "java.io.FileDescriptor":
1940 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1941
1942 for f in clazz.fields:
1943 if f.typ == "java.io.FileDescriptor":
1944 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1945
1946
Adrian Roos93bafc42019-03-05 18:31:37 +01001947@verifier
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001948def verify_numbers(clazz):
1949 """Discourage small numbers types like short and byte."""
1950
1951 discouraged = ["short","byte"]
1952
1953 for c in clazz.ctors:
1954 for arg in c.args:
1955 if arg in discouraged:
1956 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1957
1958 for f in clazz.fields:
1959 if f.typ in discouraged:
1960 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1961
1962 for m in clazz.methods:
1963 if m.typ in discouraged:
1964 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1965 for arg in m.args:
1966 if arg in discouraged:
1967 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1968
Adrian Roos93bafc42019-03-05 18:31:37 +01001969
Adrian Roos80545ef2019-02-27 16:45:00 +01001970PRIMITIVES = {"void", "int", "float", "boolean", "short", "char", "byte", "long", "double"}
1971
Adrian Roos93bafc42019-03-05 18:31:37 +01001972@verifier
Adrian Roos80545ef2019-02-27 16:45:00 +01001973def verify_nullability(clazz):
1974 """Catches missing nullability annotations"""
1975
1976 for f in clazz.fields:
1977 if f.value is not None and 'static' in f.split and 'final' in f.split:
1978 continue # Nullability of constants can be inferred.
1979 if f.typ not in PRIMITIVES and not has_nullability(f.annotations):
1980 error(clazz, f, "M12", "Field must be marked either @NonNull or @Nullable")
1981
1982 for c in clazz.ctors:
1983 verify_nullability_args(clazz, c)
1984
1985 for m in clazz.methods:
1986 if m.name == "writeToParcel" or m.name == "onReceive":
1987 continue # Parcelable.writeToParcel() and BroadcastReceiver.onReceive() are not yet annotated
1988
1989 if m.typ not in PRIMITIVES and not has_nullability(m.annotations):
1990 error(clazz, m, "M12", "Return value must be marked either @NonNull or @Nullable")
1991 verify_nullability_args(clazz, m)
1992
1993def verify_nullability_args(clazz, m):
1994 for i, arg in enumerate(m.detailed_args):
1995 if arg.type not in PRIMITIVES and not has_nullability(arg.annotations):
1996 error(clazz, m, "M12", "Argument %d must be marked either @NonNull or @Nullable" % (i+1,))
1997
1998def has_nullability(annotations):
1999 return "@NonNull" in annotations or "@Nullable" in annotations
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06002000
Adrian Roos93bafc42019-03-05 18:31:37 +01002001
2002@verifier
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06002003def verify_singleton(clazz):
2004 """Catch singleton objects with constructors."""
2005
2006 singleton = False
2007 for m in clazz.methods:
2008 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
2009 singleton = True
2010
2011 if singleton:
2012 for c in clazz.ctors:
2013 error(clazz, c, None, "Singleton classes should use getInstance() methods")
2014
2015
2016
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002017def is_interesting(clazz):
2018 """Test if given class is interesting from an Android PoV."""
2019
2020 if clazz.pkg.name.startswith("java"): return False
2021 if clazz.pkg.name.startswith("junit"): return False
2022 if clazz.pkg.name.startswith("org.apache"): return False
2023 if clazz.pkg.name.startswith("org.xml"): return False
2024 if clazz.pkg.name.startswith("org.json"): return False
2025 if clazz.pkg.name.startswith("org.w3c"): return False
2026 if clazz.pkg.name.startswith("android.icu."): return False
2027 return True
2028
2029
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002030def examine_clazz(clazz):
2031 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002032
2033 notice(clazz)
2034
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002035 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002036
Adrian Roos93bafc42019-03-05 18:31:37 +01002037 for v in verifiers.itervalues():
2038 v(clazz)
2039
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002040 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002041
2042
Adrian Roos038a0292018-12-19 17:11:21 +01002043def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002044 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002045 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002046 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002047 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01002048 _parse_stream(stream, examine_clazz, base_f=base_stream,
2049 in_classes_with_base=in_classes_with_base,
2050 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002051 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002052
2053
2054def examine_api(api):
2055 """Find all style issues in the given parsed API."""
2056 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07002057 failures = {}
2058 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002059 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002060 return failures
2061
2062
Jeff Sharkey037458a2014-09-04 15:46:20 -07002063def verify_compat(cur, prev):
2064 """Find any incompatible API changes between two levels."""
2065 global failures
2066
2067 def class_exists(api, test):
2068 return test.fullname in api
2069
2070 def ctor_exists(api, clazz, test):
2071 for m in clazz.ctors:
2072 if m.ident == test.ident: return True
2073 return False
2074
2075 def all_methods(api, clazz):
2076 methods = list(clazz.methods)
2077 if clazz.extends is not None:
2078 methods.extend(all_methods(api, api[clazz.extends]))
2079 return methods
2080
2081 def method_exists(api, clazz, test):
2082 methods = all_methods(api, clazz)
2083 for m in methods:
2084 if m.ident == test.ident: return True
2085 return False
2086
2087 def field_exists(api, clazz, test):
2088 for f in clazz.fields:
2089 if f.ident == test.ident: return True
2090 return False
2091
2092 failures = {}
2093 for key in sorted(prev.keys()):
2094 prev_clazz = prev[key]
2095
2096 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002097 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002098 continue
2099
2100 cur_clazz = cur[key]
2101
2102 for test in prev_clazz.ctors:
2103 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002104 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002105
2106 methods = all_methods(prev, prev_clazz)
2107 for test in methods:
2108 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002109 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002110
2111 for test in prev_clazz.fields:
2112 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002113 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002114
2115 return failures
2116
2117
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002118def show_deprecations_at_birth(cur, prev):
2119 """Show API deprecations at birth."""
2120 global failures
2121
2122 # Remove all existing things so we're left with new
2123 for prev_clazz in prev.values():
2124 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002125 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002126
2127 sigs = { i.ident: i for i in prev_clazz.ctors }
2128 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
2129 sigs = { i.ident: i for i in prev_clazz.methods }
2130 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
2131 sigs = { i.ident: i for i in prev_clazz.fields }
2132 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
2133
2134 # Forget about class entirely when nothing new
2135 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
2136 del cur[prev_clazz.fullname]
2137
2138 for clazz in cur.values():
Adrian Roosb787c182019-01-03 18:54:33 +01002139 if "deprecated" in clazz.split and not clazz.fullname in prev:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002140 error(clazz, None, None, "Found API deprecation at birth")
2141
2142 for i in clazz.ctors + clazz.methods + clazz.fields:
Adrian Roosb787c182019-01-03 18:54:33 +01002143 if "deprecated" in i.split:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002144 error(clazz, i, None, "Found API deprecation at birth")
2145
2146 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
2147 format(reset=True)))
2148 for f in sorted(failures):
2149 print failures[f]
2150 print
2151
2152
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002153def show_stats(cur, prev):
2154 """Show API stats."""
2155
2156 stats = collections.defaultdict(int)
2157 for cur_clazz in cur.values():
2158 if not is_interesting(cur_clazz): continue
2159
2160 if cur_clazz.fullname not in prev:
2161 stats['new_classes'] += 1
2162 stats['new_ctors'] += len(cur_clazz.ctors)
2163 stats['new_methods'] += len(cur_clazz.methods)
2164 stats['new_fields'] += len(cur_clazz.fields)
2165 else:
2166 prev_clazz = prev[cur_clazz.fullname]
2167
2168 sigs = { i.ident: i for i in prev_clazz.ctors }
2169 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
2170 sigs = { i.ident: i for i in prev_clazz.methods }
2171 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
2172 sigs = { i.ident: i for i in prev_clazz.fields }
2173 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
2174
2175 if ctors + methods + fields > 0:
2176 stats['extend_classes'] += 1
2177 stats['extend_ctors'] += ctors
2178 stats['extend_methods'] += methods
2179 stats['extend_fields'] += fields
2180
2181 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
2182 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
2183
2184
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002185if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002186 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
2187 patterns. It ignores lint messages from a previous API level, if provided.")
2188 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
2189 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
2190 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01002191 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
2192 help="The base current.txt to use when examining system-current.txt or"
2193 " test-current.txt")
2194 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
2195 help="The base previous.txt to use when examining system-previous.txt or"
2196 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002197 parser.add_argument("--no-color", action='store_const', const=True,
2198 help="Disable terminal colors")
2199 parser.add_argument("--allow-google", action='store_const', const=True,
2200 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002201 parser.add_argument("--show-noticed", action='store_const', const=True,
2202 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002203 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
2204 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002205 parser.add_argument("--show-stats", action='store_const', const=True,
2206 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002207 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002208
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002209 if args['no_color']:
2210 USE_COLOR = False
2211
2212 if args['allow_google']:
2213 ALLOW_GOOGLE = True
2214
2215 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002216 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002217 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002218 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002219
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002220 if args['show_deprecations_at_birth']:
2221 with current_file as f:
2222 cur = _parse_stream(f)
2223 with previous_file as f:
2224 prev = _parse_stream(f)
2225 show_deprecations_at_birth(cur, prev)
2226 sys.exit()
2227
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002228 if args['show_stats']:
2229 with current_file as f:
2230 cur = _parse_stream(f)
2231 with previous_file as f:
2232 prev = _parse_stream(f)
2233 show_stats(cur, prev)
2234 sys.exit()
2235
Adrian Roos038a0292018-12-19 17:11:21 +01002236 classes_with_base = []
2237
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002238 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002239 if base_current_file:
2240 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002241 cur_fail, cur_noticed = examine_stream(f, base_f,
2242 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002243 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002244 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
2245
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002246 if not previous_file is None:
2247 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002248 if base_previous_file:
2249 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002250 prev_fail, prev_noticed = examine_stream(f, base_f,
2251 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002252 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002253 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002254
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002255 # ignore errors from previous API level
2256 for p in prev_fail:
2257 if p in cur_fail:
2258 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002259
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002260 # ignore classes unchanged from previous API level
2261 for k, v in prev_noticed.iteritems():
2262 if k in cur_noticed and v == cur_noticed[k]:
2263 del cur_noticed[k]
2264
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002265 """
2266 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002267 # look for compatibility issues
2268 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002269
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002270 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2271 for f in sorted(compat_fail):
2272 print compat_fail[f]
2273 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002274 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002275
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002276 if args['show_noticed'] and len(cur_noticed) != 0:
2277 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2278 for f in sorted(cur_noticed.keys()):
2279 print f
2280 print
2281
Jason Monk53b2a732017-11-10 15:43:17 -05002282 if len(cur_fail) != 0:
2283 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2284 for f in sorted(cur_fail):
2285 print cur_fail[f]
2286 print
2287 sys.exit(77)