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

Commit 57b99960 authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge "Bunch of new API lint rules." into oc-dev

parents 3fc17691 24bda1d6
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):