Commit 53b2dae3 authored by Clark DuVall's avatar Clark DuVall Committed by Commit Bot

Attempt to fix ClassLoader mismatch issues with isolated splits

It seems there is a bug in Android which will use a different
ClassLoader for components declared in a split than is returned by
Context.createContextForSplit().getClassLoader(). This fix attempts to
force the ClassLoader to be the same using AppComponentFactory. Note
that this fix only works on P+, but nearly all the reports have been
from P+ (with most from Q+). If this stops the crash on those versions,
we can see if another workaround can be found for O.

One of the main places this is seen is when loading feature modules,
since the implementation class is loaded with the ClassLoader from
ContextUtils.getApplicationContext(). Another place I believe has the
same root cause is when native code calls back into java. This also
uses the ClassLoader from ContextUtils.getApplicationContext(), and can
cause weird errors like globals not being set that were set in the other
ClassLoader. crbug.com/1142614 has some of those.

Bug: 1142589, 1142614, 1142588
Change-Id: Iddee0964ce057b0811833bdd0ca7c5da56fdefb8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2522511Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Commit-Queue: Clark DuVall <cduvall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#824938}
parent 1d3d6dd3
......@@ -2104,6 +2104,7 @@ android_library("base_module_java") {
"java/src/org/chromium/chrome/browser/ChromeBackupAgent.java",
"java/src/org/chromium/chrome/browser/base/MainDexApplicationImpl.java",
"java/src/org/chromium/chrome/browser/base/SplitChromeApplication.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatAppComponentFactory.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatApplication.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatBackupAgent.java",
"java/src/org/chromium/chrome/browser/base/SplitCompatCustomTabsService.java",
......
......@@ -178,6 +178,9 @@ by a child template that "extends" this file.
{% endif %}
android:networkSecurityConfig="@xml/network_security_config"
android:allowAudioPlaybackCapture="false"
{% if definitions_in_split %}
android:appComponentFactory="org.chromium.chrome.browser.base.SplitCompatAppComponentFactory"
{% endif %}
{% block extra_application_attributes %}{% endblock %}>
{% macro application_definitions() %}
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.base;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AppComponentFactory;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
/**
* There are some cases where the ClassLoader for components in the chrome split does not match the
* ClassLoader from the application Context. This can cause issues, such as ClassCastExceptions when
* trying to cast between the two ClassLoaders. This class attempts to workaround this bug by
* explicitly setting the activity's ClassLoader. See b/172602571 for more details.
*
* Note: this workaround is not needed for services, since they always uses the base module's
* ClassLoader, see b/169196314 for more details.
*/
@TargetApi(Build.VERSION_CODES.P)
public class SplitCompatAppComponentFactory extends AppComponentFactory {
private static final String TAG = "SplitCompat";
@Override
public Activity instantiateActivity(ClassLoader cl, String className, Intent intent)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return super.instantiateActivity(getComponentClassLoader(cl, className), className, intent);
}
@Override
public ContentProvider instantiateProvider(ClassLoader cl, String className)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return super.instantiateProvider(getComponentClassLoader(cl, className), className);
}
@Override
public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return super.instantiateReceiver(getComponentClassLoader(cl, className), className, intent);
}
private ClassLoader getComponentClassLoader(ClassLoader cl, String className) {
Context context = ContextUtils.getApplicationContext();
if (context == null) {
Log.e(TAG, "Unexpected null Context when instantiating component: %s", className);
return cl;
}
ClassLoader chromeClassLoader = context.getClassLoader();
if (!cl.equals(chromeClassLoader) && isComponentInChromeSplit(className)) {
Log.w(TAG, "Mismatched ClassLoaders between Application and component: %s", className);
return chromeClassLoader;
}
return cl;
}
private boolean isComponentInChromeSplit(String className) {
// First, try using this class's ClassLoader, which only has classes from the base module.
try {
Class.forName(className, false, getClass().getClassLoader());
return false;
} catch (ClassNotFoundException e) {
}
// Next, try using the chrome ClassLoader. If the class is found, it is in the chrome split.
try {
Class.forName(className, false, ContextUtils.getApplicationContext().getClassLoader());
return true;
} catch (ClassNotFoundException e) {
}
return false;
}
}
......@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.base;
import android.content.Context;
import org.chromium.base.BundleUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.annotations.IdentifierNameString;
/** Utils for compatibility with isolated splits. */
......@@ -31,6 +32,11 @@ public class SplitCompatUtils {
* Constructs a new instance of the given class name using the class loader from the context.
*/
public static Object newInstance(Context context, String className) {
// TODO(crbug.com/1142589): If this crash fix works we can remove the context arg from here.
Context appContext = ContextUtils.getApplicationContext();
if (appContext != null) {
context = appContext;
}
try {
return context.getClassLoader().loadClass(className).newInstance();
} catch (ReflectiveOperationException e) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment