Merge "Apilint: Lint missing nullability annotations"

am: d6a886a500

Change-Id: I4aaec5b2341b73af5cc976d7f27a4e61fbc9c138
This commit is contained in:
Adrian Roos
2019-02-28 07:39:12 -08:00
committed by android-build-merger
2 changed files with 74 additions and 22 deletions

View File

@@ -79,6 +79,7 @@ class Field():
self.value = raw[3].strip(';"') self.value = raw[3].strip(';"')
else: else:
self.value = None self.value = None
self.annotations = []
self.ident = "-".join((self.typ, self.name, self.value or "")) self.ident = "-".join((self.typ, self.name, self.value or ""))
@@ -88,6 +89,18 @@ class Field():
def __repr__(self): def __repr__(self):
return self.raw return self.raw
class Argument(object):
__slots__ = ["type", "annotations", "name", "default"]
def __init__(self, type):
self.type = type
self.annotations = []
self.name = None
self.default = None
class Method(): class Method():
def __init__(self, clazz, line, raw, blame, sig_format = 1): def __init__(self, clazz, line, raw, blame, sig_format = 1):
self.clazz = clazz self.clazz = clazz
@@ -118,21 +131,24 @@ class Method():
self.name = raw[1] self.name = raw[1]
# parse args # parse args
self.args = [] self.detailed_args = []
for arg in re.split(",\s*", raw_args): for arg in re.split(",\s*", raw_args):
arg = re.split("\s", arg) arg = re.split("\s", arg)
# ignore annotations for now # ignore annotations for now
arg = [ a for a in arg if not a.startswith("@") ] arg = [ a for a in arg if not a.startswith("@") ]
if len(arg[0]) > 0: if len(arg[0]) > 0:
self.args.append(arg[0]) self.detailed_args.append(Argument(arg[0]))
# parse throws # parse throws
self.throws = [] self.throws = []
for throw in re.split(",\s*", raw_throws): for throw in re.split(",\s*", raw_throws):
self.throws.append(throw) self.throws.append(throw)
self.annotations = []
else: else:
raise ValueError("Unknown signature format: " + sig_format) raise ValueError("Unknown signature format: " + sig_format)
self.args = map(lambda a: a.type, self.detailed_args)
self.ident = "-".join((self.typ, self.name, "-".join(self.args))) self.ident = "-".join((self.typ, self.name, "-".join(self.args)))
def sig_matches(self, typ, name, args): def sig_matches(self, typ, name, args):
@@ -312,10 +328,10 @@ class V2LineParser(object):
method.split = [] method.split = []
kind = self.parse_one_of("ctor", "method") kind = self.parse_one_of("ctor", "method")
method.split.append(kind) method.split.append(kind)
annotations = self.parse_annotations() method.annotations = self.parse_annotations()
method.split.extend(self.parse_modifiers()) method.split.extend(self.parse_modifiers())
self.parse_matching_paren("<", ">") self.parse_matching_paren("<", ">")
if "@Deprecated" in annotations: if "@Deprecated" in method.annotations:
method.split.append("deprecated") method.split.append("deprecated")
if kind == "ctor": if kind == "ctor":
method.typ = "ctor" method.typ = "ctor"
@@ -325,7 +341,7 @@ class V2LineParser(object):
method.name = self.parse_name() method.name = self.parse_name()
method.split.append(method.name) method.split.append(method.name)
self.parse_token("(") self.parse_token("(")
method.args = self.parse_args() method.detailed_args = self.parse_args()
self.parse_token(")") self.parse_token(")")
method.throws = self.parse_throws() method.throws = self.parse_throws()
if "@interface" in method.clazz.split: if "@interface" in method.clazz.split:
@@ -360,8 +376,8 @@ class V2LineParser(object):
def parse_into_field(self, field): def parse_into_field(self, field):
kind = self.parse_one_of(*V2LineParser.FIELD_KINDS) kind = self.parse_one_of(*V2LineParser.FIELD_KINDS)
field.split = [kind] field.split = [kind]
annotations = self.parse_annotations() field.annotations = self.parse_annotations()
if "@Deprecated" in annotations: if "@Deprecated" in field.annotations:
field.split.append("deprecated") field.split.append("deprecated")
field.split.extend(self.parse_modifiers()) field.split.extend(self.parse_modifiers())
field.typ = self.parse_type() field.typ = self.parse_type()
@@ -488,15 +504,16 @@ class V2LineParser(object):
def parse_arg(self): def parse_arg(self):
self.parse_if("vararg") # kotlin vararg self.parse_if("vararg") # kotlin vararg
self.parse_annotations() annotations = self.parse_annotations()
type = self.parse_arg_type() arg = Argument(self.parse_arg_type())
arg.annotations = annotations
l = self.lookahead() l = self.lookahead()
if l != "," and l != ")": if l != "," and l != ")":
if self.lookahead() != '=': if self.lookahead() != '=':
self.parse_token() # kotlin argument name arg.name = self.parse_token() # kotlin argument name
if self.parse_if('='): # kotlin default value if self.parse_if('='): # kotlin default value
self.parse_expression() arg.default = self.parse_expression()
return type return arg
def parse_expression(self): def parse_expression(self):
while not self.lookahead() in [')', ',', ';']: while not self.lookahead() in [')', ',', ';']:
@@ -593,7 +610,7 @@ def _parse_stream_to_generator(f):
blame = None blame = None
sig_format = 1 sig_format = 1
re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$") re_blame = re.compile(r"^(\^?[a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
field_prefixes = map(lambda kind: " %s" % (kind,), V2LineParser.FIELD_KINDS) field_prefixes = map(lambda kind: " %s" % (kind,), V2LineParser.FIELD_KINDS)
def startsWithFieldPrefix(raw): def startsWithFieldPrefix(raw):
@@ -608,11 +625,13 @@ def _parse_stream_to_generator(f):
match = re_blame.match(raw) match = re_blame.match(raw)
if match is not None: if match is not None:
blame = match.groups()[0:2] blame = match.groups()[0:2]
if blame[0].startswith("^"): # Outside of blame range
blame = None
raw = match.groups()[2] raw = match.groups()[2]
else: else:
blame = None blame = None
if line == 1 and raw.startswith("// Signature format: "): if line == 1 and V2Tokenizer.SIGNATURE_PREFIX in raw:
sig_format_string = raw[len(V2Tokenizer.SIGNATURE_PREFIX):] sig_format_string = raw[len(V2Tokenizer.SIGNATURE_PREFIX):]
if sig_format_string in ["2.0", "3.0"]: if sig_format_string in ["2.0", "3.0"]:
sig_format = 2 sig_format = 2
@@ -1871,6 +1890,35 @@ def verify_numbers(clazz):
if arg in discouraged: if arg in discouraged:
warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead") warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
PRIMITIVES = {"void", "int", "float", "boolean", "short", "char", "byte", "long", "double"}
def verify_nullability(clazz):
"""Catches missing nullability annotations"""
for f in clazz.fields:
if f.value is not None and 'static' in f.split and 'final' in f.split:
continue # Nullability of constants can be inferred.
if f.typ not in PRIMITIVES and not has_nullability(f.annotations):
error(clazz, f, "M12", "Field must be marked either @NonNull or @Nullable")
for c in clazz.ctors:
verify_nullability_args(clazz, c)
for m in clazz.methods:
if m.name == "writeToParcel" or m.name == "onReceive":
continue # Parcelable.writeToParcel() and BroadcastReceiver.onReceive() are not yet annotated
if m.typ not in PRIMITIVES and not has_nullability(m.annotations):
error(clazz, m, "M12", "Return value must be marked either @NonNull or @Nullable")
verify_nullability_args(clazz, m)
def verify_nullability_args(clazz, m):
for i, arg in enumerate(m.detailed_args):
if arg.type not in PRIMITIVES and not has_nullability(arg.annotations):
error(clazz, m, "M12", "Argument %d must be marked either @NonNull or @Nullable" % (i+1,))
def has_nullability(annotations):
return "@NonNull" in annotations or "@Nullable" in annotations
def verify_singleton(clazz): def verify_singleton(clazz):
"""Catch singleton objects with constructors.""" """Catch singleton objects with constructors."""
@@ -1959,6 +2007,7 @@ def examine_clazz(clazz):
verify_pfd(clazz) verify_pfd(clazz)
verify_numbers(clazz) verify_numbers(clazz)
verify_singleton(clazz) verify_singleton(clazz)
verify_nullability(clazz)
def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None): def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):

