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

Commit 80010e6d authored by Lee Shombert's avatar Lee Shombert
Browse files

Validate Computer functions

Bug: 186654390

Add a validation function to perform some basic checks on the
structure of functions that are part of the Computer interface. The
checks are:

1. Every function in Computer must have an @Live annotation.  The
   annotation indicates if the function is locked or not.

2. If function is locked then there must be an override in
   ComputerLocked.

3. If the function is not locked then there may not be an override in
  ComputerLocked.

4. Every function in ComputerLocked must be an override of a function
   declared in Computer.

Test: atest
 * PackageManagerServiceTests#testComputerStructure

The test is run once on the candidate image - no failures  are
reported.  The test is then run on modified builds, exercising the
following five cases, to verify that errors are properly detected.

Manual testing with the following three cases:

1. A function in Computer that does not have an @LiveImplementation
   annotation.

2. A function in Computer that has an invalid
   LiveImplementation.override annotation.

3. A locked function that is not overridden in ComputerLocked.

4. A not-locked function that is is overridden in ComputerLocked.

5. A function that is defined for the first time in ComputerLocked.

Change-Id: Ic34aac67fe40aa4fec2d343fe4babdf1c565ce85
parent 6a6b9eff
Loading
Loading
Loading
Loading
+212 −114

File changed.

Preview size limit exceeded, changes collapsed.

+179 −0
Original line number Diff line number Diff line
@@ -18,7 +18,11 @@ package com.android.server.pm;

import static com.google.common.truth.Truth.assertWithMessage;

import static org.junit.Assert.fail;

import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isPrivate;
import static java.lang.reflect.Modifier.isProtected;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;

@@ -44,9 +48,12 @@ import org.junit.runner.RunWith;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;

