Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 24bda1d6 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Bunch of new API lint rules.

Test: manual verification
Fixes: 34709091
Fixes: 36699437
Fixes: 36737455
Fixes: 36737419
Fixes: 37279778
Fixes: 37283667
Fixes: 37473581
Fixes: 37505566
Fixes: 37509300
Change-Id: Ie5dbcc2932313225e5cbc1f4aa6961e4db2f3d45
parent 4132f860
Loading
Loading
Loading
Loading
+138 −9
Original line number Diff line number Diff line
@@ -259,14 +259,19 @@ def error(clazz, detail, rule, msg):
def verify_constants(clazz):
    """All static final constants must be FOO_NAME style."""
    if re.match("android\.R\.[a-z]+", clazz.fullname): return
    if clazz.fullname.startswith("android.os.Build"): return
    if clazz.fullname == "android.system.OsConstants": return

    req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
    for f in clazz.fields:
        if "static" in f.split and "final" in f.split:
            if re.match("[A-Z0-9_]+", f.name) is None:
                error(clazz, f, "C2", "Constant field names must be FOO_NAME")
            elif f.typ != "java.lang.String":
            if f.typ != "java.lang.String":
                if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
                    warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
            if f.typ in req and f.value is None:
                error(clazz, f, None, "All constants must be defined at compile time")


def verify_enums(clazz):
@@ -352,6 +357,7 @@ def verify_actions(clazz):
        if f.value is None: continue
        if f.name.startswith("EXTRA_"): continue
        if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
        if "INTERACTION" in f.name: continue

        if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
            if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
@@ -447,10 +453,14 @@ def verify_fields(clazz):
        "android.app.Notification",
        "android.content.pm.ActivityInfo",
        "android.content.pm.ApplicationInfo",
        "android.content.pm.ComponentInfo",
        "android.content.pm.ResolveInfo",
        "android.content.pm.FeatureGroupInfo",
        "android.content.pm.InstrumentationInfo",
        "android.content.pm.PackageInfo",
        "android.content.pm.PackageItemInfo",
        "android.content.res.Configuration",
        "android.graphics.BitmapFactory.Options",
        "android.os.Message",
        "android.system.StructPollfd",
    ]
@@ -786,6 +796,10 @@ def verify_manager(clazz):
    for c in clazz.ctors:
        error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")

    for m in clazz.methods:
        if m.typ == clazz.fullname:
            error(clazz, m, None, "Managers must always be obtained from Context")


def verify_boxed(clazz):
    """Verifies that methods avoid boxed primitives."""
@@ -812,17 +826,19 @@ def verify_boxed(clazz):
def verify_static_utils(clazz):
    """Verifies that helper classes can't be constructed."""
    if clazz.fullname.startswith("android.opengl"): return
    if re.match("android\.R\.[a-z]+", clazz.fullname): return
    if clazz.fullname.startswith("android.R"): return

    if len(clazz.fields) > 0: return
    if len(clazz.methods) == 0: return
    # Only care about classes with default constructors
    if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
        test = []
        test.extend(clazz.fields)
        test.extend(clazz.methods)

    for m in clazz.methods:
        if "static" not in m.split:
        if len(test) == 0: return
        for t in test:
            if "static" not in t.split:
                return

    # At this point, we have no fields, and all methods are static
    if len(clazz.ctors) > 0:
        error(clazz, None, None, "Fully-static utility classes must not have constructor")


@@ -920,6 +936,9 @@ def verify_context_first(clazz):
        if len(m.args) > 1 and m.args[0] != "android.content.Context":
            if "android.content.Context" in m.args[1:]:
                error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
        if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
            if "android.content.ContentResolver" in m.args[1:]:
                error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")


def verify_listener_last(clazz):
@@ -1001,6 +1020,112 @@ def verify_abstract_inner(clazz):
            warn(clazz, None, None, "Abstract inner classes should be static to improve testability")