View File

@@ -88,20 +88,22 @@ class UtilTests(unittest.TestCase):
faulty_current_txt = """ faulty_current_txt = """
// Signature format: 2.0
package android.app { package android.app {
public final class Activity { public final class Activity {
} }
public final class WallpaperColors implements android.os.Parcelable { public final class WallpaperColors implements android.os.Parcelable {
ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(@NonNull android.os.Parcel);
method public int describeContents(); method public int describeContents();
method public void writeToParcel(android.os.Parcel, int); method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; field @NonNull public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR;
} }
} }
""".split('\n') """.strip().split('\n')
ok_current_txt = """ ok_current_txt = """
// Signature format: 2.0
package android.app { package android.app {
public final class Activity { public final class Activity {
} }
@@ -109,19 +111,20 @@ package android.app {
public final class WallpaperColors implements android.os.Parcelable { public final class WallpaperColors implements android.os.Parcelable {
ctor public WallpaperColors(); ctor public WallpaperColors();
method public int describeContents(); method public int describeContents();
method public void writeToParcel(android.os.Parcel, int); method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; field @NonNull public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR;
} }
} }
""".split('\n') """.strip().split('\n')
system_current_txt = """ system_current_txt = """
// Signature format: 2.0
package android.app { package android.app {
public final class WallpaperColors implements android.os.Parcelable { public final class WallpaperColors implements android.os.Parcelable {
method public int getSomething(); method public int getSomething();
} }
} }
""".split('\n') """.strip().split('\n')