@@ -393,6 +400,178 @@ public class PackageManagerServiceTest {
        Assert.assertEquals(3600000003L, multiPackage[1].timeouts.maxPendingTimeUs);
    }

    // Report an error from the Computer structure validation test.
    private void flag(String name, String msg) {
        fail(name + " " + msg);
    }

    // Return a string that identifies a Method.  This is not very efficient but it is not
    // called very often.
    private String displayName(Method m) {
        String r = m.getName();
        String p = Arrays.toString(m.getGenericParameterTypes())
                   .replaceAll("([a-zA-Z0-9]+\\.)+", "")
                   .replace("class ", "")
                   .replaceAll("^\\[", "(")
                   .replaceAll("\\]$", ")");
        return r + p;
    }

    // Match a method to an array of Methods.  Matching is on method signature: name and
    // parameter types.  If a method in the declared array matches, return it.  Otherwise
    // return null.
    private Method matchMethod(Method m, Method[] declared) {
        String n = m.getName();
        Type[] t = m.getGenericParameterTypes();
        for (int i = 0; i < declared.length; i++) {
            Method l = declared[i];
            if (l != null && l.getName().equals(n)
                    && Arrays.equals(l.getGenericParameterTypes(), t)) {
                Method result = l;
                // Set the method to null since it has been visited already.
                declared[i] = null;
                return result;
            }
        }
        return null;
    }

    // Return the boolean locked value.  A null return means the annotation was not
    // found.  This method will fail if the annotation is found but is not one of the
    // known constants.
    private Boolean getOverride(Method m) {
        final String name = "Computer." + displayName(m);
        final PackageManagerService.Computer.LiveImplementation annotation =
                m.getAnnotation(PackageManagerService.Computer.LiveImplementation.class);
        if (annotation == null) {
            return null;
        }
        final int override = annotation.override();
        if (override == PackageManagerService.Computer.LiveImplementation.MANDATORY) {
            return true;
        } else if (override == PackageManagerService.Computer.LiveImplementation.NOT_ALLOWED) {
            return false;
        } else {
            flag(name, "invalid Live value: " + override);
            return null;
        }
    }

    @Test
    public void testComputerStructure() {
        // Verify that Copmuter methods are properly annotated and that ComputerLocked is
        // properly populated per annotations.
        // Call PackageManagerService.validateComputer();
        Class base = PackageManagerService.Computer.class;

        HashMap<Method, Boolean> methodType = new HashMap<>();

        // Verify that all Computer methods are annotated and that the annotation
        // parameter locked() is valid.
        for (Method m : base.getDeclaredMethods()) {
            final String name = "Computer." + displayName(m);
            Boolean override = getOverride(m);
            if (override == null) {
                flag(name, "missing required Live annotation");
            }
            methodType.put(m, override);
        }

        Class coreClass = PackageManagerService.ComputerEngine.class;
        final Method[] coreMethods = coreClass.getDeclaredMethods();

        // Examine every method in the core.  If it inherits from a base method it must be
        // "public final" if the base is NOT_ALLOWED or "public" if the base is MANDATORY.
        // If the core method does not inherit from the base then it must be either
        // private or protected.
        for (Method m : base.getDeclaredMethods()) {
            String name = "Computer." + displayName(m);
            final boolean locked = methodType.get(m);
            final Method core = matchMethod(m, coreMethods);
            if (core == null) {
                flag(name, "not overridden in ComputerEngine");
                continue;
            }
            name = "ComputerEngine." + displayName(m);
            final int modifiers = core.getModifiers();
            if (!locked) {
                if (!isPublic(modifiers)) {
                    flag(name, "is not public");
                }
                if (!isFinal(modifiers)) {
                    flag(name, "is not final");
                }
            }
        }
        // Any methods left in the coreMethods array must be private or protected.
        // Protected methods must be overridden (and final) in the live list.
        Method[] coreHelpers = new Method[coreMethods.length];
        int coreIndex = 0;
        for (Method m : coreMethods) {
            if (m != null) {
                final String name = "ComputerEngine." + displayName(m);
                final int modifiers = m.getModifiers();
                if (isPrivate(modifiers)) {
                    // Okay
                } else if (isProtected(modifiers)) {
                    coreHelpers[coreIndex++] = m;
                } else {
                    flag(name, "is neither private nor protected");
                }
            }
        }

        Class liveClass = PackageManagerService.ComputerLocked.class;
        final Method[] liveMethods = liveClass.getDeclaredMethods();

        // Examine every method in the live list.  Every method must be final and must
        // inherit either from base or core.  If the method inherits from a base method
        // then the base must be MANDATORY.
        for (Method m : base.getDeclaredMethods()) {
            String name = "Computer." + displayName(m);
            final boolean locked = methodType.get(m);
            final Method live = matchMethod(m, liveMethods);
            if (live == null) {
                if (locked) {
                    flag(name, "not overridden in ComputerLocked");
                }
                continue;
            }
            if (!locked) {
                flag(name, "improperly overridden in ComputerLocked");
                continue;
            }

            name = "ComputerLocked." + displayName(m);
            final int modifiers = live.getModifiers();
            if (!locked) {
                if (!isPublic(modifiers)) {
                    flag(name, "is not public");
                }
                if (!isFinal(modifiers)) {
                    flag(name, "is not final");
                }
            }
        }
        for (Method m : coreHelpers) {
            if (m == null) {
                continue;
            }
            String name = "ComputerLocked." + displayName(m);
            final Method live = matchMethod(m, liveMethods);
            if (live == null) {
                flag(name, "is not overridden in ComputerLocked");
                continue;
            }
        }
        for (Method m : liveMethods) {
            if (m != null) {
                String name = "ComputerLocked." + displayName(m);
                flag(name, "illegal local method");
            }
        }
    }

    private static PerPackageReadTimeouts[] getPerPackageReadTimeouts(String knownDigestersList) {
        final String defaultTimeouts = "3600000001:3600000002:3600000003";
        List<PerPackageReadTimeouts> result = PerPackageReadTimeouts.parseDigestersList(