def verify_runtime_exceptions(clazz):
    """Verifies that runtime exceptions aren't listed in throws."""

    banned = [
        "java.lang.NullPointerException",
        "java.lang.ClassCastException",
        "java.lang.IndexOutOfBoundsException",
        "java.lang.reflect.UndeclaredThrowableException",
        "java.lang.reflect.MalformedParametersException",
        "java.lang.reflect.MalformedParameterizedTypeException",
        "java.lang.invoke.WrongMethodTypeException",
        "java.lang.EnumConstantNotPresentException",
        "java.lang.IllegalMonitorStateException",
        "java.lang.SecurityException",
        "java.lang.UnsupportedOperationException",
        "java.lang.annotation.AnnotationTypeMismatchException",
        "java.lang.annotation.IncompleteAnnotationException",
        "java.lang.TypeNotPresentException",
        "java.lang.IllegalStateException",
        "java.lang.ArithmeticException",
        "java.lang.IllegalArgumentException",
        "java.lang.ArrayStoreException",
        "java.lang.NegativeArraySizeException",
        "java.util.MissingResourceException",
        "java.util.EmptyStackException",
        "java.util.concurrent.CompletionException",
        "java.util.concurrent.RejectedExecutionException",
        "java.util.IllformedLocaleException",
        "java.util.ConcurrentModificationException",
        "java.util.NoSuchElementException",
        "java.io.UncheckedIOException",
        "java.time.DateTimeException",
        "java.security.ProviderException",
        "java.nio.BufferUnderflowException",
        "java.nio.BufferOverflowException",
    ]

    test = []
    test.extend(clazz.ctors)
    test.extend(clazz.methods)

    for t in test:
        if " throws " not in t.raw: continue
        throws = t.raw[t.raw.index(" throws "):]
        for b in banned:
            if b in throws:
                error(clazz, t, None, "Methods must not mention RuntimeException subclasses in throws clauses")


def verify_error(clazz):
    """Verifies that we always use Exception instead of Error."""
    if not clazz.extends: return
    if clazz.extends.endswith("Error"):
        error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
    if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
        error(clazz, None, None, "Exceptions must be named FooException")


def verify_units(clazz):
    """Verifies that we use consistent naming for units."""

    # If we find K, recommend replacing with V
    bad = {
        "Ns": "Nanos",
        "Ms": "Millis or Micros",
        "Sec": "Seconds", "Secs": "Seconds",
        "Hr": "Hours", "Hrs": "Hours",
        "Mo": "Months", "Mos": "Months",
        "Yr": "Years", "Yrs": "Years",
        "Byte": "Bytes", "Space": "Bytes",
    }

    for m in clazz.methods:
        if m.typ not in ["short","int","long"]: continue
        for k, v in bad.iteritems():
            if m.name.endswith(k):
                error(clazz, m, None, "Expected method name units to be " + v)
        if m.name.endswith("Nanos") or m.name.endswith("Micros"):
            warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
        if m.name.endswith("Seconds"):
            error(clazz, m, None, "Returned time values must be in milliseconds")

    for m in clazz.methods:
        typ = m.typ
        if typ == "void":
            if len(m.args) != 1: continue
            typ = m.args[0]

        if m.name.endswith("Fraction") and typ != "float":
            error(clazz, m, None, "Fractions must use floats")
        if m.name.endswith("Percentage") and typ != "int":
            error(clazz, m, None, "Percentage must use ints")


def verify_closable(clazz):
    """Verifies that classes are AutoClosable."""
    if "implements java.lang.AutoCloseable" in clazz.raw: return
    if "implements java.io.Closeable" in clazz.raw: return

    for m in clazz.methods:
        if len(m.args) > 0: continue
        if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
            warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
            return


def examine_clazz(clazz):
    """Find all style issues in the given class."""
    if clazz.pkg.name.startswith("java"): return
@@ -1048,6 +1173,10 @@ def examine_clazz(clazz):
    verify_files(clazz)
    verify_manager_list(clazz)
    verify_abstract_inner(clazz)
    verify_runtime_exceptions(clazz)
    verify_error(clazz)
    verify_units(clazz)
    verify_closable(clazz)


def examine_stream(stream):