Question

About the Title

"classes of the same package" means classes that share the same package access. Please pay attention to the fact that class a.b.c.Foo doesn't have package access to class a.b.Bar . Because the latter can't access to the former if the former's modifier is default.

Problem

If I split two classes in the same packages into two dex files, even though I load them correctly, I will also get some error while running, the logcat likes:

I/dalvikvm( 6498): DexOpt: illegal method access (call Lcom/fish47/multidex/Foo;.isWholeWord (Lcom/fish47/multidex/Foo;)Z from Lcom/fish47/multidex/TestMatchWord;)
I/dalvikvm( 6498): Could not find method com.fish47.multidex.core.Foo.isWholeWord, referenced from method com.fish47.multidex.core.TestMatchWord.test_english
W/dalvikvm( 6498): VFY: unable to resolve virtual method 758: Lcom/fish47/multidex/Foo;.isWholeWord (Lcom/fish47/multidex/Foo;)Z


Conjecture

That's code which pop out the error message below:
vm/analysis/Optimize.c ==> line: 697 - 714

/* access allowed? */
tweakLoader(referrer, resMethod->clazz);
bool allowed = dvmCheckMethodAccess(referrer, resMethod);
untweakLoader(referrer, resMethod->clazz);
if (!allowed) {
    IF_LOGI() {
        char* desc = dexProtoCopyMethodDescriptor(&resMethod->prototype);
        LOGI("DexOpt: illegal method access (call %s.%s %s from %s)\n",
            resMethod->clazz->descriptor, resMethod->name, desc,
            referrer->descriptor);
        free(desc);
    }
    if (pFailure != NULL)
        *pFailure = VERIFY_ERROR_ACCESS_METHOD;
    return NULL;
}


Pay attention to the value of resClass->classLoader . If the two classes aren't from the same dex file, its value will be set 0xdead3333.
vm/analysis/Optimize.c ==> line: 285 - 299

static void tweakLoader(ClassObject* referrer, ClassObject* resClass)
{
    if (!gDvm.optimizing)
        return;
    assert(referrer->classLoader == NULL);
    assert(resClass->classLoader == NULL);

    if (!gDvm.optimizingBootstrapClass) {
        /* class loader for an array class comes from element type */
        if (dvmIsArrayClass(resClass))
            resClass = resClass->elementClass;
        if (referrer->pDvmDex != resClass->pDvmDex)
            resClass->classLoader = (Object*) 0xdead3333;
    }
}


This is a trick, it lets checkAccess(...) method return false at last, if the two classes are in the same package, accessible to each other but not to public.
vm/oo/AccessCheck.c ==> line: 88 - 116

static bool checkAccess(const ClassObject* accessFrom,
    const ClassObject* accessTo, u4 accessFlags)
{
    /* quick accept for public access */
    if (accessFlags & ACC_PUBLIC)
        return true;

    /* quick accept for access from same class */
    if (accessFrom == accessTo)
        return true;

    /* quick reject for private access from another class */
    if (accessFlags & ACC_PRIVATE)
        return false;

    /*
     * Semi-quick test for protected access from a sub-class, which may or
     * may not be in the same package.
     */
    if (accessFlags & ACC_PROTECTED)
        if (dvmIsSubClass(accessFrom, accessTo))
            return true;

    /*
     * Allow protected and private access from other classes in the same
     * package.
     */
    return dvmInSamePackage(accessFrom, accessTo);
}

vm/oo/AccessCheck.c ==> line: 39 - 83

bool dvmInSamePackage(const ClassObject* class1, const ClassObject* class2)
{
    ...

    /* class loaders must match */
    if (class1->classLoader != class2->classLoader)
        return false;

    ...
} 
Was it helpful?

Solution

This is a somewhat strange area, owing largely to the "pre-verification" and optimization performed by dexopt. For background, you should read the comments at the start of oo/Class.cpp (lines 39-153).

(Note: the files were changed from ".c" to ".cpp" back in ICS. You should probably be examining current sources, though in practice little has changed here in the last few years.)

Generally speaking, two classes in the same package in different DEX files would be able to access each other with package scope so long as both DEX files are loaded by the same class loader. That's what the checks in AccessCheck.cpp enforce.

What you're looking at in Optimize.cpp is a parallel implementation of the resolver -- dvmOptResolveClass vs. dvmResolveClass -- that is used during verification and optimization. It will tweak the class loader as you noted, but only if it's running inside dexopt (that's what the check against !gDvm.optimizing means). If it's inside a normally-executing VM instance, the loader is not tweaked during the checks.

When running as part of dexopt, the code in Optimize.cpp is either verifying+optimizing the bootstrap classes, or verifying+optimizing a single non-bootstrap DEX file. Either way, all of the DEX files are loaded through the bootstrap loader, because the VM isn't really running and it's the only way to load classes. (The point of dexopt is to verify as many classes as possible at build or install time so we don't have to do it at app startup time. Read more about dexopt here.)

The code in tweakLoader says: if I'm in dexopt, and I'm not optimizing an actual bootstrap DEX file (e.g. framework.jar), then I need to make sure that the package-scope checks assume that the classes in the current DEX file are not being loaded by the bootstrap class loader.

For example, I could create a class called java.lang.Stuff in my app. In dexopt, because everything is loaded by a single loader, it would be able to touch package-private stuff in other java.lang classes if we didn't tweak the loader. When the app is actually run, the java.lang classes come from the bootstrap loader and the Stuff class comes from the app loader, so those accesses should be forbidden.

So that's what the code does. As far as your specific issue goes, I'd expect the calls to work so long as the same loader is used to load both DEX files. If one DEX is loaded by the app framework and the other by a custom DexClassLoader then I wouldn't expect it to work.

One additional note: the errors you pasted in mention both com.fish47.multidex.Foo and com.fish47.multidex.core.Foo, which aren't the same package. I don't know if that's related. Also, if there are additional VFY messages it's good to include those, even if they're a bit unintelligible. And for anything of this nature it's also important to specify what version of Android you're using -- it hasn't changed in a while, but if you go back far enough it's very different.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top