blob: ddfee622699e3b4fc7aa84ddba7843ec89e4e4b9 [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
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700766def verify_constants(clazz):
767 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700768 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600769 if clazz.fullname.startswith("android.os.Build"): return
770 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700771
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600772 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700773 for f in clazz.fields:
774 if "static" in f.split and "final" in f.split:
775 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800776 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600777 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700778 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
779 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600780 if f.typ in req and f.value is None:
781 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700782
783
784def verify_enums(clazz):
785 """Enums are bad, mmkay?"""
Adrian Roosb787c182019-01-03 18:54:33 +0100786 if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800787 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700788
789
790def verify_class_names(clazz):
791 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700792 if clazz.fullname.startswith("android.opengl"): return
793 if clazz.fullname.startswith("android.renderscript"): return
794 if re.match("android\.R\.[a-z]+", clazz.fullname): return
795
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700796 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800797 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700798 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800799 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700800 if clazz.name.endswith("Impl"):
801 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700802
803
804def verify_method_names(clazz):
805 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700806 if clazz.fullname.startswith("android.opengl"): return
807 if clazz.fullname.startswith("android.renderscript"): return
808 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700809
810 for m in clazz.methods:
811 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800812 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700813 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800814 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700815
816
817def verify_callbacks(clazz):
818 """Verify Callback classes.
819 All callback classes must be abstract.
820 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700821 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700822
823 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800824 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700825 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800826 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700827
828 if clazz.name.endswith("Callback"):
829 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800830 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700831
832 for m in clazz.methods:
833 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800834 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700835
836
837def verify_listeners(clazz):
838 """Verify Listener classes.
839 All Listener classes must be interface.
840 All methods must follow onFoo() naming style.
841 If only a single method, it must match class name:
842 interface OnFooListener { void onFoo() }"""
843
844 if clazz.name.endswith("Listener"):
Adrian Roosb787c182019-01-03 18:54:33 +0100845 if "abstract" in clazz.split and "class" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800846 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700847
848 for m in clazz.methods:
849 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800850 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700851
852 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
853 m = clazz.methods[0]
854 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800855 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700856
857
858def verify_actions(clazz):
859 """Verify intent actions.
860 All action names must be named ACTION_FOO.
861 All action values must be scoped by package and match name:
862 package android.foo {
863 String ACTION_BAR = "android.foo.action.BAR";
864 }"""
865 for f in clazz.fields:
866 if f.value is None: continue
867 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700868 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600869 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700870
871 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
872 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
873 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800874 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700875 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700876 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700877 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700878 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700879 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700880 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
881 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700882 else:
883 prefix = clazz.pkg.name + ".action"
884 expected = prefix + "." + f.name[7:]
885 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700886 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700887
888
889def verify_extras(clazz):
890 """Verify intent extras.
891 All extra names must be named EXTRA_FOO.
892 All extra values must be scoped by package and match name:
893 package android.foo {
894 String EXTRA_BAR = "android.foo.extra.BAR";
895 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700896 if clazz.fullname == "android.app.Notification": return
897 if clazz.fullname == "android.appwidget.AppWidgetManager": return
898
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700899 for f in clazz.fields:
900 if f.value is None: continue
901 if f.name.startswith("ACTION_"): continue
902
903 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
904 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
905 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800906 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700907 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700908 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700909 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700910 elif clazz.pkg.name == "android.app.admin":
911 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700912 else:
913 prefix = clazz.pkg.name + ".extra"
914 expected = prefix + "." + f.name[6:]
915 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700916 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700917
918
919def verify_equals(clazz):
920 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e62016-12-21 13:46:33 -0700921 eq = False
922 hc = False
923 for m in clazz.methods:
Adrian Roosb787c182019-01-03 18:54:33 +0100924 if "static" in m.split: continue
925 if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
926 if m.sig_matches("int", "hashCode", []): hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700927 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800928 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700929
930
931def verify_parcelable(clazz):
932 """Verify that Parcelable objects aren't hiding required bits."""
Adrian Roosb787c182019-01-03 18:54:33 +0100933 if clazz.implements == "android.os.Parcelable":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700934 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
935 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
936 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
937
938 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800939 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700940
Adrian Roosb787c182019-01-03 18:54:33 +0100941 if "final" not in clazz.split:
Jeff Sharkey331279b2016-02-29 16:02:02 -0700942 error(clazz, None, "FW8", "Parcelable classes must be final")
943
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700944 for c in clazz.ctors:
945 if c.args == ["android.os.Parcel"]:
946 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
947
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700948
949def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800950 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700951 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600952 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700953 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800954 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700955 for f in clazz.fields:
956 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800957 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700958
959
960def verify_fields(clazz):
961 """Verify that all exposed fields are final.
962 Exposed fields must follow myName style.
963 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700964
965 IGNORE_BARE_FIELDS = [
966 "android.app.ActivityManager.RecentTaskInfo",
967 "android.app.Notification",
968 "android.content.pm.ActivityInfo",
969 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600970 "android.content.pm.ComponentInfo",
971 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700972 "android.content.pm.FeatureGroupInfo",
973 "android.content.pm.InstrumentationInfo",
974 "android.content.pm.PackageInfo",
975 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600976 "android.content.res.Configuration",
977 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700978 "android.os.Message",
979 "android.system.StructPollfd",
980 ]
981
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700982 for f in clazz.fields:
983 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700984 if clazz.fullname in IGNORE_BARE_FIELDS:
985 pass
986 elif clazz.fullname.endswith("LayoutParams"):
987 pass
988 elif clazz.fullname.startswith("android.util.Mutable"):
989 pass
990 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800991 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700992
Adrian Roosd1e38922019-01-14 15:44:15 +0100993 if "static" not in f.split and "property" not in f.split:
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700994 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800995 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700996
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700997 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800998 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700999
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001000 if re.match("[A-Z_]+", f.name):
1001 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001002 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001003
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001004
1005def verify_register(clazz):
1006 """Verify parity of registration methods.
1007 Callback objects use register/unregister methods.
1008 Listener objects use add/remove methods."""
1009 methods = [ m.name for m in clazz.methods ]
1010 for m in clazz.methods:
1011 if "Callback" in m.raw:
1012 if m.name.startswith("register"):
1013 other = "unregister" + m.name[8:]
1014 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001015 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001016 if m.name.startswith("unregister"):
1017 other = "register" + m.name[10:]
1018 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001019 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001020
1021 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001022 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001023
1024 if "Listener" in m.raw:
1025 if m.name.startswith("add"):
1026 other = "remove" + m.name[3:]
1027 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001028 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001029 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
1030 other = "add" + m.name[6:]
1031 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001032 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001033
1034 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001035 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001036
1037
1038def verify_sync(clazz):
1039 """Verify synchronized methods aren't exposed."""
1040 for m in clazz.methods:
1041 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001042 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001043
1044
1045def verify_intent_builder(clazz):
1046 """Verify that Intent builders are createFooIntent() style."""
1047 if clazz.name == "Intent": return
1048
1049 for m in clazz.methods:
1050 if m.typ == "android.content.Intent":
1051 if m.name.startswith("create") and m.name.endswith("Intent"):
1052 pass
1053 else:
Adam Powell539ea122015-04-10 13:01:37 -07001054 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001055
1056
1057def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001058 """Verify that helper classes are named consistently with what they extend.
1059 All developer extendable methods should be named onFoo()."""
1060 test_methods = False
Adrian Roosb787c182019-01-03 18:54:33 +01001061 if clazz.extends == "android.app.Service":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001062 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001063 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001064 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001065
1066 found = False
1067 for f in clazz.fields:
1068 if f.name == "SERVICE_INTERFACE":
1069 found = True
1070 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001071 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001072
Adrian Roosb787c182019-01-03 18:54:33 +01001073 if clazz.extends == "android.content.ContentProvider":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001074 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001075 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001076 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001077
1078 found = False
1079 for f in clazz.fields:
1080 if f.name == "PROVIDER_INTERFACE":
1081 found = True
1082 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001083 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001084
Adrian Roosb787c182019-01-03 18:54:33 +01001085 if clazz.extends == "android.content.BroadcastReceiver":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001086 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001087 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001088 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001089
Adrian Roosb787c182019-01-03 18:54:33 +01001090 if clazz.extends == "android.app.Activity":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001091 test_methods = True
1092 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001093 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001094
1095 if test_methods:
1096 for m in clazz.methods:
1097 if "final" in m.split: continue
1098 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001099 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001100 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001101 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001102 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001103
1104
1105def verify_builder(clazz):
1106 """Verify builder classes.
1107 Methods should return the builder to enable chaining."""
Adrian Roosb787c182019-01-03 18:54:33 +01001108 if clazz.extends: return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001109 if not clazz.name.endswith("Builder"): return
1110
1111 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001112 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001113
1114 has_build = False
1115 for m in clazz.methods:
1116 if m.name == "build":
1117 has_build = True
1118 continue
1119
1120 if m.name.startswith("get"): continue
1121 if m.name.startswith("clear"): continue
1122
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001123 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001124 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001125
1126 if m.name.startswith("set"):
1127 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001128 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001129
1130 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001131 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001132
Adrian Roosdeb0ff22019-02-27 23:58:13 +01001133 if "final" not in clazz.split:
1134 error(clazz, None, None, "Builder should be final")
1135
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001136
1137def verify_aidl(clazz):
1138 """Catch people exposing raw AIDL."""
Adrian Roosb787c182019-01-03 18:54:33 +01001139 if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001140 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001141
1142
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001143def verify_internal(clazz):
1144 """Catch people exposing internal classes."""
1145 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001146 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001147
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001148def layering_build_ranking(ranking_list):
1149 r = {}
1150 for rank, ps in enumerate(ranking_list):
1151 if not isinstance(ps, list):
1152 ps = [ps]
1153 for p in ps:
1154 rs = r
1155 for n in p.split('.'):
1156 if n not in rs:
1157 rs[n] = {}
1158 rs = rs[n]
1159 rs['-rank'] = rank
1160 return r
1161
1162LAYERING_PACKAGE_RANKING = layering_build_ranking([
1163 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
1164 "android.app",
1165 "android.widget",
1166 "android.view",
1167 "android.animation",
1168 "android.provider",
1169 ["android.content","android.graphics.drawable"],
1170 "android.database",
1171 "android.text",
1172 "android.graphics",
1173 "android.os",
1174 "android.util"
1175])
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001176
1177def verify_layering(clazz):
1178 """Catch package layering violations.
1179 For example, something in android.os depending on android.app."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001180
1181 def rank(p):
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001182 r = None
1183 l = LAYERING_PACKAGE_RANKING
1184 for n in p.split('.'):
1185 if n in l:
1186 l = l[n]
1187 if '-rank' in l:
1188 r = l['-rank']
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001189 else:
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001190 break
1191 return r
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001192
1193 cr = rank(clazz.pkg.name)
1194 if cr is None: return
1195
1196 for f in clazz.fields:
1197 ir = rank(f.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001198 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001199 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001200
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001201 for m in itertools.chain(clazz.methods, clazz.ctors):
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001202 ir = rank(m.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001203 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001204 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001205 for arg in m.args:
1206 ir = rank(arg)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001207 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001208 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001209
1210
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001211def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001212 """Verifies that boolean accessors are named correctly.
1213 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001214
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001215 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
1216 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001217
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001218 gets = [ m for m in clazz.methods if is_get(m) ]
1219 sets = [ m for m in clazz.methods if is_set(m) ]
1220
1221 def error_if_exists(methods, trigger, expected, actual):
1222 for m in methods:
1223 if m.name == actual:
1224 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001225
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001226 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001227 if is_get(m):
1228 if re.match("is[A-Z]", m.name):
1229 target = m.name[2:]
1230 expected = "setIs" + target
1231 error_if_exists(sets, m.name, expected, "setHas" + target)
1232 elif re.match("has[A-Z]", m.name):
1233 target = m.name[3:]
1234 expected = "setHas" + target
1235 error_if_exists(sets, m.name, expected, "setIs" + target)
1236 error_if_exists(sets, m.name, expected, "set" + target)
1237 elif re.match("get[A-Z]", m.name):
1238 target = m.name[3:]
1239 expected = "set" + target
1240 error_if_exists(sets, m.name, expected, "setIs" + target)
1241 error_if_exists(sets, m.name, expected, "setHas" + target)
1242
1243 if is_set(m):
1244 if re.match("set[A-Z]", m.name):
1245 target = m.name[3:]
1246 expected = "get" + target
1247 error_if_exists(sets, m.name, expected, "is" + target)
1248 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001249
1250
1251def verify_collections(clazz):
1252 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001253 if clazz.fullname == "android.os.Bundle": return
Adrian Roos02e18dd2019-02-28 12:41:48 +01001254 if clazz.fullname == "android.os.Parcel": return
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001255
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001256 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
1257 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
1258 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001259 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001260 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001261 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001262 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001263 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001264
Adrian Roos56ff7842019-02-28 12:45:00 +01001265def verify_uris(clazz):
1266 bad = ["java.net.URL", "java.net.URI", "android.net.URL"]
1267
1268 for f in clazz.fields:
1269 if f.typ in bad:
1270 error(clazz, f, None, "Field must be android.net.Uri instead of " + f.typ)
1271
1272 for m in clazz.methods + clazz.ctors:
1273 if m.typ in bad:
1274 error(clazz, m, None, "Must return android.net.Uri instead of " + m.typ)
1275 for arg in m.args:
1276 if arg in bad:
1277 error(clazz, m, None, "Argument must take android.net.Uri instead of " + arg)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001278
1279def verify_flags(clazz):
1280 """Verifies that flags are non-overlapping."""
1281 known = collections.defaultdict(int)
1282 for f in clazz.fields:
1283 if "FLAG_" in f.name:
1284 try:
1285 val = int(f.value)
1286 except:
1287 continue
1288
1289 scope = f.name[0:f.name.index("FLAG_")]
1290 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001291 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001292 known[scope] |= val
1293
1294
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001295def verify_exception(clazz):
1296 """Verifies that methods don't throw generic exceptions."""
1297 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001298 for t in m.throws:
1299 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
1300 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001301
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001302 if t in ["android.os.RemoteException"]:
Adrian Roos02e18dd2019-02-28 12:41:48 +01001303 if clazz.fullname == "android.content.ContentProviderClient": continue
1304 if clazz.fullname == "android.os.Binder": continue
1305 if clazz.fullname == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -07001306
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001307 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
1308
1309 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
1310 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -07001311
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001312GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001313
1314def verify_google(clazz):
1315 """Verifies that APIs never reference Google."""
1316
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001317 if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001318 error(clazz, None, None, "Must never reference Google")
1319
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001320 for test in clazz.ctors, clazz.fields, clazz.methods:
1321 for t in test:
1322 if GOOGLE_IGNORECASE.search(t.raw) is not None:
1323 error(clazz, t, None, "Must never reference Google")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001324
1325
1326def verify_bitset(clazz):
1327 """Verifies that we avoid using heavy BitSet."""
1328
1329 for f in clazz.fields:
1330 if f.typ == "java.util.BitSet":
1331 error(clazz, f, None, "Field type must not be heavy BitSet")
1332
1333 for m in clazz.methods:
1334 if m.typ == "java.util.BitSet":
1335 error(clazz, m, None, "Return type must not be heavy BitSet")
1336 for arg in m.args:
1337 if arg == "java.util.BitSet":
1338 error(clazz, m, None, "Argument type must not be heavy BitSet")
1339
1340
1341def verify_manager(clazz):
1342 """Verifies that FooManager is only obtained from Context."""
1343
1344 if not clazz.name.endswith("Manager"): return
1345
1346 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001347 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001348
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001349 for m in clazz.methods:
1350 if m.typ == clazz.fullname:
1351 error(clazz, m, None, "Managers must always be obtained from Context")
1352
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001353
1354def verify_boxed(clazz):
1355 """Verifies that methods avoid boxed primitives."""
1356
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001357 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 -08001358
1359 for c in clazz.ctors:
1360 for arg in c.args:
1361 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001362 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001363
1364 for f in clazz.fields:
1365 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001366 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001367
1368 for m in clazz.methods:
1369 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001370 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001371 for arg in m.args:
1372 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001373 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001374
1375
1376def verify_static_utils(clazz):
1377 """Verifies that helper classes can't be constructed."""
1378 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001379 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001380
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001381 # Only care about classes with default constructors
1382 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
1383 test = []
1384 test.extend(clazz.fields)
1385 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001386
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001387 if len(test) == 0: return
1388 for t in test:
1389 if "static" not in t.split:
1390 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001391
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001392 error(clazz, None, None, "Fully-static utility classes must not have constructor")
1393
1394
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001395def verify_overload_args(clazz):
1396 """Verifies that method overloads add new arguments at the end."""
1397 if clazz.fullname.startswith("android.opengl"): return
1398
1399 overloads = collections.defaultdict(list)
1400 for m in clazz.methods:
1401 if "deprecated" in m.split: continue
1402 overloads[m.name].append(m)
1403
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001404 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001405 if len(methods) <= 1: continue
1406
1407 # Look for arguments common across all overloads
1408 def cluster(args):
1409 count = collections.defaultdict(int)
1410 res = set()
1411 for i in range(len(args)):
1412 a = args[i]
1413 res.add("%s#%d" % (a, count[a]))
1414 count[a] += 1
1415 return res
1416
1417 common_args = cluster(methods[0].args)
1418 for m in methods:
1419 common_args = common_args & cluster(m.args)
1420
1421 if len(common_args) == 0: continue
1422
1423 # Require that all common arguments are present at start of signature
1424 locked_sig = None
1425 for m in methods:
1426 sig = m.args[0:len(common_args)]
1427 if not common_args.issubset(cluster(sig)):
1428 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1429 elif not locked_sig:
1430 locked_sig = sig
1431 elif locked_sig != sig:
1432 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1433
1434
1435def verify_callback_handlers(clazz):
1436 """Verifies that methods adding listener/callback have overload
1437 for specifying delivery thread."""
1438
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001439 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001440 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001441 "animation",
1442 "view",
1443 "graphics",
1444 "transition",
1445 "widget",
1446 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001447 ]
1448 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001449 if s in clazz.pkg.name_path: return
1450 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001451
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001452 # Ignore UI classes which assume main thread
1453 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1454 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1455 if s in clazz.fullname: return
1456 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1457 for s in ["Loader"]:
1458 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001459
1460 found = {}
1461 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001462 examine = clazz.ctors + clazz.methods
1463 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001464 if m.name.startswith("unregister"): continue
1465 if m.name.startswith("remove"): continue
1466 if re.match("on[A-Z]+", m.name): continue
1467
1468 by_name[m.name].append(m)
1469
1470 for a in m.args:
1471 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1472 found[m.name] = m
1473
1474 for f in found.values():
1475 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001476 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001477 for m in by_name[f.name]:
1478 if "android.os.Handler" in m.args:
1479 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001480 if "java.util.concurrent.Executor" in m.args:
1481 takes_exec = True
1482 if not takes_exec:
1483 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001484
1485
1486def verify_context_first(clazz):
1487 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001488 examine = clazz.ctors + clazz.methods
1489 for m in examine:
1490 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001491 if "android.content.Context" in m.args[1:]:
1492 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001493 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1494 if "android.content.ContentResolver" in m.args[1:]:
1495 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001496
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001497
1498def verify_listener_last(clazz):
1499 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1500 examine = clazz.ctors + clazz.methods
1501 for m in examine:
1502 if "Listener" in m.name or "Callback" in m.name: continue
1503 found = False
1504 for a in m.args:
1505 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1506 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001507 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001508 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1509
1510
1511def verify_resource_names(clazz):
1512 """Verifies that resource names have consistent case."""
1513 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1514
1515 # Resources defined by files are foo_bar_baz
1516 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1517 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001518 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1519 if f.name.startswith("config_"):
1520 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1521
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001522 if re.match("[a-z1-9_]+$", f.name): continue
1523 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1524
1525 # Resources defined inside files are fooBarBaz
1526 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1527 for f in clazz.fields:
1528 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1529 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1530 if re.match("state_[a-z_]*$", f.name): continue
1531
1532 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1533 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1534
1535 # Styles are FooBar_Baz
1536 if clazz.name in ["style"]:
1537 for f in clazz.fields:
1538 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1539 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001540
1541
Jeff Sharkey331279b2016-02-29 16:02:02 -07001542def verify_files(clazz):
1543 """Verifies that methods accepting File also accept streams."""
1544
1545 has_file = set()
1546 has_stream = set()
1547
1548 test = []
1549 test.extend(clazz.ctors)
1550 test.extend(clazz.methods)
1551
1552 for m in test:
1553 if "java.io.File" in m.args:
1554 has_file.add(m)
1555 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:
1556 has_stream.add(m.name)
1557
1558 for m in has_file:
1559 if m.name not in has_stream:
1560 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1561
1562
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001563def verify_manager_list(clazz):
1564 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1565
1566 if not clazz.name.endswith("Manager"): return
1567
1568 for m in clazz.methods:
1569 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1570 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1571
1572
Jeff Sharkey26c80902016-12-21 13:41:17 -07001573def verify_abstract_inner(clazz):
1574 """Verifies that abstract inner classes are static."""
1575
1576 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
Adrian Roosb787c182019-01-03 18:54:33 +01001577 if "abstract" in clazz.split and "static" not in clazz.split:
Jeff Sharkey26c80902016-12-21 13:41:17 -07001578 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1579
1580
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001581def verify_runtime_exceptions(clazz):
1582 """Verifies that runtime exceptions aren't listed in throws."""
1583
1584 banned = [
1585 "java.lang.NullPointerException",
1586 "java.lang.ClassCastException",
1587 "java.lang.IndexOutOfBoundsException",
1588 "java.lang.reflect.UndeclaredThrowableException",
1589 "java.lang.reflect.MalformedParametersException",
1590 "java.lang.reflect.MalformedParameterizedTypeException",
1591 "java.lang.invoke.WrongMethodTypeException",
1592 "java.lang.EnumConstantNotPresentException",
1593 "java.lang.IllegalMonitorStateException",
1594 "java.lang.SecurityException",
1595 "java.lang.UnsupportedOperationException",
1596 "java.lang.annotation.AnnotationTypeMismatchException",
1597 "java.lang.annotation.IncompleteAnnotationException",
1598 "java.lang.TypeNotPresentException",
1599 "java.lang.IllegalStateException",
1600 "java.lang.ArithmeticException",
1601 "java.lang.IllegalArgumentException",
1602 "java.lang.ArrayStoreException",
1603 "java.lang.NegativeArraySizeException",
1604 "java.util.MissingResourceException",
1605 "java.util.EmptyStackException",
1606 "java.util.concurrent.CompletionException",
1607 "java.util.concurrent.RejectedExecutionException",
1608 "java.util.IllformedLocaleException",
1609 "java.util.ConcurrentModificationException",
1610 "java.util.NoSuchElementException",
1611 "java.io.UncheckedIOException",
1612 "java.time.DateTimeException",
1613 "java.security.ProviderException",
1614 "java.nio.BufferUnderflowException",
1615 "java.nio.BufferOverflowException",
1616 ]
1617
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001618 examine = clazz.ctors + clazz.methods
1619 for m in examine:
1620 for t in m.throws:
1621 if t in banned:
1622 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001623
1624
1625def verify_error(clazz):
1626 """Verifies that we always use Exception instead of Error."""
1627 if not clazz.extends: return
1628 if clazz.extends.endswith("Error"):
1629 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1630 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1631 error(clazz, None, None, "Exceptions must be named FooException")
1632
1633
1634def verify_units(clazz):
1635 """Verifies that we use consistent naming for units."""
1636
1637 # If we find K, recommend replacing with V
1638 bad = {
1639 "Ns": "Nanos",
1640 "Ms": "Millis or Micros",
1641 "Sec": "Seconds", "Secs": "Seconds",
1642 "Hr": "Hours", "Hrs": "Hours",
1643 "Mo": "Months", "Mos": "Months",
1644 "Yr": "Years", "Yrs": "Years",
1645 "Byte": "Bytes", "Space": "Bytes",
1646 }
1647
1648 for m in clazz.methods:
1649 if m.typ not in ["short","int","long"]: continue
1650 for k, v in bad.iteritems():
1651 if m.name.endswith(k):
1652 error(clazz, m, None, "Expected method name units to be " + v)
1653 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1654 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1655 if m.name.endswith("Seconds"):
1656 error(clazz, m, None, "Returned time values must be in milliseconds")
1657
1658 for m in clazz.methods:
1659 typ = m.typ
1660 if typ == "void":
1661 if len(m.args) != 1: continue
1662 typ = m.args[0]
1663
1664 if m.name.endswith("Fraction") and typ != "float":
1665 error(clazz, m, None, "Fractions must use floats")
1666 if m.name.endswith("Percentage") and typ != "int":
1667 error(clazz, m, None, "Percentage must use ints")
1668
1669
1670def verify_closable(clazz):
1671 """Verifies that classes are AutoClosable."""
Adrian Roos02e18dd2019-02-28 12:41:48 +01001672 if "java.lang.AutoCloseable" in clazz.implements_all: return
1673 if "java.io.Closeable" in clazz.implements_all: return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001674
1675 for m in clazz.methods:
1676 if len(m.args) > 0: continue
1677 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1678 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1679 return
1680
1681
Jake Wharton9e6738f2017-08-23 11:59:55 -04001682def verify_member_name_not_kotlin_keyword(clazz):
1683 """Prevent method names which are keywords in Kotlin."""
1684
1685 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1686 # This list does not include Java keywords as those are already impossible to use.
1687 keywords = [
1688 'as',
1689 'fun',
1690 'in',
1691 'is',
1692 'object',
1693 'typealias',
1694 'val',
1695 'var',
1696 'when',
1697 ]
1698
1699 for m in clazz.methods:
1700 if m.name in keywords:
1701 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1702 for f in clazz.fields:
1703 if f.name in keywords:
1704 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1705
1706
1707def verify_method_name_not_kotlin_operator(clazz):
1708 """Warn about method names which become operators in Kotlin."""
1709
1710 binary = set()
1711
1712 def unique_binary_op(m, op):
1713 if op in binary:
1714 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1715 binary.add(op)
1716
1717 for m in clazz.methods:
Adrian Roosd1e38922019-01-14 15:44:15 +01001718 if 'static' in m.split or 'operator' in m.split:
Jake Wharton9e6738f2017-08-23 11:59:55 -04001719 continue
1720
1721 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1722 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1723 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1724
1725 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1726 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1727 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1728 # practical way of checking that relationship here.
1729 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1730
1731 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1732 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1733 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1734 unique_binary_op(m, m.name)
1735
1736 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1737 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1738 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1739
1740 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1741 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1742 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1743
1744 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1745 if m.name == 'invoke':
1746 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1747
1748 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1749 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1750 and len(m.args) == 1 \
1751 and m.typ == 'void':
1752 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1753 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1754
1755
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001756def verify_collections_over_arrays(clazz):
1757 """Warn that [] should be Collections."""
1758
Adrian Roosb787c182019-01-03 18:54:33 +01001759 if "@interface" in clazz.split:
1760 return
1761
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001762 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1763 for m in clazz.methods:
1764 if m.typ.endswith("[]") and m.typ not in safe:
1765 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1766 for arg in m.args:
1767 if arg.endswith("[]") and arg not in safe:
1768 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1769
1770
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001771def verify_user_handle(clazz):
1772 """Methods taking UserHandle should be ForUser or AsUser."""
1773 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1774 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1775 if clazz.fullname == "android.content.pm.LauncherApps": return
1776 if clazz.fullname == "android.os.UserHandle": return
1777 if clazz.fullname == "android.os.UserManager": return
1778
1779 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001780 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001781
1782 has_arg = "android.os.UserHandle" in m.args
1783 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1784
1785 if clazz.fullname.endswith("Manager") and has_arg:
1786 warn(clazz, m, None, "When a method overload is needed to target a specific "
1787 "UserHandle, callers should be directed to use "
1788 "Context.createPackageContextAsUser() and re-obtain the relevant "
1789 "Manager, and no new API should be added")
1790 elif has_arg and not has_name:
1791 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1792 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001793
1794
1795def verify_params(clazz):
1796 """Parameter classes should be 'Params'."""
1797 if clazz.name.endswith("Params"): return
1798 if clazz.fullname == "android.app.ActivityOptions": return
1799 if clazz.fullname == "android.app.BroadcastOptions": return
1800 if clazz.fullname == "android.os.Bundle": return
1801 if clazz.fullname == "android.os.BaseBundle": return
1802 if clazz.fullname == "android.os.PersistableBundle": return
1803
1804 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1805 for b in bad:
1806 if clazz.name.endswith(b):
1807 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1808
1809
1810def verify_services(clazz):
1811 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1812 if clazz.fullname != "android.content.Context": return
1813
1814 for f in clazz.fields:
1815 if f.typ != "java.lang.String": continue
1816 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1817 if found:
1818 expected = found.group(1).lower()
1819 if f.value != expected:
1820 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1821
1822
1823def verify_tense(clazz):
1824 """Verify tenses of method names."""
1825 if clazz.fullname.startswith("android.opengl"): return
1826
1827 for m in clazz.methods:
1828 if m.name.endswith("Enable"):
1829 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1830
1831
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001832def verify_icu(clazz):
1833 """Verifies that richer ICU replacements are used."""
1834 better = {
1835 "java.util.TimeZone": "android.icu.util.TimeZone",
1836 "java.util.Calendar": "android.icu.util.Calendar",
1837 "java.util.Locale": "android.icu.util.ULocale",
1838 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1839 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1840 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1841 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1842 "java.lang.Character": "android.icu.lang.UCharacter",
1843 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1844 "java.text.Collator": "android.icu.text.Collator",
1845 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1846 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1847 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1848 "java.text.DateFormat": "android.icu.text.DateFormat",
1849 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1850 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1851 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1852 }
1853
1854 for m in clazz.ctors + clazz.methods:
1855 types = []
1856 types.extend(m.typ)
1857 types.extend(m.args)
1858 for arg in types:
1859 if arg in better:
1860 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1861
1862
1863def verify_clone(clazz):
1864 """Verify that clone() isn't implemented; see EJ page 61."""
1865 for m in clazz.methods:
1866 if m.name == "clone":
1867 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1868
1869
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001870def verify_pfd(clazz):
1871 """Verify that android APIs use PFD over FD."""
Adrian Roos02e18dd2019-02-28 12:41:48 +01001872 if clazz.fullname == "android.os.FileUtils" or clazz.fullname == "android.system.Os":
1873 return
1874
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001875 examine = clazz.ctors + clazz.methods
1876 for m in examine:
1877 if m.typ == "java.io.FileDescriptor":
1878 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1879 if m.typ == "int":
1880 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1881 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1882 for arg in m.args:
1883 if arg == "java.io.FileDescriptor":
1884 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1885
1886 for f in clazz.fields:
1887 if f.typ == "java.io.FileDescriptor":
1888 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1889
1890
1891def verify_numbers(clazz):
1892 """Discourage small numbers types like short and byte."""
1893
1894 discouraged = ["short","byte"]
1895
1896 for c in clazz.ctors:
1897 for arg in c.args:
1898 if arg in discouraged:
1899 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1900
1901 for f in clazz.fields:
1902 if f.typ in discouraged:
1903 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1904
1905 for m in clazz.methods:
1906 if m.typ in discouraged:
1907 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1908 for arg in m.args:
1909 if arg in discouraged:
1910 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1911
Adrian Roos80545ef2019-02-27 16:45:00 +01001912PRIMITIVES = {"void", "int", "float", "boolean", "short", "char", "byte", "long", "double"}
1913
1914def verify_nullability(clazz):
1915 """Catches missing nullability annotations"""
1916
1917 for f in clazz.fields:
1918 if f.value is not None and 'static' in f.split and 'final' in f.split:
1919 continue # Nullability of constants can be inferred.
1920 if f.typ not in PRIMITIVES and not has_nullability(f.annotations):
1921 error(clazz, f, "M12", "Field must be marked either @NonNull or @Nullable")
1922
1923 for c in clazz.ctors:
1924 verify_nullability_args(clazz, c)
1925
1926 for m in clazz.methods:
1927 if m.name == "writeToParcel" or m.name == "onReceive":
1928 continue # Parcelable.writeToParcel() and BroadcastReceiver.onReceive() are not yet annotated
1929
1930 if m.typ not in PRIMITIVES and not has_nullability(m.annotations):
1931 error(clazz, m, "M12", "Return value must be marked either @NonNull or @Nullable")
1932 verify_nullability_args(clazz, m)
1933
1934def verify_nullability_args(clazz, m):
1935 for i, arg in enumerate(m.detailed_args):
1936 if arg.type not in PRIMITIVES and not has_nullability(arg.annotations):
1937 error(clazz, m, "M12", "Argument %d must be marked either @NonNull or @Nullable" % (i+1,))
1938
1939def has_nullability(annotations):
1940 return "@NonNull" in annotations or "@Nullable" in annotations
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001941
1942def verify_singleton(clazz):
1943 """Catch singleton objects with constructors."""
1944
1945 singleton = False
1946 for m in clazz.methods:
1947 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1948 singleton = True
1949
1950 if singleton:
1951 for c in clazz.ctors:
1952 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1953
1954
1955
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001956def is_interesting(clazz):
1957 """Test if given class is interesting from an Android PoV."""
1958
1959 if clazz.pkg.name.startswith("java"): return False
1960 if clazz.pkg.name.startswith("junit"): return False
1961 if clazz.pkg.name.startswith("org.apache"): return False
1962 if clazz.pkg.name.startswith("org.xml"): return False
1963 if clazz.pkg.name.startswith("org.json"): return False
1964 if clazz.pkg.name.startswith("org.w3c"): return False
1965 if clazz.pkg.name.startswith("android.icu."): return False
1966 return True
1967
1968
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001969def examine_clazz(clazz):
1970 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001971
1972 notice(clazz)
1973
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001974 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001975
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001976 verify_constants(clazz)
1977 verify_enums(clazz)
1978 verify_class_names(clazz)
1979 verify_method_names(clazz)
1980 verify_callbacks(clazz)
1981 verify_listeners(clazz)
1982 verify_actions(clazz)
1983 verify_extras(clazz)
1984 verify_equals(clazz)
1985 verify_parcelable(clazz)
1986 verify_protected(clazz)
1987 verify_fields(clazz)
1988 verify_register(clazz)
1989 verify_sync(clazz)
1990 verify_intent_builder(clazz)
1991 verify_helper_classes(clazz)
1992 verify_builder(clazz)
1993 verify_aidl(clazz)
1994 verify_internal(clazz)
1995 verify_layering(clazz)
1996 verify_boolean(clazz)
1997 verify_collections(clazz)
Adrian Roos56ff7842019-02-28 12:45:00 +01001998 verify_uris(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001999 verify_flags(clazz)
2000 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002001 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002002 verify_bitset(clazz)
2003 verify_manager(clazz)
2004 verify_boxed(clazz)
2005 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07002006 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002007 verify_callback_handlers(clazz)
2008 verify_context_first(clazz)
2009 verify_listener_last(clazz)
2010 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07002011 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06002012 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07002013 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06002014 verify_runtime_exceptions(clazz)
2015 verify_error(clazz)
2016 verify_units(clazz)
2017 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04002018 verify_member_name_not_kotlin_keyword(clazz)
2019 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07002020 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07002021 verify_user_handle(clazz)
2022 verify_params(clazz)
2023 verify_services(clazz)
2024 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07002025 verify_icu(clazz)
2026 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06002027 verify_pfd(clazz)
2028 verify_numbers(clazz)
2029 verify_singleton(clazz)
Adrian Roos80545ef2019-02-27 16:45:00 +01002030 verify_nullability(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002031
2032
Adrian Roos038a0292018-12-19 17:11:21 +01002033def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002034 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002035 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002036 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002037 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01002038 _parse_stream(stream, examine_clazz, base_f=base_stream,
2039 in_classes_with_base=in_classes_with_base,
2040 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002041 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002042
2043
2044def examine_api(api):
2045 """Find all style issues in the given parsed API."""
2046 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07002047 failures = {}
2048 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002049 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002050 return failures
2051
2052
Jeff Sharkey037458a2014-09-04 15:46:20 -07002053def verify_compat(cur, prev):
2054 """Find any incompatible API changes between two levels."""
2055 global failures
2056
2057 def class_exists(api, test):
2058 return test.fullname in api
2059
2060 def ctor_exists(api, clazz, test):
2061 for m in clazz.ctors:
2062 if m.ident == test.ident: return True
2063 return False
2064
2065 def all_methods(api, clazz):
2066 methods = list(clazz.methods)
2067 if clazz.extends is not None:
2068 methods.extend(all_methods(api, api[clazz.extends]))
2069 return methods
2070
2071 def method_exists(api, clazz, test):
2072 methods = all_methods(api, clazz)
2073 for m in methods:
2074 if m.ident == test.ident: return True
2075 return False
2076
2077 def field_exists(api, clazz, test):
2078 for f in clazz.fields:
2079 if f.ident == test.ident: return True
2080 return False
2081
2082 failures = {}
2083 for key in sorted(prev.keys()):
2084 prev_clazz = prev[key]
2085
2086 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002087 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002088 continue
2089
2090 cur_clazz = cur[key]
2091
2092 for test in prev_clazz.ctors:
2093 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002094 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002095
2096 methods = all_methods(prev, prev_clazz)
2097 for test in methods:
2098 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002099 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002100
2101 for test in prev_clazz.fields:
2102 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002103 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002104
2105 return failures
2106
2107
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002108def show_deprecations_at_birth(cur, prev):
2109 """Show API deprecations at birth."""
2110 global failures
2111
2112 # Remove all existing things so we're left with new
2113 for prev_clazz in prev.values():
2114 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002115 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002116
2117 sigs = { i.ident: i for i in prev_clazz.ctors }
2118 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
2119 sigs = { i.ident: i for i in prev_clazz.methods }
2120 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
2121 sigs = { i.ident: i for i in prev_clazz.fields }
2122 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
2123
2124 # Forget about class entirely when nothing new
2125 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
2126 del cur[prev_clazz.fullname]
2127
2128 for clazz in cur.values():
Adrian Roosb787c182019-01-03 18:54:33 +01002129 if "deprecated" in clazz.split and not clazz.fullname in prev:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002130 error(clazz, None, None, "Found API deprecation at birth")
2131
2132 for i in clazz.ctors + clazz.methods + clazz.fields:
Adrian Roosb787c182019-01-03 18:54:33 +01002133 if "deprecated" in i.split:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002134 error(clazz, i, None, "Found API deprecation at birth")
2135
2136 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
2137 format(reset=True)))
2138 for f in sorted(failures):
2139 print failures[f]
2140 print
2141
2142
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002143def show_stats(cur, prev):
2144 """Show API stats."""
2145
2146 stats = collections.defaultdict(int)
2147 for cur_clazz in cur.values():
2148 if not is_interesting(cur_clazz): continue
2149
2150 if cur_clazz.fullname not in prev:
2151 stats['new_classes'] += 1
2152 stats['new_ctors'] += len(cur_clazz.ctors)
2153 stats['new_methods'] += len(cur_clazz.methods)
2154 stats['new_fields'] += len(cur_clazz.fields)
2155 else:
2156 prev_clazz = prev[cur_clazz.fullname]
2157
2158 sigs = { i.ident: i for i in prev_clazz.ctors }
2159 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
2160 sigs = { i.ident: i for i in prev_clazz.methods }
2161 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
2162 sigs = { i.ident: i for i in prev_clazz.fields }
2163 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
2164
2165 if ctors + methods + fields > 0:
2166 stats['extend_classes'] += 1
2167 stats['extend_ctors'] += ctors
2168 stats['extend_methods'] += methods
2169 stats['extend_fields'] += fields
2170
2171 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
2172 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
2173
2174
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002175if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002176 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
2177 patterns. It ignores lint messages from a previous API level, if provided.")
2178 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
2179 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
2180 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01002181 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
2182 help="The base current.txt to use when examining system-current.txt or"
2183 " test-current.txt")
2184 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
2185 help="The base previous.txt to use when examining system-previous.txt or"
2186 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002187 parser.add_argument("--no-color", action='store_const', const=True,
2188 help="Disable terminal colors")
2189 parser.add_argument("--allow-google", action='store_const', const=True,
2190 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002191 parser.add_argument("--show-noticed", action='store_const', const=True,
2192 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002193 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
2194 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002195 parser.add_argument("--show-stats", action='store_const', const=True,
2196 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002197 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002198
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002199 if args['no_color']:
2200 USE_COLOR = False
2201
2202 if args['allow_google']:
2203 ALLOW_GOOGLE = True
2204
2205 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002206 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002207 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002208 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002209
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002210 if args['show_deprecations_at_birth']:
2211 with current_file as f:
2212 cur = _parse_stream(f)
2213 with previous_file as f:
2214 prev = _parse_stream(f)
2215 show_deprecations_at_birth(cur, prev)
2216 sys.exit()
2217
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002218 if args['show_stats']:
2219 with current_file as f:
2220 cur = _parse_stream(f)
2221 with previous_file as f:
2222 prev = _parse_stream(f)
2223 show_stats(cur, prev)
2224 sys.exit()
2225
Adrian Roos038a0292018-12-19 17:11:21 +01002226 classes_with_base = []
2227
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002228 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002229 if base_current_file:
2230 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002231 cur_fail, cur_noticed = examine_stream(f, base_f,
2232 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002233 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002234 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
2235
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002236 if not previous_file is None:
2237 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002238 if base_previous_file:
2239 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002240 prev_fail, prev_noticed = examine_stream(f, base_f,
2241 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002242 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002243 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002244
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002245 # ignore errors from previous API level
2246 for p in prev_fail:
2247 if p in cur_fail:
2248 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002249
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002250 # ignore classes unchanged from previous API level
2251 for k, v in prev_noticed.iteritems():
2252 if k in cur_noticed and v == cur_noticed[k]:
2253 del cur_noticed[k]
2254
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002255 """
2256 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002257 # look for compatibility issues
2258 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002259
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002260 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2261 for f in sorted(compat_fail):
2262 print compat_fail[f]
2263 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002264 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002265
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002266 if args['show_noticed'] and len(cur_noticed) != 0:
2267 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2268 for f in sorted(cur_noticed.keys()):
2269 print f
2270 print
2271
Jason Monk53b2a732017-11-10 15:43:17 -05002272 if len(cur_fail) != 0:
2273 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2274 for f in sorted(cur_fail):
2275 print cur_fail[f]
2276 print
2277 sys.exit(77)