Remove framework support for compiled views
This feature did not ship and is being removed. The performance benefits were unclear and the additional complextiy was non-trivial. Remove associated support in the framework. Bug: 158121974 Test: m Change-Id: I580f7616fe517ad8ef3126d1635fe978d2a1c322
This commit is contained in:
parent
c8f659f266
commit
72fd8769f9
@ -3493,10 +3493,6 @@ package android.view {
|
||||
method public boolean isSystemGroup();
|
||||
}
|
||||
|
||||
public abstract class LayoutInflater {
|
||||
method public void setPrecompiledLayoutsEnabledForTesting(boolean);
|
||||
}
|
||||
|
||||
public final class MotionEvent extends android.view.InputEvent implements android.os.Parcelable {
|
||||
method public int getDisplayId();
|
||||
method public void setActionButton(int);
|
||||
|
@ -20,11 +20,9 @@ import android.annotation.LayoutRes;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemService;
|
||||
import android.annotation.TestApi;
|
||||
import android.annotation.UiContext;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.content.res.XmlResourceParser;
|
||||
@ -42,15 +40,11 @@ import android.widget.FrameLayout;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
import dalvik.system.PathClassLoader;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
@ -82,12 +76,6 @@ public abstract class LayoutInflater {
|
||||
private static final String TAG = LayoutInflater.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String COMPILED_VIEW_DEX_FILE_NAME = "/compiled_view.dex";
|
||||
/**
|
||||
* Whether or not we use the precompiled layout.
|
||||
*/
|
||||
private static final String USE_PRECOMPILED_LAYOUT = "view.precompiled_layout_enabled";
|
||||
|
||||
/** Empty stack trace used to avoid log spam in re-throw exceptions. */
|
||||
private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
|
||||
|
||||
@ -116,13 +104,6 @@ public abstract class LayoutInflater {
|
||||
private Factory2 mPrivateFactory;
|
||||
private Filter mFilter;
|
||||
|
||||
// Indicates whether we should try to inflate layouts using a precompiled layout instead of
|
||||
// inflating from the XML resource.
|
||||
private boolean mUseCompiledView;
|
||||
// This variable holds the classloader that will be used to look for precompiled layouts. The
|
||||
// The classloader includes the generated compiled_view.dex file.
|
||||
private ClassLoader mPrecompiledClassLoader;
|
||||
|
||||
/**
|
||||
* This is not a public API. Two APIs are now available to alleviate the need to access
|
||||
* this directly: {@link #createView(Context, String, String, AttributeSet)} and
|
||||
@ -259,7 +240,6 @@ public abstract class LayoutInflater {
|
||||
protected LayoutInflater(Context context) {
|
||||
StrictMode.assertConfigurationContext(context, "LayoutInflater");
|
||||
mContext = context;
|
||||
initPrecompiledViews();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -277,7 +257,6 @@ public abstract class LayoutInflater {
|
||||
mFactory2 = original.mFactory2;
|
||||
mPrivateFactory = original.mPrivateFactory;
|
||||
setFilter(original.mFilter);
|
||||
initPrecompiledViews();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -419,57 +398,6 @@ public abstract class LayoutInflater {
|
||||
}
|
||||
}
|
||||
|
||||
private void initPrecompiledViews() {
|
||||
// Precompiled layouts are not supported in this release.
|
||||
boolean enabled = false;
|
||||
initPrecompiledViews(enabled);
|
||||
}
|
||||
|
||||
private void initPrecompiledViews(boolean enablePrecompiledViews) {
|
||||
mUseCompiledView = enablePrecompiledViews;
|
||||
|
||||
if (!mUseCompiledView) {
|
||||
mPrecompiledClassLoader = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the application allows code generation
|
||||
ApplicationInfo appInfo = mContext.getApplicationInfo();
|
||||
if (appInfo.isEmbeddedDexUsed() || appInfo.isPrivilegedApp()) {
|
||||
mUseCompiledView = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to load the precompiled layout file.
|
||||
try {
|
||||
mPrecompiledClassLoader = mContext.getClassLoader();
|
||||
String dexFile = mContext.getCodeCacheDir() + COMPILED_VIEW_DEX_FILE_NAME;
|
||||
if (new File(dexFile).exists()) {
|
||||
mPrecompiledClassLoader = new PathClassLoader(dexFile, mPrecompiledClassLoader);
|
||||
} else {
|
||||
// If the precompiled layout file doesn't exist, then disable precompiled
|
||||
// layouts.
|
||||
mUseCompiledView = false;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
Log.e(TAG, "Failed to initialized precompiled views layouts", e);
|
||||
}
|
||||
mUseCompiledView = false;
|
||||
}
|
||||
if (!mUseCompiledView) {
|
||||
mPrecompiledClassLoader = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide for use by CTS tests
|
||||
*/
|
||||
@TestApi
|
||||
public void setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts) {
|
||||
initPrecompiledViews(enablePrecompiledLayouts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate a new view hierarchy from the specified xml resource. Throws
|
||||
* {@link InflateException} if there is an error.
|
||||
@ -529,10 +457,6 @@ public abstract class LayoutInflater {
|
||||
+ Integer.toHexString(resource) + ")");
|
||||
}
|
||||
|
||||
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
|
||||
if (view != null) {
|
||||
return view;
|
||||
}
|
||||
XmlResourceParser parser = res.getLayout(resource);
|
||||
try {
|
||||
return inflate(parser, root, attachToRoot);
|
||||
@ -541,54 +465,6 @@ public abstract class LayoutInflater {
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable
|
||||
View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
|
||||
boolean attachToRoot) {
|
||||
if (!mUseCompiledView) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");
|
||||
|
||||
// Try to inflate using a precompiled layout.
|
||||
String pkg = res.getResourcePackageName(resource);
|
||||
String layout = res.getResourceEntryName(resource);
|
||||
|
||||
try {
|
||||
Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
|
||||
Method inflater = clazz.getMethod(layout, Context.class, int.class);
|
||||
View view = (View) inflater.invoke(null, mContext, resource);
|
||||
|
||||
if (view != null && root != null) {
|
||||
// We were able to use the precompiled inflater, but now we need to do some work to
|
||||
// attach the view to the root correctly.
|
||||
XmlResourceParser parser = res.getLayout(resource);
|
||||
try {
|
||||
AttributeSet attrs = Xml.asAttributeSet(parser);
|
||||
advanceToRootNode(parser);
|
||||
ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
|
||||
|
||||
if (attachToRoot) {
|
||||
root.addView(view, params);
|
||||
} else {
|
||||
view.setLayoutParams(params);
|
||||
}
|
||||
} finally {
|
||||
parser.close();
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
Log.e(TAG, "Failed to use precompiled view", e);
|
||||
}
|
||||
} finally {
|
||||
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the given parser to the first START_TAG. Throws InflateException if no start tag is
|
||||
* found.
|
||||
@ -1050,7 +926,7 @@ public abstract class LayoutInflater {
|
||||
* of the general view creation logic, and thus may return {@code null} for some tags. This
|
||||
* method is used by {@link LayoutInflater#inflate} in creating {@code View} objects.
|
||||
*
|
||||
* @hide for use by precompiled layouts.
|
||||
* @hide originally for internal use by precompiled layouts, which have since been removed.
|
||||
*
|
||||
* @param parent the parent view, used to inflate layout params
|
||||
* @param name the name of the XML tag used to define the view
|
||||
@ -1217,85 +1093,80 @@ public abstract class LayoutInflater {
|
||||
+ "reference. The layout ID " + value + " is not valid.");
|
||||
}
|
||||
|
||||
final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
|
||||
(ViewGroup) parent, /*attachToRoot=*/true);
|
||||
if (precompiled == null) {
|
||||
final XmlResourceParser childParser = context.getResources().getLayout(layout);
|
||||
final XmlResourceParser childParser = context.getResources().getLayout(layout);
|
||||
try {
|
||||
final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
|
||||
|
||||
try {
|
||||
final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
|
||||
|
||||
while ((type = childParser.next()) != XmlPullParser.START_TAG &&
|
||||
type != XmlPullParser.END_DOCUMENT) {
|
||||
// Empty.
|
||||
}
|
||||
|
||||
if (type != XmlPullParser.START_TAG) {
|
||||
throw new InflateException(getParserStateDescription(context, childAttrs)
|
||||
+ ": No start tag found!");
|
||||
}
|
||||
|
||||
final String childName = childParser.getName();
|
||||
|
||||
if (TAG_MERGE.equals(childName)) {
|
||||
// The <merge> tag doesn't support android:theme, so
|
||||
// nothing special to do here.
|
||||
rInflate(childParser, parent, context, childAttrs, false);
|
||||
} else {
|
||||
final View view = createViewFromTag(parent, childName,
|
||||
context, childAttrs, hasThemeOverride);
|
||||
final ViewGroup group = (ViewGroup) parent;
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.Include);
|
||||
final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
|
||||
final int visibility = a.getInt(R.styleable.Include_visibility, -1);
|
||||
a.recycle();
|
||||
|
||||
// We try to load the layout params set in the <include /> tag.
|
||||
// If the parent can't generate layout params (ex. missing width
|
||||
// or height for the framework ViewGroups, though this is not
|
||||
// necessarily true of all ViewGroups) then we expect it to throw
|
||||
// a runtime exception.
|
||||
// We catch this exception and set localParams accordingly: true
|
||||
// means we successfully loaded layout params from the <include>
|
||||
// tag, false means we need to rely on the included layout params.
|
||||
ViewGroup.LayoutParams params = null;
|
||||
try {
|
||||
params = group.generateLayoutParams(attrs);
|
||||
} catch (RuntimeException e) {
|
||||
// Ignore, just fail over to child attrs.
|
||||
}
|
||||
if (params == null) {
|
||||
params = group.generateLayoutParams(childAttrs);
|
||||
}
|
||||
view.setLayoutParams(params);
|
||||
|
||||
// Inflate all children.
|
||||
rInflateChildren(childParser, view, childAttrs, true);
|
||||
|
||||
if (id != View.NO_ID) {
|
||||
view.setId(id);
|
||||
}
|
||||
|
||||
switch (visibility) {
|
||||
case 0:
|
||||
view.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
case 1:
|
||||
view.setVisibility(View.INVISIBLE);
|
||||
break;
|
||||
case 2:
|
||||
view.setVisibility(View.GONE);
|
||||
break;
|
||||
}
|
||||
|
||||
group.addView(view);
|
||||
}
|
||||
} finally {
|
||||
childParser.close();
|
||||
while ((type = childParser.next()) != XmlPullParser.START_TAG
|
||||
&& type != XmlPullParser.END_DOCUMENT) {
|
||||
// Empty.
|
||||
}
|
||||
|
||||
if (type != XmlPullParser.START_TAG) {
|
||||
throw new InflateException(getParserStateDescription(context, childAttrs)
|
||||
+ ": No start tag found!");
|
||||
}
|
||||
|
||||
final String childName = childParser.getName();
|
||||
|
||||
if (TAG_MERGE.equals(childName)) {
|
||||
// The <merge> tag doesn't support android:theme, so
|
||||
// nothing special to do here.
|
||||
rInflate(childParser, parent, context, childAttrs, false);
|
||||
} else {
|
||||
final View view =
|
||||
createViewFromTag(parent, childName, context, childAttrs, hasThemeOverride);
|
||||
final ViewGroup group = (ViewGroup) parent;
|
||||
|
||||
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Include);
|
||||
final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
|
||||
final int visibility = a.getInt(R.styleable.Include_visibility, -1);
|
||||
a.recycle();
|
||||
|
||||
// We try to load the layout params set in the <include /> tag.
|
||||
// If the parent can't generate layout params (ex. missing width
|
||||
// or height for the framework ViewGroups, though this is not
|
||||
// necessarily true of all ViewGroups) then we expect it to throw
|
||||
// a runtime exception.
|
||||
// We catch this exception and set localParams accordingly: true
|
||||
// means we successfully loaded layout params from the <include>
|
||||
// tag, false means we need to rely on the included layout params.
|
||||
ViewGroup.LayoutParams params = null;
|
||||
try {
|
||||
params = group.generateLayoutParams(attrs);
|
||||
} catch (RuntimeException e) {
|
||||
// Ignore, just fail over to child attrs.
|
||||
}
|
||||
if (params == null) {
|
||||
params = group.generateLayoutParams(childAttrs);
|
||||
}
|
||||
view.setLayoutParams(params);
|
||||
|
||||
// Inflate all children.
|
||||
rInflateChildren(childParser, view, childAttrs, true);
|
||||
|
||||
if (id != View.NO_ID) {
|
||||
view.setId(id);
|
||||
}
|
||||
|
||||
switch (visibility) {
|
||||
case 0:
|
||||
view.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
case 1:
|
||||
view.setVisibility(View.INVISIBLE);
|
||||
break;
|
||||
case 2:
|
||||
view.setVisibility(View.GONE);
|
||||
break;
|
||||
}
|
||||
|
||||
group.addView(view);
|
||||
}
|
||||
} finally {
|
||||
childParser.close();
|
||||
}
|
||||
|
||||
LayoutInflater.consumeChildElements(parser);
|
||||
}
|
||||
|
||||
|
@ -223,11 +223,6 @@ public final class DexOptHelper {
|
||||
pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
|
||||
}
|
||||
|
||||
// TODO(b/251903639): Do this when ART Service is used, or remove it from here.
|
||||
if (SystemProperties.getBoolean(mPm.PRECOMPILE_LAYOUTS, false)) {
|
||||
mPm.mArtManagerService.compileLayouts(packageState, pkg);
|
||||
}
|
||||
|
||||
int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
|
||||
|
||||
String filter = getCompilerFilterForReason(pkgCompilationReason);
|
||||
|
@ -59,7 +59,6 @@ import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY;
|
||||
import static com.android.server.pm.PackageManagerService.MIN_INSTALLABLE_TARGET_SDK;
|
||||
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
|
||||
import static com.android.server.pm.PackageManagerService.POST_INSTALL;
|
||||
import static com.android.server.pm.PackageManagerService.PRECOMPILE_LAYOUTS;
|
||||
import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
|
||||
import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX;
|
||||
import static com.android.server.pm.PackageManagerService.SCAN_AS_FACTORY;
|
||||
@ -135,7 +134,6 @@ import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SELinux;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.Trace;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
@ -167,7 +165,6 @@ import com.android.server.pm.Installer.LegacyDexoptDisabledException;
|
||||
import com.android.server.pm.dex.ArtManagerService;
|
||||
import com.android.server.pm.dex.DexManager;
|
||||
import com.android.server.pm.dex.DexoptOptions;
|
||||
import com.android.server.pm.dex.ViewCompiler;
|
||||
import com.android.server.pm.parsing.PackageCacher;
|
||||
import com.android.server.pm.parsing.PackageParser2;
|
||||
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
|
||||
@ -222,7 +219,6 @@ final class InstallPackageHelper {
|
||||
private final Context mContext;
|
||||
private final PackageDexOptimizer mPackageDexOptimizer;
|
||||
private final PackageAbiHelper mPackageAbiHelper;
|
||||
private final ViewCompiler mViewCompiler;
|
||||
private final SharedLibrariesImpl mSharedLibraries;
|
||||
private final PackageManagerServiceInjector mInjector;
|
||||
private final UpdateOwnershipHelper mUpdateOwnershipHelper;
|
||||
@ -246,7 +242,6 @@ final class InstallPackageHelper {
|
||||
mContext = pm.mInjector.getContext();
|
||||
mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer();
|
||||
mPackageAbiHelper = pm.mInjector.getAbiHelper();
|
||||
mViewCompiler = pm.mInjector.getViewCompiler();
|
||||
mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
|
||||
mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
|
||||
}
|
||||
@ -2538,13 +2533,6 @@ final class InstallPackageHelper {
|
||||
&& !isApex;
|
||||
|
||||
if (performDexopt) {
|
||||
// Compile the layout resources.
|
||||
if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
|
||||
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts");
|
||||
mViewCompiler.compileLayouts(ps, pkg.getBaseApkPath());
|
||||
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
|
||||
}
|
||||
|
||||
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
|
||||
|
||||
// This mirrors logic from commitReconciledScanResultLocked, where the library files
|
||||
|
@ -1128,14 +1128,6 @@ public class Installer extends SystemService {
|
||||
throw new InstallerException("Invalid instruction set: " + instructionSet);
|
||||
}
|
||||
|
||||
public boolean compileLayouts(String apkPath, String packageName, String outDexFile, int uid) {
|
||||
try {
|
||||
return mInstalld.compileLayouts(apkPath, packageName, outDexFile, uid);
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visibility of the optimized artifacts.
|
||||
*
|
||||
|
@ -213,7 +213,6 @@ import com.android.server.pm.dex.ArtManagerService;
|
||||
import com.android.server.pm.dex.ArtUtils;
|
||||
import com.android.server.pm.dex.DexManager;
|
||||
import com.android.server.pm.dex.DynamicCodeLogger;
|
||||
import com.android.server.pm.dex.ViewCompiler;
|
||||
import com.android.server.pm.local.PackageManagerLocalImpl;
|
||||
import com.android.server.pm.parsing.PackageInfoUtils;
|
||||
import com.android.server.pm.parsing.PackageParser2;
|
||||
@ -810,8 +809,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
|
||||
private final DexManager mDexManager;
|
||||
private final DynamicCodeLogger mDynamicCodeLogger;
|
||||
|
||||
final ViewCompiler mViewCompiler;
|
||||
|
||||
private final AtomicInteger mNextMoveId = new AtomicInteger();
|
||||
final MovePackageHelper.MoveCallbacks mMoveCallbacks;
|
||||
|
||||
@ -1669,7 +1666,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
|
||||
(i, pm) -> new ArtManagerService(i.getContext(), i.getInstaller(),
|
||||
i.getInstallLock()),
|
||||
(i, pm) -> ApexManager.getInstance(),
|
||||
(i, pm) -> new ViewCompiler(i.getInstallLock(), i.getInstaller()),
|
||||
(i, pm) -> (IncrementalManager)
|
||||
i.getContext().getSystemService(Context.INCREMENTAL_SERVICE),
|
||||
(i, pm) -> new DefaultAppProvider(() -> context.getSystemService(RoleManager.class),
|
||||
@ -1880,7 +1876,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
|
||||
mProcessLoggingHandler = testParams.processLoggingHandler;
|
||||
mProtectedPackages = testParams.protectedPackages;
|
||||
mSeparateProcesses = testParams.separateProcesses;
|
||||
mViewCompiler = testParams.viewCompiler;
|
||||
mRequiredVerifierPackages = testParams.requiredVerifierPackages;
|
||||
mRequiredInstallerPackage = testParams.requiredInstallerPackage;
|
||||
mRequiredUninstallerPackage = testParams.requiredUninstallerPackage;
|
||||
@ -2043,7 +2038,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
|
||||
mBackgroundDexOptService = injector.getBackgroundDexOptService();
|
||||
mArtManagerService = injector.getArtManagerService();
|
||||
mMoveCallbacks = new MovePackageHelper.MoveCallbacks(FgThread.get().getLooper());
|
||||
mViewCompiler = injector.getViewCompiler();
|
||||
mSharedLibraries = mInjector.getSharedLibrariesImpl();
|
||||
mBackgroundHandler = injector.getBackgroundHandler();
|
||||
|
||||
|
@ -32,7 +32,6 @@ import com.android.server.compat.PlatformCompat;
|
||||
import com.android.server.pm.dex.ArtManagerService;
|
||||
import com.android.server.pm.dex.DexManager;
|
||||
import com.android.server.pm.dex.DynamicCodeLogger;
|
||||
import com.android.server.pm.dex.ViewCompiler;
|
||||
import com.android.server.pm.parsing.PackageParser2;
|
||||
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
|
||||
import com.android.server.pm.permission.PermissionManagerServiceInternal;
|
||||
@ -112,7 +111,6 @@ public class PackageManagerServiceInjector {
|
||||
private final Singleton<ArtManagerService>
|
||||
mArtManagerServiceProducer;
|
||||
private final Singleton<ApexManager> mApexManagerProducer;
|
||||
private final Singleton<ViewCompiler> mViewCompilerProducer;
|
||||
private final Singleton<IncrementalManager>
|
||||
mIncrementalManagerProducer;
|
||||
private final Singleton<DefaultAppProvider>
|
||||
@ -164,7 +162,6 @@ public class PackageManagerServiceInjector {
|
||||
Producer<DynamicCodeLogger> dynamicCodeLoggerProducer,
|
||||
Producer<ArtManagerService> artManagerServiceProducer,
|
||||
Producer<ApexManager> apexManagerProducer,
|
||||
Producer<ViewCompiler> viewCompilerProducer,
|
||||
Producer<IncrementalManager> incrementalManagerProducer,
|
||||
Producer<DefaultAppProvider> defaultAppProviderProducer,
|
||||
Producer<DisplayMetrics> displayMetricsProducer,
|
||||
@ -214,7 +211,6 @@ public class PackageManagerServiceInjector {
|
||||
mArtManagerServiceProducer = new Singleton<>(
|
||||
artManagerServiceProducer);
|
||||
mApexManagerProducer = new Singleton<>(apexManagerProducer);
|
||||
mViewCompilerProducer = new Singleton<>(viewCompilerProducer);
|
||||
mIncrementalManagerProducer = new Singleton<>(
|
||||
incrementalManagerProducer);
|
||||
mDefaultAppProviderProducer = new Singleton<>(
|
||||
@ -339,10 +335,6 @@ public class PackageManagerServiceInjector {
|
||||
return mApexManagerProducer.get(this, mPackageManager);
|
||||
}
|
||||
|
||||
public ViewCompiler getViewCompiler() {
|
||||
return mViewCompilerProducer.get(this, mPackageManager);
|
||||
}
|
||||
|
||||
public Handler getBackgroundHandler() {
|
||||
return mBackgroundHandler;
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ import com.android.internal.content.om.OverlayConfig;
|
||||
import com.android.server.pm.dex.ArtManagerService;
|
||||
import com.android.server.pm.dex.DexManager;
|
||||
import com.android.server.pm.dex.DynamicCodeLogger;
|
||||
import com.android.server.pm.dex.ViewCompiler;
|
||||
import com.android.server.pm.parsing.PackageParser2;
|
||||
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
|
||||
import com.android.server.pm.pkg.AndroidPackage;
|
||||
@ -93,7 +92,6 @@ public final class PackageManagerServiceTestParams {
|
||||
public @Nullable String systemTextClassifierPackage;
|
||||
public @Nullable String overlayConfigSignaturePackage;
|
||||
public @NonNull String requiredSdkSandboxPackage;
|
||||
public ViewCompiler viewCompiler;
|
||||
public @Nullable String retailDemoPackage;
|
||||
public @Nullable String recentsPackage;
|
||||
public @Nullable String ambientContextDetectionPackage;
|
||||
|
@ -539,44 +539,6 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile layout resources in a given package.
|
||||
*/
|
||||
public boolean compileLayouts(@NonNull PackageStateInternal ps, @NonNull AndroidPackage pkg) {
|
||||
try {
|
||||
if (ps.isPrivileged() || pkg.isUseEmbeddedDex()
|
||||
|| pkg.isDefaultToDeviceProtectedStorage()) {
|
||||
// Privileged apps prefer to load trusted code so they don't use compiled views.
|
||||
// If the app is not privileged but prefers code integrity, also avoid compiling
|
||||
// views.
|
||||
// Also disable the view compiler for protected storage apps since there are
|
||||
// selinux permissions required for writing to user_de.
|
||||
return false;
|
||||
}
|
||||
final String packageName = pkg.getPackageName();
|
||||
final String apkPath = pkg.getSplits().get(0).getPath();
|
||||
final File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
|
||||
if (dataDir == null) {
|
||||
// The app is not installed on the target user and doesn't have a data dir
|
||||
return false;
|
||||
}
|
||||
final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
|
||||
Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
|
||||
") to " + outDexFile);
|
||||
final long callingId = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
|
||||
pkg.getUid());
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(callingId);
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Log.e("PackageManager", "Failed to compile layouts", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the profiles names for all the package code paths (excluding resource only paths).
|
||||
* Return the map [code path -> profile name].
|
||||
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package com.android.server.pm.dex;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.server.pm.Installer;
|
||||
import com.android.server.pm.parsing.PackageInfoUtils;
|
||||
import com.android.server.pm.pkg.PackageStateInternal;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ViewCompiler {
|
||||
private final Object mInstallLock;
|
||||
@GuardedBy("mInstallLock")
|
||||
private final Installer mInstaller;
|
||||
|
||||
public ViewCompiler(Object installLock, Installer installer) {
|
||||
mInstallLock = installLock;
|
||||
mInstaller = installer;
|
||||
}
|
||||
|
||||
public boolean compileLayouts(PackageStateInternal ps, String apkPath) {
|
||||
try {
|
||||
final String packageName = ps.getPackageName();
|
||||
File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
|
||||
if (dataDir == null) {
|
||||
// The app is not installed on the target user and doesn't have a data dir
|
||||
return false;
|
||||
}
|
||||
final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
|
||||
Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
|
||||
") to " + outDexFile);
|
||||
final long callingId = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mInstallLock) {
|
||||
return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
|
||||
ps.getAppId());
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(callingId);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Log.e("PackageManager", "Failed to compile layouts", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2018 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
// all of the 'license_kinds' from "frameworks_base_license"
|
||||
// to get the below license kinds:
|
||||
// SPDX-license-identifier-Apache-2.0
|
||||
default_applicable_licenses: ["frameworks_base_license"],
|
||||
}
|
||||
|
||||
cc_defaults {
|
||||
name: "viewcompiler_defaults",
|
||||
header_libs: [
|
||||
"libbase_headers",
|
||||
],
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"slicer",
|
||||
],
|
||||
static_libs: [
|
||||
"libcutils",
|
||||
"libtinyxml2",
|
||||
"liblog",
|
||||
"libutils",
|
||||
"libziparchive",
|
||||
"libz",
|
||||
],
|
||||
cpp_std: "gnu++2b",
|
||||
target: {
|
||||
android: {
|
||||
shared_libs: [
|
||||
"libandroidfw",
|
||||
],
|
||||
},
|
||||
host: {
|
||||
static_libs: [
|
||||
"libandroidfw",
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "libviewcompiler",
|
||||
defaults: ["viewcompiler_defaults"],
|
||||
srcs: [
|
||||
"apk_layout_compiler.cc",
|
||||
"dex_builder.cc",
|
||||
"dex_layout_compiler.cc",
|
||||
"java_lang_builder.cc",
|
||||
"tinyxml_layout_parser.cc",
|
||||
"util.cc",
|
||||
"layout_validation.cc",
|
||||
],
|
||||
host_supported: true,
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
name: "viewcompiler",
|
||||
defaults: ["viewcompiler_defaults"],
|
||||
srcs: [
|
||||
"main.cc",
|
||||
],
|
||||
static_libs: [
|
||||
"libgflags",
|
||||
"libviewcompiler",
|
||||
],
|
||||
host_supported: true,
|
||||
}
|
||||
|
||||
cc_test_host {
|
||||
name: "view-compiler-tests",
|
||||
defaults: ["viewcompiler_defaults"],
|
||||
srcs: [
|
||||
"layout_validation_test.cc",
|
||||
"util_test.cc",
|
||||
],
|
||||
static_libs: [
|
||||
"libviewcompiler",
|
||||
],
|
||||
}
|
||||
|
||||
cc_binary_host {
|
||||
name: "dex_testcase_generator",
|
||||
defaults: ["viewcompiler_defaults"],
|
||||
srcs: ["dex_testcase_generator.cc"],
|
||||
static_libs: [
|
||||
"libviewcompiler",
|
||||
],
|
||||
}
|
||||
|
||||
genrule {
|
||||
name: "generate_dex_testcases",
|
||||
tools: [":dex_testcase_generator"],
|
||||
cmd: "$(location :dex_testcase_generator) $(genDir)",
|
||||
out: [
|
||||
"simple.dex",
|
||||
"trivial.dex",
|
||||
],
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
eholk@google.com
|
||||
mathieuc@google.com
|
@ -1,53 +0,0 @@
|
||||
# View Compiler
|
||||
|
||||
This directory contains an experimental compiler for layout files.
|
||||
|
||||
It will take a layout XML file and produce a CompiledLayout.java file with a
|
||||
specialized layout inflation function.
|
||||
|
||||
To use it, let's assume you had a layout in `my_layout.xml` and your app was in
|
||||
the Java language package `com.example.myapp`. Run the following command:
|
||||
|
||||
viewcompiler my_layout.xml --package com.example.myapp --out CompiledView.java
|
||||
|
||||
This will produce a `CompiledView.java`, which can then be compiled into your
|
||||
Android app. Then to use it, in places where you would have inflated
|
||||
`R.layouts.my_layout`, instead call `CompiledView.inflate`.
|
||||
|
||||
Precompiling views like this generally improves the time needed to inflate them.
|
||||
|
||||
This tool is still in its early stages and has a number of limitations.
|
||||
* Currently only one layout can be compiled at a time.
|
||||
* `merge` and `include` nodes are not supported.
|
||||
* View compilation is a manual process that requires code changes in the
|
||||
application.
|
||||
* This only works for apps that do not use a custom layout inflater.
|
||||
* Other limitations yet to be discovered.
|
||||
|
||||
## DexBuilder Tests
|
||||
|
||||
The DexBuilder has several low-level end to end tests to verify generated DEX
|
||||
code validates, runs, and has the correct behavior. There are, unfortunately, a
|
||||
number of pieces that must be added to generate new tests. Here are the
|
||||
components:
|
||||
|
||||
* `dex_testcase_generator` - Written in C++ using `DexBuilder`. This runs as a
|
||||
build step produce the DEX files that will be tested on device. See the
|
||||
`genrule` named `generate_dex_testcases` in `Android.bp`. These files are then
|
||||
copied over to the device by TradeFed when running tests.
|
||||
* `DexBuilderTest` - This is a Java Language test harness that loads the
|
||||
generated DEX files and exercises methods in the file.
|
||||
|
||||
To add a new DEX file test, follow these steps:
|
||||
1. Modify `dex_testcase_generator` to produce the DEX file.
|
||||
2. Add the filename to the `out` list of the `generate_dex_testcases` rule in
|
||||
`Android.bp`.
|
||||
3. Add a new `push` option to `AndroidTest.xml` to copy the DEX file to the
|
||||
device.
|
||||
4. Modify `DexBuilderTest.java` to load and exercise the new test.
|
||||
|
||||
In each case, you should be able to cargo-cult the existing test cases.
|
||||
|
||||
In general, you can probably get by without adding a new generated DEX file, and
|
||||
instead add more methods to the files that are already generated. In this case,
|
||||
you can skip all of steps 2 and 3 above, and simplify steps 1 and 4.
|
@ -1,179 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "apk_layout_compiler.h"
|
||||
#include "dex_layout_compiler.h"
|
||||
#include "java_lang_builder.h"
|
||||
#include "layout_validation.h"
|
||||
#include "util.h"
|
||||
|
||||
#include "androidfw/ApkAssets.h"
|
||||
#include "androidfw/AssetManager2.h"
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <locale>
|
||||
|
||||
#include "android-base/stringprintf.h"
|
||||
|
||||
namespace startop {
|
||||
|
||||
using android::ResXMLParser;
|
||||
using android::base::StringPrintf;
|
||||
|
||||
class ResXmlVisitorAdapter {
|
||||
public:
|
||||
ResXmlVisitorAdapter(ResXMLParser* parser) : parser_{parser} {}
|
||||
|
||||
template <typename Visitor>
|
||||
void Accept(Visitor* visitor) {
|
||||
size_t depth{0};
|
||||
do {
|
||||
switch (parser_->next()) {
|
||||
case ResXMLParser::START_DOCUMENT:
|
||||
depth++;
|
||||
visitor->VisitStartDocument();
|
||||
break;
|
||||
case ResXMLParser::END_DOCUMENT:
|
||||
depth--;
|
||||
visitor->VisitEndDocument();
|
||||
break;
|
||||
case ResXMLParser::START_TAG: {
|
||||
depth++;
|
||||
size_t name_length = 0;
|
||||
const char16_t* name = parser_->getElementName(&name_length);
|
||||
visitor->VisitStartTag(std::u16string{name, name_length});
|
||||
break;
|
||||
}
|
||||
case ResXMLParser::END_TAG:
|
||||
depth--;
|
||||
visitor->VisitEndTag();
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
} while (depth > 0 || parser_->getEventType() == ResXMLParser::FIRST_CHUNK_CODE);
|
||||
}
|
||||
|
||||
private:
|
||||
ResXMLParser* parser_;
|
||||
};
|
||||
|
||||
bool CanCompileLayout(ResXMLParser* parser) {
|
||||
ResXmlVisitorAdapter adapter{parser};
|
||||
LayoutValidationVisitor visitor;
|
||||
adapter.Accept(&visitor);
|
||||
|
||||
return visitor.can_compile();
|
||||
}
|
||||
|
||||
namespace {
|
||||
void CompileApkAssetsLayouts(const android::ApkAssetsPtr& assets, CompilationTarget target,
|
||||
std::ostream& target_out) {
|
||||
android::AssetManager2 resources;
|
||||
resources.SetApkAssets({assets});
|
||||
|
||||
std::string package_name;
|
||||
|
||||
// TODO: handle multiple packages better
|
||||
bool first = true;
|
||||
for (const auto& package : assets->GetLoadedArsc()->GetPackages()) {
|
||||
CHECK(first);
|
||||
package_name = package->GetPackageName();
|
||||
first = false;
|
||||
}
|
||||
|
||||
dex::DexBuilder dex_file;
|
||||
dex::ClassBuilder compiled_view{
|
||||
dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
|
||||
std::vector<dex::MethodBuilder> methods;
|
||||
|
||||
assets->GetAssetsProvider()->ForEachFile("res/", [&](android::StringPiece s, android::FileType) {
|
||||
if (s == "layout") {
|
||||
auto path = StringPrintf("res/%.*s/", (int)s.size(), s.data());
|
||||
assets->GetAssetsProvider()
|
||||
->ForEachFile(path, [&](android::StringPiece layout_file, android::FileType) {
|
||||
auto layout_path = StringPrintf("%s%.*s", path.c_str(),
|
||||
(int)layout_file.size(), layout_file.data());
|
||||
android::ApkAssetsCookie cookie = android::kInvalidCookie;
|
||||
auto asset = resources.OpenNonAsset(layout_path,
|
||||
android::Asset::ACCESS_RANDOM, &cookie);
|
||||
CHECK(asset);
|
||||
CHECK(android::kInvalidCookie != cookie);
|
||||
const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
|
||||
CHECK(nullptr != dynamic_ref_table);
|
||||
android::ResXMLTree xml_tree{dynamic_ref_table};
|
||||
xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true), asset->getLength(),
|
||||
/*copy_data=*/true);
|
||||
android::ResXMLParser parser{xml_tree};
|
||||
parser.restart();
|
||||
if (CanCompileLayout(&parser)) {
|
||||
parser.restart();
|
||||
const std::string layout_name =
|
||||
startop::util::FindLayoutNameFromFilename(layout_path);
|
||||
ResXmlVisitorAdapter adapter{&parser};
|
||||
switch (target) {
|
||||
case CompilationTarget::kDex: {
|
||||
methods.push_back(compiled_view.CreateMethod(
|
||||
layout_name,
|
||||
dex::Prototype{dex::TypeDescriptor::FromClassname(
|
||||
"android.view.View"),
|
||||
dex::TypeDescriptor::FromClassname(
|
||||
"android.content.Context"),
|
||||
dex::TypeDescriptor::Int()}));
|
||||
DexViewBuilder builder(&methods.back());
|
||||
builder.Start();
|
||||
LayoutCompilerVisitor visitor{&builder};
|
||||
adapter.Accept(&visitor);
|
||||
builder.Finish();
|
||||
methods.back().Encode();
|
||||
break;
|
||||
}
|
||||
case CompilationTarget::kJavaLanguage: {
|
||||
JavaLangViewBuilder builder{package_name, layout_name,
|
||||
target_out};
|
||||
builder.Start();
|
||||
LayoutCompilerVisitor visitor{&builder};
|
||||
adapter.Accept(&visitor);
|
||||
builder.Finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (target == CompilationTarget::kDex) {
|
||||
slicer::MemView image{dex_file.CreateImage()};
|
||||
target_out.write(image.ptr<const char>(), image.size());
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void CompileApkLayouts(const std::string& filename, CompilationTarget target,
|
||||
std::ostream& target_out) {
|
||||
auto assets = android::ApkAssets::Load(filename);
|
||||
CompileApkAssetsLayouts(assets, target, target_out);
|
||||
}
|
||||
|
||||
void CompileApkLayoutsFd(android::base::unique_fd fd, CompilationTarget target,
|
||||
std::ostream& target_out) {
|
||||
constexpr const char* friendly_name{"viewcompiler assets"};
|
||||
auto assets = android::ApkAssets::LoadFromFd(std::move(fd), friendly_name);
|
||||
CompileApkAssetsLayouts(assets, target, target_out);
|
||||
}
|
||||
|
||||
} // namespace startop
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef APK_LAYOUT_COMPILER_H_
|
||||
#define APK_LAYOUT_COMPILER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "android-base/unique_fd.h"
|
||||
|
||||
namespace startop {
|
||||
|
||||
enum class CompilationTarget { kJavaLanguage, kDex };
|
||||
|
||||
void CompileApkLayouts(const std::string& filename, CompilationTarget target,
|
||||
std::ostream& target_out);
|
||||
void CompileApkLayoutsFd(android::base::unique_fd fd, CompilationTarget target,
|
||||
std::ostream& target_out);
|
||||
|
||||
} // namespace startop
|
||||
|
||||
#endif // APK_LAYOUT_COMPILER_H_
|
@ -1,704 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "dex_builder.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
namespace startop {
|
||||
namespace dex {
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::string;
|
||||
|
||||
using ::dex::kAccPublic;
|
||||
using Op = Instruction::Op;
|
||||
|
||||
const TypeDescriptor TypeDescriptor::Int() { return TypeDescriptor{"I"}; };
|
||||
const TypeDescriptor TypeDescriptor::Void() { return TypeDescriptor{"V"}; };
|
||||
|
||||
namespace {
|
||||
// From https://source.android.com/devices/tech/dalvik/dex-format#dex-file-magic
|
||||
constexpr uint8_t kDexFileMagic[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x38, 0x00};
|
||||
|
||||
// Strings lengths can be 32 bits long, but encoded as LEB128 this can take up to five bytes.
|
||||
constexpr size_t kMaxEncodedStringLength{5};
|
||||
|
||||
// Converts invoke-* to invoke-*/range
|
||||
constexpr ::dex::Opcode InvokeToInvokeRange(::dex::Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case ::dex::Opcode::OP_INVOKE_VIRTUAL:
|
||||
return ::dex::Opcode::OP_INVOKE_VIRTUAL_RANGE;
|
||||
case ::dex::Opcode::OP_INVOKE_DIRECT:
|
||||
return ::dex::Opcode::OP_INVOKE_DIRECT_RANGE;
|
||||
case ::dex::Opcode::OP_INVOKE_STATIC:
|
||||
return ::dex::Opcode::OP_INVOKE_STATIC_RANGE;
|
||||
case ::dex::Opcode::OP_INVOKE_INTERFACE:
|
||||
return ::dex::Opcode::OP_INVOKE_INTERFACE_RANGE;
|
||||
default:
|
||||
LOG(FATAL) << opcode << " is not a recognized invoke opcode.";
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
std::string DotToDescriptor(const char* class_name) {
|
||||
std::string descriptor(class_name);
|
||||
std::replace(descriptor.begin(), descriptor.end(), '.', '/');
|
||||
if (descriptor.length() > 0 && descriptor[0] != '[') {
|
||||
descriptor = "L" + descriptor + ";";
|
||||
}
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) {
|
||||
switch (opcode) {
|
||||
case Instruction::Op::kReturn:
|
||||
out << "kReturn";
|
||||
return out;
|
||||
case Instruction::Op::kReturnObject:
|
||||
out << "kReturnObject";
|
||||
return out;
|
||||
case Instruction::Op::kMove:
|
||||
out << "kMove";
|
||||
return out;
|
||||
case Instruction::Op::kMoveObject:
|
||||
out << "kMoveObject";
|
||||
return out;
|
||||
case Instruction::Op::kInvokeVirtual:
|
||||
out << "kInvokeVirtual";
|
||||
return out;
|
||||
case Instruction::Op::kInvokeDirect:
|
||||
out << "kInvokeDirect";
|
||||
return out;
|
||||
case Instruction::Op::kInvokeStatic:
|
||||
out << "kInvokeStatic";
|
||||
return out;
|
||||
case Instruction::Op::kInvokeInterface:
|
||||
out << "kInvokeInterface";
|
||||
return out;
|
||||
case Instruction::Op::kBindLabel:
|
||||
out << "kBindLabel";
|
||||
return out;
|
||||
case Instruction::Op::kBranchEqz:
|
||||
out << "kBranchEqz";
|
||||
return out;
|
||||
case Instruction::Op::kBranchNEqz:
|
||||
out << "kBranchNEqz";
|
||||
return out;
|
||||
case Instruction::Op::kNew:
|
||||
out << "kNew";
|
||||
return out;
|
||||
case Instruction::Op::kCheckCast:
|
||||
out << "kCheckCast";
|
||||
return out;
|
||||
case Instruction::Op::kGetStaticField:
|
||||
out << "kGetStaticField";
|
||||
return out;
|
||||
case Instruction::Op::kSetStaticField:
|
||||
out << "kSetStaticField";
|
||||
return out;
|
||||
case Instruction::Op::kGetInstanceField:
|
||||
out << "kGetInstanceField";
|
||||
return out;
|
||||
case Instruction::Op::kSetInstanceField:
|
||||
out << "kSetInstanceField";
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const Value& value) {
|
||||
if (value.is_register()) {
|
||||
out << "Register(" << value.value() << ")";
|
||||
} else if (value.is_parameter()) {
|
||||
out << "Parameter(" << value.value() << ")";
|
||||
} else if (value.is_immediate()) {
|
||||
out << "Immediate(" << value.value() << ")";
|
||||
} else if (value.is_string()) {
|
||||
out << "String(" << value.value() << ")";
|
||||
} else if (value.is_label()) {
|
||||
out << "Label(" << value.value() << ")";
|
||||
} else if (value.is_type()) {
|
||||
out << "Type(" << value.value() << ")";
|
||||
} else {
|
||||
out << "UnknownValue";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void* TrackingAllocator::Allocate(size_t size) {
|
||||
std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(size);
|
||||
void* raw_buffer = buffer.get();
|
||||
allocations_[raw_buffer] = std::move(buffer);
|
||||
return raw_buffer;
|
||||
}
|
||||
|
||||
void TrackingAllocator::Free(void* ptr) { allocations_.erase(allocations_.find(ptr)); }
|
||||
|
||||
// Write out a DEX file that is basically:
|
||||
//
|
||||
// package dextest;
|
||||
// public class DexTest {
|
||||
// public static int foo(String s) { return s.length(); }
|
||||
// }
|
||||
void WriteTestDexFile(const string& filename) {
|
||||
DexBuilder dex_file;
|
||||
|
||||
ClassBuilder cbuilder{dex_file.MakeClass("dextest.DexTest")};
|
||||
cbuilder.set_source_file("dextest.java");
|
||||
|
||||
TypeDescriptor string_type = TypeDescriptor::FromClassname("java.lang.String");
|
||||
|
||||
MethodBuilder method{cbuilder.CreateMethod("foo", Prototype{TypeDescriptor::Int(), string_type})};
|
||||
|
||||
LiveRegister result = method.AllocRegister();
|
||||
|
||||
MethodDeclData string_length =
|
||||
dex_file.GetOrDeclareMethod(string_type, "length", Prototype{TypeDescriptor::Int()});
|
||||
|
||||
method.AddInstruction(Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0)));
|
||||
method.BuildReturn(result);
|
||||
|
||||
method.Encode();
|
||||
|
||||
slicer::MemView image{dex_file.CreateImage()};
|
||||
|
||||
std::ofstream out_file(filename);
|
||||
out_file.write(image.ptr<const char>(), image.size());
|
||||
}
|
||||
|
||||
TypeDescriptor TypeDescriptor::FromClassname(const std::string& name) {
|
||||
return TypeDescriptor{DotToDescriptor(name.c_str())};
|
||||
}
|
||||
|
||||
DexBuilder::DexBuilder() : dex_file_{std::make_shared<ir::DexFile>()} {
|
||||
dex_file_->magic = slicer::MemView{kDexFileMagic, sizeof(kDexFileMagic)};
|
||||
}
|
||||
|
||||
slicer::MemView DexBuilder::CreateImage() {
|
||||
::dex::Writer writer(dex_file_);
|
||||
size_t image_size{0};
|
||||
::dex::u1* image = writer.CreateImage(&allocator_, &image_size);
|
||||
return slicer::MemView{image, image_size};
|
||||
}
|
||||
|
||||
ir::String* DexBuilder::GetOrAddString(const std::string& string) {
|
||||
ir::String*& entry = strings_[string];
|
||||
|
||||
if (entry == nullptr) {
|
||||
// Need to encode the length and then write out the bytes, including 1 byte for null terminator
|
||||
auto buffer = std::make_unique<uint8_t[]>(string.size() + kMaxEncodedStringLength + 1);
|
||||
uint8_t* string_data_start = ::dex::WriteULeb128(buffer.get(), string.size());
|
||||
|
||||
size_t header_length =
|
||||
reinterpret_cast<uintptr_t>(string_data_start) - reinterpret_cast<uintptr_t>(buffer.get());
|
||||
|
||||
auto end = std::copy(string.begin(), string.end(), string_data_start);
|
||||
*end = '\0';
|
||||
|
||||
entry = Alloc<ir::String>();
|
||||
// +1 for null terminator
|
||||
entry->data = slicer::MemView{buffer.get(), header_length + string.size() + 1};
|
||||
::dex::u4 const new_index = dex_file_->strings_indexes.AllocateIndex();
|
||||
dex_file_->strings_map[new_index] = entry;
|
||||
entry->orig_index = new_index;
|
||||
string_data_.push_back(std::move(buffer));
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
ClassBuilder DexBuilder::MakeClass(const std::string& name) {
|
||||
auto* class_def = Alloc<ir::Class>();
|
||||
ir::Type* type_def = GetOrAddType(DotToDescriptor(name.c_str()));
|
||||
type_def->class_def = class_def;
|
||||
|
||||
class_def->type = type_def;
|
||||
class_def->super_class = GetOrAddType(DotToDescriptor("java.lang.Object"));
|
||||
class_def->access_flags = kAccPublic;
|
||||
return ClassBuilder{this, name, class_def};
|
||||
}
|
||||
|
||||
ir::Type* DexBuilder::GetOrAddType(const std::string& descriptor) {
|
||||
if (types_by_descriptor_.find(descriptor) != types_by_descriptor_.end()) {
|
||||
return types_by_descriptor_[descriptor];
|
||||
}
|
||||
|
||||
ir::Type* type = Alloc<ir::Type>();
|
||||
type->descriptor = GetOrAddString(descriptor);
|
||||
types_by_descriptor_[descriptor] = type;
|
||||
type->orig_index = dex_file_->types_indexes.AllocateIndex();
|
||||
dex_file_->types_map[type->orig_index] = type;
|
||||
return type;
|
||||
}
|
||||
|
||||
ir::FieldDecl* DexBuilder::GetOrAddField(TypeDescriptor parent, const std::string& name,
|
||||
TypeDescriptor type) {
|
||||
const auto key = std::make_tuple(parent, name);
|
||||
if (field_decls_by_key_.find(key) != field_decls_by_key_.end()) {
|
||||
return field_decls_by_key_[key];
|
||||
}
|
||||
|
||||
ir::FieldDecl* field = Alloc<ir::FieldDecl>();
|
||||
field->parent = GetOrAddType(parent);
|
||||
field->name = GetOrAddString(name);
|
||||
field->type = GetOrAddType(type);
|
||||
field->orig_index = dex_file_->fields_indexes.AllocateIndex();
|
||||
dex_file_->fields_map[field->orig_index] = field;
|
||||
field_decls_by_key_[key] = field;
|
||||
return field;
|
||||
}
|
||||
|
||||
ir::Proto* Prototype::Encode(DexBuilder* dex) const {
|
||||
auto* proto = dex->Alloc<ir::Proto>();
|
||||
proto->shorty = dex->GetOrAddString(Shorty());
|
||||
proto->return_type = dex->GetOrAddType(return_type_.descriptor());
|
||||
if (param_types_.size() > 0) {
|
||||
proto->param_types = dex->Alloc<ir::TypeList>();
|
||||
for (const auto& param_type : param_types_) {
|
||||
proto->param_types->types.push_back(dex->GetOrAddType(param_type.descriptor()));
|
||||
}
|
||||
} else {
|
||||
proto->param_types = nullptr;
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
std::string Prototype::Shorty() const {
|
||||
std::string shorty;
|
||||
shorty.append(return_type_.short_descriptor());
|
||||
for (const auto& type_descriptor : param_types_) {
|
||||
shorty.append(type_descriptor.short_descriptor());
|
||||
}
|
||||
return shorty;
|
||||
}
|
||||
|
||||
const TypeDescriptor& Prototype::ArgType(size_t index) const {
|
||||
CHECK_LT(index, param_types_.size());
|
||||
return param_types_[index];
|
||||
}
|
||||
|
||||
ClassBuilder::ClassBuilder(DexBuilder* parent, const std::string& name, ir::Class* class_def)
|
||||
: parent_(parent), type_descriptor_{TypeDescriptor::FromClassname(name)}, class_(class_def) {}
|
||||
|
||||
MethodBuilder ClassBuilder::CreateMethod(const std::string& name, Prototype prototype) {
|
||||
ir::MethodDecl* decl = parent_->GetOrDeclareMethod(type_descriptor_, name, prototype).decl;
|
||||
|
||||
return MethodBuilder{parent_, class_, decl};
|
||||
}
|
||||
|
||||
void ClassBuilder::set_source_file(const string& source) {
|
||||
class_->source_file = parent_->GetOrAddString(source);
|
||||
}
|
||||
|
||||
MethodBuilder::MethodBuilder(DexBuilder* dex, ir::Class* class_def, ir::MethodDecl* decl)
|
||||
: dex_{dex}, class_{class_def}, decl_{decl} {}
|
||||
|
||||
ir::EncodedMethod* MethodBuilder::Encode() {
|
||||
auto* method = dex_->Alloc<ir::EncodedMethod>();
|
||||
method->decl = decl_;
|
||||
|
||||
// TODO: make access flags configurable
|
||||
method->access_flags = kAccPublic | ::dex::kAccStatic;
|
||||
|
||||
auto* code = dex_->Alloc<ir::Code>();
|
||||
CHECK(decl_->prototype != nullptr);
|
||||
size_t const num_args =
|
||||
decl_->prototype->param_types != nullptr ? decl_->prototype->param_types->types.size() : 0;
|
||||
code->registers = NumRegisters() + num_args + kMaxScratchRegisters;
|
||||
code->ins_count = num_args;
|
||||
EncodeInstructions();
|
||||
code->instructions = slicer::ArrayView<const ::dex::u2>(buffer_.data(), buffer_.size());
|
||||
size_t const return_count = decl_->prototype->return_type == dex_->GetOrAddType("V") ? 0 : 1;
|
||||
code->outs_count = std::max(return_count, max_args_);
|
||||
method->code = code;
|
||||
|
||||
class_->direct_methods.push_back(method);
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
LiveRegister MethodBuilder::AllocRegister() {
|
||||
// Find a free register
|
||||
for (size_t i = 0; i < register_liveness_.size(); ++i) {
|
||||
if (!register_liveness_[i]) {
|
||||
register_liveness_[i] = true;
|
||||
return LiveRegister{®ister_liveness_, i};
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, all the registers are in use, so we have to allocate a new
|
||||
// one.
|
||||
register_liveness_.push_back(true);
|
||||
return LiveRegister{®ister_liveness_, register_liveness_.size() - 1};
|
||||
}
|
||||
|
||||
Value MethodBuilder::MakeLabel() {
|
||||
labels_.push_back({});
|
||||
return Value::Label(labels_.size() - 1);
|
||||
}
|
||||
|
||||
void MethodBuilder::AddInstruction(Instruction instruction) {
|
||||
instructions_.push_back(instruction);
|
||||
}
|
||||
|
||||
void MethodBuilder::BuildReturn() { AddInstruction(Instruction::OpNoArgs(Op::kReturn)); }
|
||||
|
||||
void MethodBuilder::BuildReturn(Value src, bool is_object) {
|
||||
AddInstruction(Instruction::OpWithArgs(
|
||||
is_object ? Op::kReturnObject : Op::kReturn, /*destination=*/{}, src));
|
||||
}
|
||||
|
||||
void MethodBuilder::BuildConst4(Value target, int value) {
|
||||
CHECK_LT(value, 16);
|
||||
AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::Immediate(value)));
|
||||
}
|
||||
|
||||
void MethodBuilder::BuildConstString(Value target, const std::string& value) {
|
||||
const ir::String* const dex_string = dex_->GetOrAddString(value);
|
||||
AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::String(dex_string->orig_index)));
|
||||
}
|
||||
|
||||
void MethodBuilder::EncodeInstructions() {
|
||||
buffer_.clear();
|
||||
for (const auto& instruction : instructions_) {
|
||||
EncodeInstruction(instruction);
|
||||
}
|
||||
}
|
||||
|
||||
void MethodBuilder::EncodeInstruction(const Instruction& instruction) {
|
||||
switch (instruction.opcode()) {
|
||||
case Instruction::Op::kReturn:
|
||||
return EncodeReturn(instruction, ::dex::Opcode::OP_RETURN);
|
||||
case Instruction::Op::kReturnObject:
|
||||
return EncodeReturn(instruction, ::dex::Opcode::OP_RETURN_OBJECT);
|
||||
case Instruction::Op::kMove:
|
||||
case Instruction::Op::kMoveObject:
|
||||
return EncodeMove(instruction);
|
||||
case Instruction::Op::kInvokeVirtual:
|
||||
return EncodeInvoke(instruction, ::dex::Opcode::OP_INVOKE_VIRTUAL);
|
||||
case Instruction::Op::kInvokeDirect:
|
||||
return EncodeInvoke(instruction, ::dex::Opcode::OP_INVOKE_DIRECT);
|
||||
case Instruction::Op::kInvokeStatic:
|
||||
return EncodeInvoke(instruction, ::dex::Opcode::OP_INVOKE_STATIC);
|
||||
case Instruction::Op::kInvokeInterface:
|
||||
return EncodeInvoke(instruction, ::dex::Opcode::OP_INVOKE_INTERFACE);
|
||||
case Instruction::Op::kBindLabel:
|
||||
return BindLabel(instruction.args()[0]);
|
||||
case Instruction::Op::kBranchEqz:
|
||||
return EncodeBranch(::dex::Opcode::OP_IF_EQZ, instruction);
|
||||
case Instruction::Op::kBranchNEqz:
|
||||
return EncodeBranch(::dex::Opcode::OP_IF_NEZ, instruction);
|
||||
case Instruction::Op::kNew:
|
||||
return EncodeNew(instruction);
|
||||
case Instruction::Op::kCheckCast:
|
||||
return EncodeCast(instruction);
|
||||
case Instruction::Op::kGetStaticField:
|
||||
case Instruction::Op::kSetStaticField:
|
||||
case Instruction::Op::kGetInstanceField:
|
||||
case Instruction::Op::kSetInstanceField:
|
||||
return EncodeFieldOp(instruction);
|
||||
}
|
||||
}
|
||||
|
||||
void MethodBuilder::EncodeReturn(const Instruction& instruction, ::dex::Opcode opcode) {
|
||||
CHECK(!instruction.dest().has_value());
|
||||
if (instruction.args().size() == 0) {
|
||||
Encode10x(::dex::Opcode::OP_RETURN_VOID);
|
||||
} else {
|
||||
CHECK_EQ(1, instruction.args().size());
|
||||
size_t source = RegisterValue(instruction.args()[0]);
|
||||
Encode11x(opcode, source);
|
||||
}
|
||||
}
|
||||
|
||||
void MethodBuilder::EncodeMove(const Instruction& instruction) {
|
||||
CHECK(Instruction::Op::kMove == instruction.opcode() ||
|
||||
Instruction::Op::kMoveObject == instruction.opcode());
|
||||
CHECK(instruction.dest().has_value());
|
||||
CHECK(instruction.dest()->is_variable());
|
||||
CHECK_EQ(1, instruction.args().size());
|
||||
|
||||
const Value& source = instruction.args()[0];
|
||||
|
||||
if (source.is_immediate()) {
|
||||
// TODO: support more registers
|
||||
CHECK_LT(RegisterValue(*instruction.dest()), 16);
|
||||
Encode11n(::dex::Opcode::OP_CONST_4, RegisterValue(*instruction.dest()), source.value());
|
||||
} else if (source.is_string()) {
|
||||
constexpr size_t kMaxRegisters = 256;
|
||||
CHECK_LT(RegisterValue(*instruction.dest()), kMaxRegisters);
|
||||
CHECK_LT(source.value(), 65536); // make sure we don't need a jumbo string
|
||||
Encode21c(::dex::Opcode::OP_CONST_STRING, RegisterValue(*instruction.dest()), source.value());
|
||||
} else if (source.is_variable()) {
|
||||
// For the moment, we only use this when we need to reshuffle registers for
|
||||
// an invoke instruction, meaning we are too big for the 4-bit version.
|
||||
// We'll err on the side of caution and always generate the 16-bit form of
|
||||
// the instruction.
|
||||
auto opcode = instruction.opcode() == Instruction::Op::kMove
|
||||
? ::dex::Opcode::OP_MOVE_16
|
||||
: ::dex::Opcode::OP_MOVE_OBJECT_16;
|
||||
Encode32x(opcode, RegisterValue(*instruction.dest()), RegisterValue(source));
|
||||
} else {
|
||||
UNIMPLEMENTED(FATAL);
|
||||
}
|
||||
}
|
||||
|
||||
void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::dex::Opcode opcode) {
|
||||
constexpr size_t kMaxArgs = 5;
|
||||
|
||||
// Currently, we only support up to 5 arguments.
|
||||
CHECK_LE(instruction.args().size(), kMaxArgs);
|
||||
|
||||
uint8_t arguments[kMaxArgs]{};
|
||||
bool has_long_args = false;
|
||||
for (size_t i = 0; i < instruction.args().size(); ++i) {
|
||||
CHECK(instruction.args()[i].is_variable());
|
||||
arguments[i] = RegisterValue(instruction.args()[i]);
|
||||
if (!IsShortRegister(arguments[i])) {
|
||||
has_long_args = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_long_args) {
|
||||
// Some of the registers don't fit in the four bit short form of the invoke
|
||||
// instruction, so we need to do an invoke/range. To do this, we need to
|
||||
// first move all the arguments into contiguous temporary registers.
|
||||
std::array<Value, kMaxArgs> scratch = GetScratchRegisters<kMaxArgs>();
|
||||
|
||||
const auto& prototype = dex_->GetPrototypeByMethodId(instruction.index_argument());
|
||||
CHECK(prototype.has_value());
|
||||
|
||||
for (size_t i = 0; i < instruction.args().size(); ++i) {
|
||||
Instruction::Op move_op;
|
||||
if (opcode == ::dex::Opcode::OP_INVOKE_VIRTUAL ||
|
||||
opcode == ::dex::Opcode::OP_INVOKE_DIRECT) {
|
||||
// In this case, there is an implicit `this` argument, which is always an object.
|
||||
if (i == 0) {
|
||||
move_op = Instruction::Op::kMoveObject;
|
||||
} else {
|
||||
move_op = prototype->ArgType(i - 1).is_object() ? Instruction::Op::kMoveObject
|
||||
: Instruction::Op::kMove;
|
||||
}
|
||||
} else {
|
||||
move_op = prototype->ArgType(i).is_object() ? Instruction::Op::kMoveObject
|
||||
: Instruction::Op::kMove;
|
||||
}
|
||||
|
||||
EncodeMove(Instruction::OpWithArgs(move_op, scratch[i], instruction.args()[i]));
|
||||
}
|
||||
|
||||
Encode3rc(InvokeToInvokeRange(opcode),
|
||||
instruction.args().size(),
|
||||
instruction.index_argument(),
|
||||
RegisterValue(scratch[0]));
|
||||
} else {
|
||||
Encode35c(opcode,
|
||||
instruction.args().size(),
|
||||
instruction.index_argument(),
|
||||
arguments[0],
|
||||
arguments[1],
|
||||
arguments[2],
|
||||
arguments[3],
|
||||
arguments[4]);
|
||||
}
|
||||
|
||||
// If there is a return value, add a move-result instruction
|
||||
if (instruction.dest().has_value()) {
|
||||
Encode11x(instruction.result_is_object() ? ::dex::Opcode::OP_MOVE_RESULT_OBJECT
|
||||
: ::dex::Opcode::OP_MOVE_RESULT,
|
||||
RegisterValue(*instruction.dest()));
|
||||
}
|
||||
|
||||
max_args_ = std::max(max_args_, instruction.args().size());
|
||||
}
|
||||
|
||||
// Encodes a conditional branch that tests a single argument.
|
||||
void MethodBuilder::EncodeBranch(::dex::Opcode op, const Instruction& instruction) {
|
||||
const auto& args = instruction.args();
|
||||
const auto& test_value = args[0];
|
||||
const auto& branch_target = args[1];
|
||||
CHECK_EQ(2, args.size());
|
||||
CHECK(test_value.is_variable());
|
||||
CHECK(branch_target.is_label());
|
||||
|
||||
size_t instruction_offset = buffer_.size();
|
||||
size_t field_offset = buffer_.size() + 1;
|
||||
Encode21c(
|
||||
op, RegisterValue(test_value), LabelValue(branch_target, instruction_offset, field_offset));
|
||||
}
|
||||
|
||||
void MethodBuilder::EncodeNew(const Instruction& instruction) {
|
||||
CHECK_EQ(Instruction::Op::kNew, instruction.opcode());
|
||||
CHECK(instruction.dest().has_value());
|
||||
CHECK(instruction.dest()->is_variable());
|
||||
CHECK_EQ(1, instruction.args().size());
|
||||
|
||||
const Value& type = instruction.args()[0];
|
||||
CHECK_LT(RegisterValue(*instruction.dest()), 256);
|
||||
CHECK(type.is_type());
|
||||
Encode21c(::dex::Opcode::OP_NEW_INSTANCE, RegisterValue(*instruction.dest()), type.value());
|
||||
}
|
||||
|
||||
void MethodBuilder::EncodeCast(const Instruction& instruction) {
|
||||
CHECK_EQ(Instruction::Op::kCheckCast, instruction.opcode());
|
||||
CHECK(instruction.dest().has_value());
|
||||
CHECK(instruction.dest()->is_variable());
|
||||
CHECK_EQ(1, instruction.args().size());
|
||||
|
||||
const Value& type = instruction.args()[0];
|
||||
CHECK_LT(RegisterValue(*instruction.dest()), 256);
|
||||
CHECK(type.is_type());
|
||||
Encode21c(::dex::Opcode::OP_CHECK_CAST, RegisterValue(*instruction.dest()), type.value());
|
||||
}
|
||||
|
||||
void MethodBuilder::EncodeFieldOp(const Instruction& instruction) {
|
||||
const auto& args = instruction.args();
|
||||
switch (instruction.opcode()) {
|
||||
case Instruction::Op::kGetStaticField: {
|
||||
CHECK(instruction.dest().has_value());
|
||||
CHECK(instruction.dest()->is_variable());
|
||||
CHECK_EQ(0, instruction.args().size());
|
||||
|
||||
Encode21c(::dex::Opcode::OP_SGET,
|
||||
RegisterValue(*instruction.dest()),
|
||||
instruction.index_argument());
|
||||
break;
|
||||
}
|
||||
case Instruction::Op::kSetStaticField: {
|
||||
CHECK(!instruction.dest().has_value());
|
||||
CHECK_EQ(1, args.size());
|
||||
CHECK(args[0].is_variable());
|
||||
|
||||
Encode21c(::dex::Opcode::OP_SPUT, RegisterValue(args[0]), instruction.index_argument());
|
||||
break;
|
||||
}
|
||||
case Instruction::Op::kGetInstanceField: {
|
||||
CHECK(instruction.dest().has_value());
|
||||
CHECK(instruction.dest()->is_variable());
|
||||
CHECK_EQ(1, instruction.args().size());
|
||||
|
||||
Encode22c(::dex::Opcode::OP_IGET,
|
||||
RegisterValue(*instruction.dest()),
|
||||
RegisterValue(args[0]),
|
||||
instruction.index_argument());
|
||||
break;
|
||||
}
|
||||
case Instruction::Op::kSetInstanceField: {
|
||||
CHECK(!instruction.dest().has_value());
|
||||
CHECK_EQ(2, args.size());
|
||||
CHECK(args[0].is_variable());
|
||||
CHECK(args[1].is_variable());
|
||||
|
||||
Encode22c(::dex::Opcode::OP_IPUT,
|
||||
RegisterValue(args[1]),
|
||||
RegisterValue(args[0]),
|
||||
instruction.index_argument());
|
||||
break;
|
||||
}
|
||||
default: { LOG(FATAL) << "Unsupported field operation"; }
|
||||
}
|
||||
}
|
||||
|
||||
size_t MethodBuilder::RegisterValue(const Value& value) const {
|
||||
if (value.is_register()) {
|
||||
return value.value();
|
||||
} else if (value.is_parameter()) {
|
||||
return value.value() + NumRegisters() + kMaxScratchRegisters;
|
||||
}
|
||||
CHECK(false && "Must be either a parameter or a register");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MethodBuilder::BindLabel(const Value& label_id) {
|
||||
CHECK(label_id.is_label());
|
||||
|
||||
LabelData& label = labels_[label_id.value()];
|
||||
CHECK(!label.bound_address.has_value());
|
||||
|
||||
label.bound_address = buffer_.size();
|
||||
|
||||
// patch any forward references to this label.
|
||||
for (const auto& ref : label.references) {
|
||||
buffer_[ref.field_offset] = *label.bound_address - ref.instruction_offset;
|
||||
}
|
||||
// No point keeping these around anymore.
|
||||
label.references.clear();
|
||||
}
|
||||
|
||||
::dex::u2 MethodBuilder::LabelValue(const Value& label_id, size_t instruction_offset,
|
||||
size_t field_offset) {
|
||||
CHECK(label_id.is_label());
|
||||
LabelData& label = labels_[label_id.value()];
|
||||
|
||||
// Short-circuit if the label is already bound.
|
||||
if (label.bound_address.has_value()) {
|
||||
return *label.bound_address - instruction_offset;
|
||||
}
|
||||
|
||||
// Otherwise, save a reference to where we need to back-patch later.
|
||||
label.references.push_front(LabelReference{instruction_offset, field_offset});
|
||||
return 0;
|
||||
}
|
||||
|
||||
const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const std::string& name,
|
||||
Prototype prototype) {
|
||||
MethodDeclData& entry = method_id_map_[{type, name, prototype}];
|
||||
|
||||
if (entry.decl == nullptr) {
|
||||
// This method has not already been declared, so declare it.
|
||||
ir::MethodDecl* decl = dex_file_->Alloc<ir::MethodDecl>();
|
||||
// The method id is the last added method.
|
||||
size_t id = dex_file_->methods.size() - 1;
|
||||
|
||||
ir::String* dex_name{GetOrAddString(name)};
|
||||
decl->name = dex_name;
|
||||
decl->parent = GetOrAddType(type.descriptor());
|
||||
decl->prototype = GetOrEncodeProto(prototype);
|
||||
|
||||
// update the index -> ir node map (see tools/dexter/slicer/dex_ir_builder.cc)
|
||||
auto new_index = dex_file_->methods_indexes.AllocateIndex();
|
||||
auto& ir_node = dex_file_->methods_map[new_index];
|
||||
CHECK(ir_node == nullptr);
|
||||
ir_node = decl;
|
||||
decl->orig_index = decl->index = new_index;
|
||||
|
||||
entry = {id, decl};
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
std::optional<const Prototype> DexBuilder::GetPrototypeByMethodId(size_t method_id) const {
|
||||
for (const auto& entry : method_id_map_) {
|
||||
if (entry.second.id == method_id) {
|
||||
return entry.first.prototype;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ir::Proto* DexBuilder::GetOrEncodeProto(Prototype prototype) {
|
||||
ir::Proto*& ir_proto = proto_map_[prototype];
|
||||
if (ir_proto == nullptr) {
|
||||
ir_proto = prototype.Encode(this);
|
||||
}
|
||||
return ir_proto;
|
||||
}
|
||||
|
||||
} // namespace dex
|
||||
} // namespace startop
|
@ -1,626 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef DEX_BUILDER_H_
|
||||
#define DEX_BUILDER_H_
|
||||
|
||||
#include <array>
|
||||
#include <forward_list>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "android-base/logging.h"
|
||||
|
||||
#include "slicer/dex_bytecode.h"
|
||||
#include "slicer/dex_ir.h"
|
||||
#include "slicer/writer.h"
|
||||
|
||||
namespace startop {
|
||||
namespace dex {
|
||||
|
||||
// TODO: remove this once the dex generation code is complete.
|
||||
void WriteTestDexFile(const std::string& filename);
|
||||
|
||||
//////////////////////////
|
||||
// Forward declarations //
|
||||
//////////////////////////
|
||||
class DexBuilder;
|
||||
|
||||
// Our custom allocator for dex::Writer
|
||||
//
|
||||
// This keeps track of all allocations and ensures they are freed when
|
||||
// TrackingAllocator is destroyed. Pointers to memory allocated by this
|
||||
// allocator must not outlive the allocator.
|
||||
class TrackingAllocator : public ::dex::Writer::Allocator {
|
||||
public:
|
||||
virtual void* Allocate(size_t size);
|
||||
virtual void Free(void* ptr);
|
||||
|
||||
private:
|
||||
std::unordered_map<void*, std::unique_ptr<uint8_t[]>> allocations_;
|
||||
};
|
||||
|
||||
// Represents a DEX type descriptor.
|
||||
//
|
||||
// TODO: add a way to create a descriptor for a reference of a class type.
|
||||
class TypeDescriptor {
|
||||
public:
|
||||
// Named constructors for base type descriptors.
|
||||
static const TypeDescriptor Int();
|
||||
static const TypeDescriptor Void();
|
||||
|
||||
// Creates a type descriptor from a fully-qualified class name. For example, it turns the class
|
||||
// name java.lang.Object into the descriptor Ljava/lang/Object.
|
||||
static TypeDescriptor FromClassname(const std::string& name);
|
||||
|
||||
// Return the full descriptor, such as I or Ljava/lang/Object
|
||||
const std::string& descriptor() const { return descriptor_; }
|
||||
// Return the shorty descriptor, such as I or L
|
||||
std::string short_descriptor() const { return descriptor().substr(0, 1); }
|
||||
|
||||
bool is_object() const { return short_descriptor() == "L"; }
|
||||
|
||||
bool operator<(const TypeDescriptor& rhs) const { return descriptor_ < rhs.descriptor_; }
|
||||
|
||||
private:
|
||||
explicit TypeDescriptor(std::string descriptor) : descriptor_{descriptor} {}
|
||||
|
||||
const std::string descriptor_;
|
||||
};
|
||||
|
||||
// Defines a function signature. For example, Prototype{TypeDescriptor::VOID, TypeDescriptor::Int}
|
||||
// represents the function type (Int) -> Void.
|
||||
class Prototype {
|
||||
public:
|
||||
template <typename... TypeDescriptors>
|
||||
explicit Prototype(TypeDescriptor return_type, TypeDescriptors... param_types)
|
||||
: return_type_{return_type}, param_types_{param_types...} {}
|
||||
|
||||
// Encode this prototype into the dex file.
|
||||
ir::Proto* Encode(DexBuilder* dex) const;
|
||||
|
||||
// Get the shorty descriptor, such as VII for (Int, Int) -> Void
|
||||
std::string Shorty() const;
|
||||
|
||||
const TypeDescriptor& ArgType(size_t index) const;
|
||||
|
||||
bool operator<(const Prototype& rhs) const {
|
||||
return std::make_tuple(return_type_, param_types_) <
|
||||
std::make_tuple(rhs.return_type_, rhs.param_types_);
|
||||
}
|
||||
|
||||
private:
|
||||
const TypeDescriptor return_type_;
|
||||
const std::vector<TypeDescriptor> param_types_;
|
||||
};
|
||||
|
||||
// Represents a DEX register or constant. We separate regular registers and parameters
|
||||
// because we will not know the real parameter id until after all instructions
|
||||
// have been generated.
|
||||
class Value {
|
||||
public:
|
||||
static constexpr Value Local(size_t id) { return Value{id, Kind::kLocalRegister}; }
|
||||
static constexpr Value Parameter(size_t id) { return Value{id, Kind::kParameter}; }
|
||||
static constexpr Value Immediate(size_t value) { return Value{value, Kind::kImmediate}; }
|
||||
static constexpr Value String(size_t value) { return Value{value, Kind::kString}; }
|
||||
static constexpr Value Label(size_t id) { return Value{id, Kind::kLabel}; }
|
||||
static constexpr Value Type(size_t id) { return Value{id, Kind::kType}; }
|
||||
|
||||
bool is_register() const { return kind_ == Kind::kLocalRegister; }
|
||||
bool is_parameter() const { return kind_ == Kind::kParameter; }
|
||||
bool is_variable() const { return is_register() || is_parameter(); }
|
||||
bool is_immediate() const { return kind_ == Kind::kImmediate; }
|
||||
bool is_string() const { return kind_ == Kind::kString; }
|
||||
bool is_label() const { return kind_ == Kind::kLabel; }
|
||||
bool is_type() const { return kind_ == Kind::kType; }
|
||||
|
||||
size_t value() const { return value_; }
|
||||
|
||||
constexpr Value() : value_{0}, kind_{Kind::kInvalid} {}
|
||||
|
||||
private:
|
||||
enum class Kind { kInvalid, kLocalRegister, kParameter, kImmediate, kString, kLabel, kType };
|
||||
|
||||
size_t value_;
|
||||
Kind kind_;
|
||||
|
||||
constexpr Value(size_t value, Kind kind) : value_{value}, kind_{kind} {}
|
||||
};
|
||||
|
||||
// Represents an allocated register returned by MethodBuilder::AllocRegister
|
||||
class LiveRegister {
|
||||
friend class MethodBuilder;
|
||||
|
||||
public:
|
||||
LiveRegister(LiveRegister&& other) : liveness_{other.liveness_}, index_{other.index_} {
|
||||
other.index_ = {};
|
||||
};
|
||||
~LiveRegister() {
|
||||
if (index_.has_value()) {
|
||||
(*liveness_)[*index_] = false;
|
||||
}
|
||||
};
|
||||
|
||||
operator const Value() const { return Value::Local(*index_); }
|
||||
|
||||
private:
|
||||
LiveRegister(std::vector<bool>* liveness, size_t index) : liveness_{liveness}, index_{index} {}
|
||||
|
||||
std::vector<bool>* const liveness_;
|
||||
std::optional<size_t> index_;
|
||||
};
|
||||
|
||||
// A virtual instruction. We convert these to real instructions in MethodBuilder::Encode.
|
||||
// Virtual instructions are needed to keep track of information that is not known until all of the
|
||||
// code is generated. This information includes things like how many local registers are created and
|
||||
// branch target locations.
|
||||
class Instruction {
|
||||
public:
|
||||
// The operation performed by this instruction. These are virtual instructions that do not
|
||||
// correspond exactly to DEX instructions.
|
||||
enum class Op {
|
||||
kBindLabel,
|
||||
kBranchEqz,
|
||||
kBranchNEqz,
|
||||
kCheckCast,
|
||||
kGetInstanceField,
|
||||
kGetStaticField,
|
||||
kInvokeDirect,
|
||||
kInvokeInterface,
|
||||
kInvokeStatic,
|
||||
kInvokeVirtual,
|
||||
kMove,
|
||||
kMoveObject,
|
||||
kNew,
|
||||
kReturn,
|
||||
kReturnObject,
|
||||
kSetInstanceField,
|
||||
kSetStaticField
|
||||
};
|
||||
|
||||
////////////////////////
|
||||
// Named Constructors //
|
||||
////////////////////////
|
||||
|
||||
// For instructions with no return value and no arguments.
|
||||
static inline Instruction OpNoArgs(Op opcode) {
|
||||
return Instruction{opcode, /*index_argument*/ 0, /*dest*/ {}};
|
||||
}
|
||||
// For most instructions, which take some number of arguments and have an optional return value.
|
||||
template <typename... T>
|
||||
static inline Instruction OpWithArgs(Op opcode, std::optional<const Value> dest,
|
||||
const T&... args) {
|
||||
return Instruction{opcode, /*index_argument=*/0, /*result_is_object=*/false, dest, args...};
|
||||
}
|
||||
|
||||
// A cast instruction. Basically, `(type)val`
|
||||
static inline Instruction Cast(Value val, Value type) {
|
||||
CHECK(type.is_type());
|
||||
return OpWithArgs(Op::kCheckCast, val, type);
|
||||
}
|
||||
|
||||
// For method calls.
|
||||
template <typename... T>
|
||||
static inline Instruction InvokeVirtual(size_t index_argument, std::optional<const Value> dest,
|
||||
Value this_arg, T... args) {
|
||||
return Instruction{
|
||||
Op::kInvokeVirtual, index_argument, /*result_is_object=*/false, dest, this_arg, args...};
|
||||
}
|
||||
// Returns an object
|
||||
template <typename... T>
|
||||
static inline Instruction InvokeVirtualObject(size_t index_argument,
|
||||
std::optional<const Value> dest, Value this_arg,
|
||||
const T&... args) {
|
||||
return Instruction{
|
||||
Op::kInvokeVirtual, index_argument, /*result_is_object=*/true, dest, this_arg, args...};
|
||||
}
|
||||
// For direct calls (basically, constructors).
|
||||
template <typename... T>
|
||||
static inline Instruction InvokeDirect(size_t index_argument, std::optional<const Value> dest,
|
||||
Value this_arg, const T&... args) {
|
||||
return Instruction{
|
||||
Op::kInvokeDirect, index_argument, /*result_is_object=*/false, dest, this_arg, args...};
|
||||
}
|
||||
// Returns an object
|
||||
template <typename... T>
|
||||
static inline Instruction InvokeDirectObject(size_t index_argument,
|
||||
std::optional<const Value> dest, Value this_arg,
|
||||
T... args) {
|
||||
return Instruction{
|
||||
Op::kInvokeDirect, index_argument, /*result_is_object=*/true, dest, this_arg, args...};
|
||||
}
|
||||
// For static calls.
|
||||
template <typename... T>
|
||||
static inline Instruction InvokeStatic(size_t index_argument, std::optional<const Value> dest,
|
||||
T... args) {
|
||||
return Instruction{
|
||||
Op::kInvokeStatic, index_argument, /*result_is_object=*/false, dest, args...};
|
||||
}
|
||||
// Returns an object
|
||||
template <typename... T>
|
||||
static inline Instruction InvokeStaticObject(size_t index_argument,
|
||||
std::optional<const Value> dest, T... args) {
|
||||
return Instruction{Op::kInvokeStatic, index_argument, /*result_is_object=*/true, dest, args...};
|
||||
}
|
||||
// For static calls.
|
||||
template <typename... T>
|
||||
static inline Instruction InvokeInterface(size_t index_argument, std::optional<const Value> dest,
|
||||
const T&... args) {
|
||||
return Instruction{
|
||||
Op::kInvokeInterface, index_argument, /*result_is_object=*/false, dest, args...};
|
||||
}
|
||||
|
||||
static inline Instruction GetStaticField(size_t field_id, Value dest) {
|
||||
return Instruction{Op::kGetStaticField, field_id, dest};
|
||||
}
|
||||
|
||||
static inline Instruction SetStaticField(size_t field_id, Value value) {
|
||||
return Instruction{
|
||||
Op::kSetStaticField, field_id, /*result_is_object=*/false, /*dest=*/{}, value};
|
||||
}
|
||||
|
||||
static inline Instruction GetField(size_t field_id, Value dest, Value object) {
|
||||
return Instruction{Op::kGetInstanceField, field_id, /*result_is_object=*/false, dest, object};
|
||||
}
|
||||
|
||||
static inline Instruction SetField(size_t field_id, Value object, Value value) {
|
||||
return Instruction{
|
||||
Op::kSetInstanceField, field_id, /*result_is_object=*/false, /*dest=*/{}, object, value};
|
||||
}
|
||||
|
||||
///////////////
|
||||
// Accessors //
|
||||
///////////////
|
||||
|
||||
Op opcode() const { return opcode_; }
|
||||
size_t index_argument() const { return index_argument_; }
|
||||
bool result_is_object() const { return result_is_object_; }
|
||||
const std::optional<const Value>& dest() const { return dest_; }
|
||||
const std::vector<const Value>& args() const { return args_; }
|
||||
|
||||
private:
|
||||
inline Instruction(Op opcode, size_t index_argument, std::optional<const Value> dest)
|
||||
: opcode_{opcode},
|
||||
index_argument_{index_argument},
|
||||
result_is_object_{false},
|
||||
dest_{dest},
|
||||
args_{} {}
|
||||
|
||||
template <typename... T>
|
||||
inline Instruction(Op opcode, size_t index_argument, bool result_is_object,
|
||||
std::optional<const Value> dest, const T&... args)
|
||||
: opcode_{opcode},
|
||||
index_argument_{index_argument},
|
||||
result_is_object_{result_is_object},
|
||||
dest_{dest},
|
||||
args_{args...} {}
|
||||
|
||||
const Op opcode_;
|
||||
// The index of the method to invoke, for kInvokeVirtual and similar opcodes.
|
||||
const size_t index_argument_{0};
|
||||
const bool result_is_object_;
|
||||
const std::optional<const Value> dest_;
|
||||
const std::vector<const Value> args_;
|
||||
};
|
||||
|
||||
// Needed for CHECK_EQ, DCHECK_EQ, etc.
|
||||
std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode);
|
||||
|
||||
// Keeps track of information needed to manipulate or call a method.
|
||||
struct MethodDeclData {
|
||||
size_t id;
|
||||
ir::MethodDecl* decl;
|
||||
};
|
||||
|
||||
// Tools to help build methods and their bodies.
|
||||
class MethodBuilder {
|
||||
public:
|
||||
MethodBuilder(DexBuilder* dex, ir::Class* class_def, ir::MethodDecl* decl);
|
||||
|
||||
// Encode the method into DEX format.
|
||||
ir::EncodedMethod* Encode();
|
||||
|
||||
// Create a new register to be used to storing values.
|
||||
LiveRegister AllocRegister();
|
||||
|
||||
Value MakeLabel();
|
||||
|
||||
/////////////////////////////////
|
||||
// Instruction builder methods //
|
||||
/////////////////////////////////
|
||||
|
||||
void AddInstruction(Instruction instruction);
|
||||
|
||||
// return-void
|
||||
void BuildReturn();
|
||||
void BuildReturn(Value src, bool is_object = false);
|
||||
// const/4
|
||||
void BuildConst4(Value target, int value);
|
||||
void BuildConstString(Value target, const std::string& value);
|
||||
template <typename... T>
|
||||
void BuildNew(Value target, TypeDescriptor type, Prototype constructor, const T&... args);
|
||||
|
||||
// TODO: add builders for more instructions
|
||||
|
||||
DexBuilder* dex_file() const { return dex_; }
|
||||
|
||||
private:
|
||||
void EncodeInstructions();
|
||||
void EncodeInstruction(const Instruction& instruction);
|
||||
|
||||
// Encodes a return instruction. For instructions with no return value, the opcode field is
|
||||
// ignored. Otherwise, this specifies which return instruction will be used (return,
|
||||
// return-object, etc.)
|
||||
void EncodeReturn(const Instruction& instruction, ::dex::Opcode opcode);
|
||||
|
||||
void EncodeMove(const Instruction& instruction);
|
||||
void EncodeInvoke(const Instruction& instruction, ::dex::Opcode opcode);
|
||||
void EncodeBranch(::dex::Opcode op, const Instruction& instruction);
|
||||
void EncodeNew(const Instruction& instruction);
|
||||
void EncodeCast(const Instruction& instruction);
|
||||
void EncodeFieldOp(const Instruction& instruction);
|
||||
|
||||
// Low-level instruction format encoding. See
|
||||
// https://source.android.com/devices/tech/dalvik/instruction-formats for documentation of
|
||||
// formats.
|
||||
|
||||
inline uint8_t ToBits(::dex::Opcode opcode) {
|
||||
static_assert(sizeof(uint8_t) == sizeof(::dex::Opcode));
|
||||
return static_cast<uint8_t>(opcode);
|
||||
}
|
||||
|
||||
inline void Encode10x(::dex::Opcode opcode) {
|
||||
// 00|op
|
||||
static_assert(sizeof(uint8_t) == sizeof(::dex::Opcode));
|
||||
buffer_.push_back(ToBits(opcode));
|
||||
}
|
||||
|
||||
inline void Encode11x(::dex::Opcode opcode, uint8_t a) {
|
||||
// aa|op
|
||||
buffer_.push_back((a << 8) | ToBits(opcode));
|
||||
}
|
||||
|
||||
inline void Encode11n(::dex::Opcode opcode, uint8_t a, int8_t b) {
|
||||
// b|a|op
|
||||
|
||||
// Make sure the fields are in bounds (4 bits for a, 4 bits for b).
|
||||
CHECK_LT(a, 16);
|
||||
CHECK_LE(-8, b);
|
||||
CHECK_LT(b, 8);
|
||||
|
||||
buffer_.push_back(((b & 0xf) << 12) | (a << 8) | ToBits(opcode));
|
||||
}
|
||||
|
||||
inline void Encode21c(::dex::Opcode opcode, uint8_t a, uint16_t b) {
|
||||
// aa|op|bbbb
|
||||
buffer_.push_back((a << 8) | ToBits(opcode));
|
||||
buffer_.push_back(b);
|
||||
}
|
||||
|
||||
inline void Encode22c(::dex::Opcode opcode, uint8_t a, uint8_t b, uint16_t c) {
|
||||
// b|a|op|bbbb
|
||||
CHECK(IsShortRegister(a));
|
||||
CHECK(IsShortRegister(b));
|
||||
buffer_.push_back((b << 12) | (a << 8) | ToBits(opcode));
|
||||
buffer_.push_back(c);
|
||||
}
|
||||
|
||||
inline void Encode32x(::dex::Opcode opcode, uint16_t a, uint16_t b) {
|
||||
buffer_.push_back(ToBits(opcode));
|
||||
buffer_.push_back(a);
|
||||
buffer_.push_back(b);
|
||||
}
|
||||
|
||||
inline void Encode35c(::dex::Opcode opcode, size_t a, uint16_t b, uint8_t c, uint8_t d,
|
||||
uint8_t e, uint8_t f, uint8_t g) {
|
||||
// a|g|op|bbbb|f|e|d|c
|
||||
|
||||
CHECK_LE(a, 5);
|
||||
CHECK(IsShortRegister(c));
|
||||
CHECK(IsShortRegister(d));
|
||||
CHECK(IsShortRegister(e));
|
||||
CHECK(IsShortRegister(f));
|
||||
CHECK(IsShortRegister(g));
|
||||
buffer_.push_back((a << 12) | (g << 8) | ToBits(opcode));
|
||||
buffer_.push_back(b);
|
||||
buffer_.push_back((f << 12) | (e << 8) | (d << 4) | c);
|
||||
}
|
||||
|
||||
inline void Encode3rc(::dex::Opcode opcode, size_t a, uint16_t b, uint16_t c) {
|
||||
CHECK_LE(a, 255);
|
||||
buffer_.push_back((a << 8) | ToBits(opcode));
|
||||
buffer_.push_back(b);
|
||||
buffer_.push_back(c);
|
||||
}
|
||||
|
||||
static constexpr bool IsShortRegister(size_t register_value) { return register_value < 16; }
|
||||
|
||||
// Returns an array of num_regs scratch registers. These are guaranteed to be
|
||||
// contiguous, so they are suitable for the invoke-*/range instructions.
|
||||
template <int num_regs>
|
||||
std::array<Value, num_regs> GetScratchRegisters() const {
|
||||
static_assert(num_regs <= kMaxScratchRegisters);
|
||||
std::array<Value, num_regs> regs;
|
||||
for (size_t i = 0; i < num_regs; ++i) {
|
||||
regs[i] = std::move(Value::Local(NumRegisters() + i));
|
||||
}
|
||||
return regs;
|
||||
}
|
||||
|
||||
// Converts a register or parameter to its DEX register number.
|
||||
size_t RegisterValue(const Value& value) const;
|
||||
|
||||
// Sets a label's address to the current position in the instruction buffer. If there are any
|
||||
// forward references to the label, this function will back-patch them.
|
||||
void BindLabel(const Value& label);
|
||||
|
||||
// Returns the offset of the label relative to the given instruction offset. If the label is not
|
||||
// bound, a reference will be saved and it will automatically be patched when the label is bound.
|
||||
::dex::u2 LabelValue(const Value& label, size_t instruction_offset, size_t field_offset);
|
||||
|
||||
DexBuilder* dex_;
|
||||
ir::Class* class_;
|
||||
ir::MethodDecl* decl_;
|
||||
|
||||
// A list of the instructions we will eventually encode.
|
||||
std::vector<Instruction> instructions_;
|
||||
|
||||
// A buffer to hold instructions that have been encoded.
|
||||
std::vector<::dex::u2> buffer_;
|
||||
|
||||
// We create some scratch registers for when we have to shuffle registers
|
||||
// around to make legal DEX code.
|
||||
static constexpr size_t kMaxScratchRegisters = 5;
|
||||
|
||||
size_t NumRegisters() const {
|
||||
return register_liveness_.size();
|
||||
}
|
||||
|
||||
// Stores information needed to back-patch a label once it is bound. We need to know the start of
|
||||
// the instruction that refers to the label, and the offset to where the actual label value should
|
||||
// go.
|
||||
struct LabelReference {
|
||||
size_t instruction_offset;
|
||||
size_t field_offset;
|
||||
};
|
||||
|
||||
struct LabelData {
|
||||
std::optional<size_t> bound_address;
|
||||
std::forward_list<LabelReference> references;
|
||||
};
|
||||
|
||||
std::vector<LabelData> labels_;
|
||||
|
||||
// During encoding, keep track of the largest number of arguments needed, so we can use it for our
|
||||
// outs count
|
||||
size_t max_args_{0};
|
||||
|
||||
std::vector<bool> register_liveness_;
|
||||
};
|
||||
|
||||
// A helper to build class definitions.
|
||||
class ClassBuilder {
|
||||
public:
|
||||
ClassBuilder(DexBuilder* parent, const std::string& name, ir::Class* class_def);
|
||||
|
||||
void set_source_file(const std::string& source);
|
||||
|
||||
// Create a method with the given name and prototype. The returned MethodBuilder can be used to
|
||||
// fill in the method body.
|
||||
MethodBuilder CreateMethod(const std::string& name, Prototype prototype);
|
||||
|
||||
private:
|
||||
DexBuilder* const parent_;
|
||||
const TypeDescriptor type_descriptor_;
|
||||
ir::Class* const class_;
|
||||
};
|
||||
|
||||
// Builds Dex files from scratch.
|
||||
class DexBuilder {
|
||||
public:
|
||||
DexBuilder();
|
||||
|
||||
// Create an in-memory image of the DEX file that can either be loaded directly or written to a
|
||||
// file.
|
||||
slicer::MemView CreateImage();
|
||||
|
||||
template <typename T>
|
||||
T* Alloc() {
|
||||
return dex_file_->Alloc<T>();
|
||||
}
|
||||
|
||||
// Find the ir::String that matches the given string, creating it if it does not exist.
|
||||
ir::String* GetOrAddString(const std::string& string);
|
||||
// Create a new class of the given name.
|
||||
ClassBuilder MakeClass(const std::string& name);
|
||||
|
||||
// Add a type for the given descriptor, or return the existing one if it already exists.
|
||||
// See the TypeDescriptor class for help generating these. GetOrAddType can be used to declare
|
||||
// imported classes.
|
||||
ir::Type* GetOrAddType(const std::string& descriptor);
|
||||
inline ir::Type* GetOrAddType(TypeDescriptor descriptor) {
|
||||
return GetOrAddType(descriptor.descriptor());
|
||||
}
|
||||
|
||||
ir::FieldDecl* GetOrAddField(TypeDescriptor parent, const std::string& name, TypeDescriptor type);
|
||||
|
||||
// Returns the method id for the method, creating it if it has not been created yet.
|
||||
const MethodDeclData& GetOrDeclareMethod(TypeDescriptor type, const std::string& name,
|
||||
Prototype prototype);
|
||||
|
||||
std::optional<const Prototype> GetPrototypeByMethodId(size_t method_id) const;
|
||||
|
||||
private:
|
||||
// Looks up the ir::Proto* corresponding to this given prototype, or creates one if it does not
|
||||
// exist.
|
||||
ir::Proto* GetOrEncodeProto(Prototype prototype);
|
||||
|
||||
std::shared_ptr<ir::DexFile> dex_file_;
|
||||
|
||||
// allocator_ is needed to be able to encode the image.
|
||||
TrackingAllocator allocator_;
|
||||
|
||||
// We'll need to allocate buffers for all of the encoded strings we create. This is where we store
|
||||
// all of them.
|
||||
std::vector<std::unique_ptr<uint8_t[]>> string_data_;
|
||||
|
||||
// Keep track of what types we've defined so we can look them up later.
|
||||
std::unordered_map<std::string, ir::Type*> types_by_descriptor_;
|
||||
|
||||
struct MethodDescriptor {
|
||||
TypeDescriptor type;
|
||||
std::string name;
|
||||
Prototype prototype;
|
||||
|
||||
inline bool operator<(const MethodDescriptor& rhs) const {
|
||||
return std::make_tuple(type, name, prototype) <
|
||||
std::make_tuple(rhs.type, rhs.name, rhs.prototype);
|
||||
}
|
||||
};
|
||||
|
||||
// Maps method declarations to their method index. This is needed to encode references to them.
|
||||
// When we go to actually write the DEX file, slicer will re-assign these after correctly sorting
|
||||
// the methods list.
|
||||
std::map<MethodDescriptor, MethodDeclData> method_id_map_;
|
||||
|
||||
// Keep track of what strings we've defined so we can look them up later.
|
||||
std::unordered_map<std::string, ir::String*> strings_;
|
||||
|
||||
// Keep track of already-encoded protos.
|
||||
std::map<Prototype, ir::Proto*> proto_map_;
|
||||
|
||||
// Keep track of fields that have been declared
|
||||
std::map<std::tuple<TypeDescriptor, std::string>, ir::FieldDecl*> field_decls_by_key_;
|
||||
};
|
||||
|
||||
template <typename... T>
|
||||
void MethodBuilder::BuildNew(Value target, TypeDescriptor type, Prototype constructor,
|
||||
const T&... args) {
|
||||
MethodDeclData constructor_data{dex_->GetOrDeclareMethod(type, "<init>", constructor)};
|
||||
// allocate the object
|
||||
ir::Type* type_def = dex_->GetOrAddType(type.descriptor());
|
||||
AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kNew, target, Value::Type(type_def->orig_index)));
|
||||
// call the constructor
|
||||
AddInstruction(Instruction::InvokeDirect(constructor_data.id, /*dest=*/{}, target, args...));
|
||||
};
|
||||
|
||||
} // namespace dex
|
||||
} // namespace startop
|
||||
|
||||
#endif // DEX_BUILDER_H_
|
@ -1,68 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2018 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
// all of the 'license_kinds' from "frameworks_base_license"
|
||||
// to get the below license kinds:
|
||||
// SPDX-license-identifier-Apache-2.0
|
||||
default_applicable_licenses: ["frameworks_base_license"],
|
||||
}
|
||||
|
||||
genrule {
|
||||
name: "generate_compiled_layout1",
|
||||
tools: [":viewcompiler"],
|
||||
cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test",
|
||||
srcs: ["res/layout/layout1.xml"],
|
||||
out: [
|
||||
"layout1.dex",
|
||||
],
|
||||
}
|
||||
|
||||
genrule {
|
||||
name: "generate_compiled_layout2",
|
||||
tools: [":viewcompiler"],
|
||||
cmd: "$(location :viewcompiler) $(in) --dex --out $(out) --package android.startop.test",
|
||||
srcs: ["res/layout/layout2.xml"],
|
||||
out: [
|
||||
"layout2.dex",
|
||||
],
|
||||
}
|
||||
|
||||
android_test {
|
||||
name: "dex-builder-test",
|
||||
srcs: [
|
||||
"src/android/startop/test/DexBuilderTest.java",
|
||||
"src/android/startop/test/LayoutCompilerTest.java",
|
||||
"src/android/startop/test/TestClass.java",
|
||||
],
|
||||
sdk_version: "current",
|
||||
data: [
|
||||
":generate_dex_testcases",
|
||||
":generate_compiled_layout1",
|
||||
":generate_compiled_layout2",
|
||||
],
|
||||
static_libs: [
|
||||
"androidx.test.core",
|
||||
"androidx.test.runner",
|
||||
"junit",
|
||||
],
|
||||
manifest: "AndroidManifest.xml",
|
||||
resource_dirs: ["res"],
|
||||
test_config: "AndroidTest.xml",
|
||||
test_suites: ["general-tests"],
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2018 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android.startop.test" >
|
||||
|
||||
<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
|
||||
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="android.startop.test"
|
||||
android:label="DexBuilder Tests"/>
|
||||
|
||||
</manifest>
|
@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2018 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<configuration description="Runs DexBuilder Tests.">
|
||||
<option name="test-suite-tag" value="apct" />
|
||||
<option name="test-suite-tag" value="apct-instrumentation" />
|
||||
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
|
||||
<option name="cleanup-apks" value="true" />
|
||||
<option name="test-file-name" value="dex-builder-test.apk" />
|
||||
</target_preparer>
|
||||
|
||||
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
|
||||
<option name="cleanup" value="true" />
|
||||
<option name="push" value="trivial.dex->/data/local/tmp/dex-builder-test/trivial.dex" />
|
||||
<option name="push" value="simple.dex->/data/local/tmp/dex-builder-test/simple.dex" />
|
||||
<option name="push" value="layout1.dex->/data/local/tmp/dex-builder-test/layout1.dex" />
|
||||
<option name="push" value="layout2.dex->/data/local/tmp/dex-builder-test/layout2.dex" />
|
||||
</target_preparer>
|
||||
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
|
||||
<option name="package" value="android.startop.test" />
|
||||
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
|
||||
</test>
|
||||
</configuration>
|
@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</LinearLayout>
|
@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Button" />
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Button" />
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Button" />
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Button" />
|
||||
</TableRow>
|
||||
|
||||
</TableRow>
|
||||
</TableRow>
|
||||
</TableRow>
|
||||
</LinearLayout>
|
@ -1,213 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package android.startop.test;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import dalvik.system.PathClassLoader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
// Adding tests here requires changes in several other places. See README.md in
|
||||
// the view_compiler directory for more information.
|
||||
public final class DexBuilderTest {
|
||||
static ClassLoader loadDexFile(String filename) throws Exception {
|
||||
return new PathClassLoader("/data/local/tmp/dex-builder-test/" + filename,
|
||||
DexBuilderTest.class.getClassLoader());
|
||||
}
|
||||
|
||||
public void hello() {}
|
||||
|
||||
@Test
|
||||
public void loadTrivialDex() throws Exception {
|
||||
ClassLoader loader = loadDexFile("trivial.dex");
|
||||
loader.loadClass("android.startop.test.testcases.Trivial");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void return5() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("return5");
|
||||
Assert.assertEquals(5, method.invoke(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnInteger5() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("returnInteger5");
|
||||
Assert.assertEquals(5, method.invoke(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnParam() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("returnParam", int.class);
|
||||
Assert.assertEquals(5, method.invoke(null, 5));
|
||||
Assert.assertEquals(42, method.invoke(null, 42));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnStringLength() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("returnStringLength", String.class);
|
||||
Assert.assertEquals(13, method.invoke(null, "Hello, World!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnIfZero() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("returnIfZero", int.class);
|
||||
Assert.assertEquals(5, method.invoke(null, 0));
|
||||
Assert.assertEquals(3, method.invoke(null, 17));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnIfNotZero() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("returnIfNotZero", int.class);
|
||||
Assert.assertEquals(3, method.invoke(null, 0));
|
||||
Assert.assertEquals(5, method.invoke(null, 17));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void backwardsBranch() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("backwardsBranch");
|
||||
Assert.assertEquals(2, method.invoke(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnNull() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("returnNull");
|
||||
Assert.assertEquals(null, method.invoke(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void makeString() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("makeString");
|
||||
Assert.assertEquals("Hello, World!", method.invoke(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnStringIfZeroAB() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("returnStringIfZeroAB", int.class);
|
||||
Assert.assertEquals("a", method.invoke(null, 0));
|
||||
Assert.assertEquals("b", method.invoke(null, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnStringIfZeroBA() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("returnStringIfZeroBA", int.class);
|
||||
Assert.assertEquals("b", method.invoke(null, 0));
|
||||
Assert.assertEquals("a", method.invoke(null, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeStaticReturnObject() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("invokeStaticReturnObject", int.class, int.class);
|
||||
Assert.assertEquals("10", method.invoke(null, 10, 10));
|
||||
Assert.assertEquals("a", method.invoke(null, 10, 16));
|
||||
Assert.assertEquals("5", method.invoke(null, 5, 16));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeVirtualReturnObject() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("invokeVirtualReturnObject", String.class, int.class);
|
||||
Assert.assertEquals("bc", method.invoke(null, "abc", 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void castObjectToString() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("castObjectToString", Object.class);
|
||||
Assert.assertEquals("abc", method.invoke(null, "abc"));
|
||||
boolean castFailed = false;
|
||||
try {
|
||||
method.invoke(null, 5);
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getCause() instanceof ClassCastException) {
|
||||
castFailed = true;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
Assert.assertTrue(castFailed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readStaticField() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("readStaticField");
|
||||
TestClass.staticInteger = 5;
|
||||
Assert.assertEquals(5, method.invoke(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setStaticField() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("setStaticField");
|
||||
TestClass.staticInteger = 5;
|
||||
method.invoke(null);
|
||||
Assert.assertEquals(7, TestClass.staticInteger);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInstanceField() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("readInstanceField", TestClass.class);
|
||||
TestClass obj = new TestClass();
|
||||
obj.instanceField = 5;
|
||||
Assert.assertEquals(5, method.invoke(null, obj));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setInstanceField() throws Exception {
|
||||
ClassLoader loader = loadDexFile("simple.dex");
|
||||
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
|
||||
Method method = clazz.getMethod("setInstanceField", TestClass.class);
|
||||
TestClass obj = new TestClass();
|
||||
obj.instanceField = 5;
|
||||
method.invoke(null, obj);
|
||||
Assert.assertEquals(7, obj.instanceField);
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package android.startop.test;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import android.view.View;
|
||||
import dalvik.system.PathClassLoader;
|
||||
import java.lang.reflect.Method;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
// Adding tests here requires changes in several other places. See README.md in
|
||||
// the view_compiler directory for more information.
|
||||
public class LayoutCompilerTest {
|
||||
static ClassLoader loadDexFile(String filename) throws Exception {
|
||||
return new PathClassLoader("/data/local/tmp/dex-builder-test/" + filename,
|
||||
ClassLoader.getSystemClassLoader());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAndInflateLayout1() throws Exception {
|
||||
ClassLoader dex_file = loadDexFile("layout1.dex");
|
||||
Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
|
||||
Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class);
|
||||
Context context = InstrumentationRegistry.getTargetContext();
|
||||
layout1.invoke(null, context, R.layout.layout1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAndInflateLayout2() throws Exception {
|
||||
ClassLoader dex_file = loadDexFile("layout2.dex");
|
||||
Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
|
||||
Method layout1 = compiled_view.getMethod("layout2", Context.class, int.class);
|
||||
Context context = InstrumentationRegistry.getTargetContext();
|
||||
layout1.invoke(null, context, R.layout.layout1);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package android.startop.test;
|
||||
|
||||
/**
|
||||
* A simple class to help test DexBuilder.
|
||||
*/
|
||||
public final class TestClass {
|
||||
public static int staticInteger;
|
||||
|
||||
public int instanceField;
|
||||
}
|
@ -1,208 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "dex_layout_compiler.h"
|
||||
#include "layout_validation.h"
|
||||
|
||||
#include "android-base/stringprintf.h"
|
||||
|
||||
namespace startop {
|
||||
|
||||
using android::base::StringPrintf;
|
||||
using dex::Instruction;
|
||||
using dex::LiveRegister;
|
||||
using dex::Prototype;
|
||||
using dex::TypeDescriptor;
|
||||
using dex::Value;
|
||||
|
||||
namespace {
|
||||
// TODO: these are a bunch of static initializers, which we should avoid. See if
|
||||
// we can make them constexpr.
|
||||
const TypeDescriptor kAttributeSet = TypeDescriptor::FromClassname("android.util.AttributeSet");
|
||||
const TypeDescriptor kContext = TypeDescriptor::FromClassname("android.content.Context");
|
||||
const TypeDescriptor kLayoutInflater = TypeDescriptor::FromClassname("android.view.LayoutInflater");
|
||||
const TypeDescriptor kResources = TypeDescriptor::FromClassname("android.content.res.Resources");
|
||||
const TypeDescriptor kString = TypeDescriptor::FromClassname("java.lang.String");
|
||||
const TypeDescriptor kView = TypeDescriptor::FromClassname("android.view.View");
|
||||
const TypeDescriptor kViewGroup = TypeDescriptor::FromClassname("android.view.ViewGroup");
|
||||
const TypeDescriptor kXmlResourceParser =
|
||||
TypeDescriptor::FromClassname("android.content.res.XmlResourceParser");
|
||||
} // namespace
|
||||
|
||||
DexViewBuilder::DexViewBuilder(dex::MethodBuilder* method)
|
||||
: method_{method},
|
||||
context_{Value::Parameter(0)},
|
||||
resid_{Value::Parameter(1)},
|
||||
inflater_{method->AllocRegister()},
|
||||
xml_{method->AllocRegister()},
|
||||
attrs_{method->AllocRegister()},
|
||||
classname_tmp_{method->AllocRegister()},
|
||||
xml_next_{method->dex_file()->GetOrDeclareMethod(kXmlResourceParser, "next",
|
||||
Prototype{TypeDescriptor::Int()})},
|
||||
try_create_view_{method->dex_file()->GetOrDeclareMethod(
|
||||
kLayoutInflater, "tryCreateView",
|
||||
Prototype{kView, kView, kString, kContext, kAttributeSet})},
|
||||
generate_layout_params_{method->dex_file()->GetOrDeclareMethod(
|
||||
kViewGroup, "generateLayoutParams",
|
||||
Prototype{TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams"),
|
||||
kAttributeSet})},
|
||||
add_view_{method->dex_file()->GetOrDeclareMethod(
|
||||
kViewGroup, "addView",
|
||||
Prototype{TypeDescriptor::Void(),
|
||||
kView,
|
||||
TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams")})} {}
|
||||
|
||||
void DexViewBuilder::BuildGetLayoutInflater(Value dest) {
|
||||
// dest = LayoutInflater.from(context);
|
||||
auto layout_inflater_from = method_->dex_file()->GetOrDeclareMethod(
|
||||
kLayoutInflater, "from", Prototype{kLayoutInflater, kContext});
|
||||
method_->AddInstruction(Instruction::InvokeStaticObject(layout_inflater_from.id, dest, context_));
|
||||
}
|
||||
|
||||
void DexViewBuilder::BuildGetResources(Value dest) {
|
||||
// dest = context.getResources();
|
||||
auto get_resources =
|
||||
method_->dex_file()->GetOrDeclareMethod(kContext, "getResources", Prototype{kResources});
|
||||
method_->AddInstruction(Instruction::InvokeVirtualObject(get_resources.id, dest, context_));
|
||||
}
|
||||
|
||||
void DexViewBuilder::BuildGetLayoutResource(Value dest, Value resources, Value resid) {
|
||||
// dest = resources.getLayout(resid);
|
||||
auto get_layout = method_->dex_file()->GetOrDeclareMethod(
|
||||
kResources, "getLayout", Prototype{kXmlResourceParser, TypeDescriptor::Int()});
|
||||
method_->AddInstruction(Instruction::InvokeVirtualObject(get_layout.id, dest, resources, resid));
|
||||
}
|
||||
|
||||
void DexViewBuilder::BuildLayoutResourceToAttributeSet(dex::Value dest,
|
||||
dex::Value layout_resource) {
|
||||
// dest = Xml.asAttributeSet(layout_resource);
|
||||
auto as_attribute_set = method_->dex_file()->GetOrDeclareMethod(
|
||||
TypeDescriptor::FromClassname("android.util.Xml"),
|
||||
"asAttributeSet",
|
||||
Prototype{kAttributeSet, TypeDescriptor::FromClassname("org.xmlpull.v1.XmlPullParser")});
|
||||
method_->AddInstruction(
|
||||
Instruction::InvokeStaticObject(as_attribute_set.id, dest, layout_resource));
|
||||
}
|
||||
|
||||
void DexViewBuilder::BuildXmlNext() {
|
||||
// xml_.next();
|
||||
method_->AddInstruction(Instruction::InvokeInterface(xml_next_.id, {}, xml_));
|
||||
}
|
||||
|
||||
void DexViewBuilder::Start() {
|
||||
BuildGetLayoutInflater(/*dest=*/inflater_);
|
||||
BuildGetResources(/*dest=*/xml_);
|
||||
BuildGetLayoutResource(/*dest=*/xml_, /*resources=*/xml_, resid_);
|
||||
BuildLayoutResourceToAttributeSet(/*dest=*/attrs_, /*layout_resource=*/xml_);
|
||||
|
||||
// Advance past start document tag
|
||||
BuildXmlNext();
|
||||
}
|
||||
|
||||
void DexViewBuilder::Finish() {}
|
||||
|
||||
namespace {
|
||||
std::string ResolveName(const std::string& name) {
|
||||
if (name == "View") return "android.view.View";
|
||||
if (name == "ViewGroup") return "android.view.ViewGroup";
|
||||
if (name.find('.') == std::string::npos) {
|
||||
return StringPrintf("android.widget.%s", name.c_str());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void DexViewBuilder::BuildTryCreateView(Value dest, Value parent, Value classname) {
|
||||
// dest = inflater_.tryCreateView(parent, classname, context_, attrs_);
|
||||
method_->AddInstruction(Instruction::InvokeVirtualObject(
|
||||
try_create_view_.id, dest, inflater_, parent, classname, context_, attrs_));
|
||||
}
|
||||
|
||||
void DexViewBuilder::StartView(const std::string& name, bool is_viewgroup) {
|
||||
bool const is_root_view = view_stack_.empty();
|
||||
|
||||
// Advance to start tag
|
||||
BuildXmlNext();
|
||||
|
||||
LiveRegister view = AcquireRegister();
|
||||
// try to create the view using the factories
|
||||
method_->BuildConstString(classname_tmp_,
|
||||
name); // TODO: the need to fully qualify the classname
|
||||
if (is_root_view) {
|
||||
LiveRegister null = AcquireRegister();
|
||||
method_->BuildConst4(null, 0);
|
||||
BuildTryCreateView(/*dest=*/view, /*parent=*/null, classname_tmp_);
|
||||
} else {
|
||||
BuildTryCreateView(/*dest=*/view, /*parent=*/GetCurrentView(), classname_tmp_);
|
||||
}
|
||||
auto label = method_->MakeLabel();
|
||||
// branch if not null
|
||||
method_->AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kBranchNEqz, /*dest=*/{}, view, label));
|
||||
|
||||
// If null, create the class directly.
|
||||
method_->BuildNew(view,
|
||||
TypeDescriptor::FromClassname(ResolveName(name)),
|
||||
Prototype{TypeDescriptor::Void(), kContext, kAttributeSet},
|
||||
context_,
|
||||
attrs_);
|
||||
|
||||
method_->AddInstruction(Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, label));
|
||||
|
||||
if (is_viewgroup) {
|
||||
// Cast to a ViewGroup so we can add children later.
|
||||
const ir::Type* view_group_def = method_->dex_file()->GetOrAddType(kViewGroup.descriptor());
|
||||
method_->AddInstruction(Instruction::Cast(view, Value::Type(view_group_def->orig_index)));
|
||||
}
|
||||
|
||||
if (!is_root_view) {
|
||||
// layout_params = parent.generateLayoutParams(attrs);
|
||||
LiveRegister layout_params{AcquireRegister()};
|
||||
method_->AddInstruction(Instruction::InvokeVirtualObject(
|
||||
generate_layout_params_.id, layout_params, GetCurrentView(), attrs_));
|
||||
view_stack_.push_back({std::move(view), std::move(layout_params)});
|
||||
} else {
|
||||
view_stack_.push_back({std::move(view), {}});
|
||||
}
|
||||
}
|
||||
|
||||
void DexViewBuilder::FinishView() {
|
||||
if (view_stack_.size() == 1) {
|
||||
method_->BuildReturn(GetCurrentView(), /*is_object=*/true);
|
||||
} else {
|
||||
// parent.add(view, layout_params)
|
||||
method_->AddInstruction(Instruction::InvokeVirtual(
|
||||
add_view_.id, /*dest=*/{}, GetParentView(), GetCurrentView(), GetCurrentLayoutParams()));
|
||||
// xml.next(); // end tag
|
||||
method_->AddInstruction(Instruction::InvokeInterface(xml_next_.id, {}, xml_));
|
||||
}
|
||||
PopViewStack();
|
||||
}
|
||||
|
||||
LiveRegister DexViewBuilder::AcquireRegister() { return method_->AllocRegister(); }
|
||||
|
||||
Value DexViewBuilder::GetCurrentView() const { return view_stack_.back().view; }
|
||||
Value DexViewBuilder::GetCurrentLayoutParams() const {
|
||||
return view_stack_.back().layout_params.value();
|
||||
}
|
||||
Value DexViewBuilder::GetParentView() const { return view_stack_[view_stack_.size() - 2].view; }
|
||||
|
||||
void DexViewBuilder::PopViewStack() {
|
||||
// Unconditionally release the view register.
|
||||
view_stack_.pop_back();
|
||||
}
|
||||
|
||||
} // namespace startop
|
@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef DEX_LAYOUT_COMPILER_H_
|
||||
#define DEX_LAYOUT_COMPILER_H_
|
||||
|
||||
#include "dex_builder.h"
|
||||
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace startop {
|
||||
|
||||
// This visitor does the actual view compilation, using a supplied builder.
|
||||
template <typename Builder>
|
||||
class LayoutCompilerVisitor {
|
||||
public:
|
||||
explicit LayoutCompilerVisitor(Builder* builder) : builder_{builder} {}
|
||||
|
||||
void VisitStartDocument() { builder_->Start(); }
|
||||
void VisitEndDocument() { builder_->Finish(); }
|
||||
void VisitStartTag(const std::u16string& name) {
|
||||
parent_stack_.push_back(ViewEntry{
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.to_bytes(name), {}});
|
||||
}
|
||||
void VisitEndTag() {
|
||||
auto entry = parent_stack_.back();
|
||||
parent_stack_.pop_back();
|
||||
|
||||
if (parent_stack_.empty()) {
|
||||
GenerateCode(entry);
|
||||
} else {
|
||||
parent_stack_.back().children.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct ViewEntry {
|
||||
std::string name;
|
||||
std::vector<ViewEntry> children;
|
||||
};
|
||||
|
||||
void GenerateCode(const ViewEntry& view) {
|
||||
builder_->StartView(view.name, !view.children.empty());
|
||||
for (const auto& child : view.children) {
|
||||
GenerateCode(child);
|
||||
}
|
||||
builder_->FinishView();
|
||||
}
|
||||
|
||||
Builder* builder_;
|
||||
|
||||
std::vector<ViewEntry> parent_stack_;
|
||||
};
|
||||
|
||||
class DexViewBuilder {
|
||||
public:
|
||||
DexViewBuilder(dex::MethodBuilder* method);
|
||||
|
||||
void Start();
|
||||
void Finish();
|
||||
void StartView(const std::string& name, bool is_viewgroup);
|
||||
void FinishView();
|
||||
|
||||
private:
|
||||
// Accessors for the stack of views that are under construction.
|
||||
dex::LiveRegister AcquireRegister();
|
||||
dex::Value GetCurrentView() const;
|
||||
dex::Value GetCurrentLayoutParams() const;
|
||||
dex::Value GetParentView() const;
|
||||
void PopViewStack();
|
||||
|
||||
// Methods to simplify building different code fragments.
|
||||
void BuildGetLayoutInflater(dex::Value dest);
|
||||
void BuildGetResources(dex::Value dest);
|
||||
void BuildGetLayoutResource(dex::Value dest, dex::Value resources, dex::Value resid);
|
||||
void BuildLayoutResourceToAttributeSet(dex::Value dest, dex::Value layout_resource);
|
||||
void BuildXmlNext();
|
||||
void BuildTryCreateView(dex::Value dest, dex::Value parent, dex::Value classname);
|
||||
|
||||
dex::MethodBuilder* method_;
|
||||
|
||||
// Parameters to the generated method
|
||||
dex::Value const context_;
|
||||
dex::Value const resid_;
|
||||
|
||||
// Registers used for code generation
|
||||
const dex::LiveRegister inflater_;
|
||||
const dex::LiveRegister xml_;
|
||||
const dex::LiveRegister attrs_;
|
||||
const dex::LiveRegister classname_tmp_;
|
||||
|
||||
const dex::MethodDeclData xml_next_;
|
||||
const dex::MethodDeclData try_create_view_;
|
||||
const dex::MethodDeclData generate_layout_params_;
|
||||
const dex::MethodDeclData add_view_;
|
||||
|
||||
// Keep track of the views currently in progress.
|
||||
struct ViewEntry {
|
||||
dex::LiveRegister view;
|
||||
std::optional<dex::LiveRegister> layout_params;
|
||||
};
|
||||
std::vector<ViewEntry> view_stack_;
|
||||
};
|
||||
|
||||
} // namespace startop
|
||||
|
||||
#endif // DEX_LAYOUT_COMPILER_H_
|
@ -1,353 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "android-base/logging.h"
|
||||
#include "dex_builder.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
// Adding tests here requires changes in several other places. See README.md in
|
||||
// the view_compiler directory for more information.
|
||||
|
||||
using namespace startop::dex;
|
||||
using namespace std;
|
||||
|
||||
void GenerateTrivialDexFile(const string& outdir) {
|
||||
DexBuilder dex_file;
|
||||
|
||||
ClassBuilder cbuilder{dex_file.MakeClass("android.startop.test.testcases.Trivial")};
|
||||
cbuilder.set_source_file("dex_testcase_generator.cc#GenerateTrivialDexFile");
|
||||
|
||||
slicer::MemView image{dex_file.CreateImage()};
|
||||
std::ofstream out_file(outdir + "/trivial.dex");
|
||||
out_file.write(image.ptr<const char>(), image.size());
|
||||
}
|
||||
|
||||
// Generates test cases that test around 1 instruction.
|
||||
void GenerateSimpleTestCases(const string& outdir) {
|
||||
DexBuilder dex_file;
|
||||
|
||||
ClassBuilder cbuilder{dex_file.MakeClass("android.startop.test.testcases.SimpleTests")};
|
||||
cbuilder.set_source_file("dex_testcase_generator.cc#GenerateSimpleTestCases");
|
||||
|
||||
// int return5() { return 5; }
|
||||
auto return5{cbuilder.CreateMethod("return5", Prototype{TypeDescriptor::Int()})};
|
||||
{
|
||||
LiveRegister r{return5.AllocRegister()};
|
||||
return5.BuildConst4(r, 5);
|
||||
return5.BuildReturn(r);
|
||||
}
|
||||
return5.Encode();
|
||||
|
||||
// int return5() { return 5; }
|
||||
auto integer_type{TypeDescriptor::FromClassname("java.lang.Integer")};
|
||||
auto returnInteger5{cbuilder.CreateMethod("returnInteger5", Prototype{integer_type})};
|
||||
[&](MethodBuilder& method) {
|
||||
LiveRegister five{method.AllocRegister()};
|
||||
method.BuildConst4(five, 5);
|
||||
LiveRegister object{method.AllocRegister()};
|
||||
method.BuildNew(
|
||||
object, integer_type, Prototype{TypeDescriptor::Void(), TypeDescriptor::Int()}, five);
|
||||
method.BuildReturn(object, /*is_object=*/true);
|
||||
}(returnInteger5);
|
||||
returnInteger5.Encode();
|
||||
|
||||
// // int returnParam(int x) { return x; }
|
||||
auto returnParam{cbuilder.CreateMethod("returnParam",
|
||||
Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
|
||||
returnParam.BuildReturn(Value::Parameter(0));
|
||||
returnParam.Encode();
|
||||
|
||||
// int returnStringLength(String x) { return x.length(); }
|
||||
auto string_type{TypeDescriptor::FromClassname("java.lang.String")};
|
||||
MethodDeclData string_length{
|
||||
dex_file.GetOrDeclareMethod(string_type, "length", Prototype{TypeDescriptor::Int()})};
|
||||
|
||||
auto returnStringLength{
|
||||
cbuilder.CreateMethod("returnStringLength", Prototype{TypeDescriptor::Int(), string_type})};
|
||||
{
|
||||
LiveRegister result = returnStringLength.AllocRegister();
|
||||
returnStringLength.AddInstruction(
|
||||
Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0)));
|
||||
returnStringLength.BuildReturn(result);
|
||||
}
|
||||
returnStringLength.Encode();
|
||||
|
||||
// int returnIfZero(int x) { if (x == 0) { return 5; } else { return 3; } }
|
||||
MethodBuilder returnIfZero{cbuilder.CreateMethod(
|
||||
"returnIfZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
|
||||
{
|
||||
LiveRegister resultIfZero{returnIfZero.AllocRegister()};
|
||||
Value else_target{returnIfZero.MakeLabel()};
|
||||
returnIfZero.AddInstruction(Instruction::OpWithArgs(
|
||||
Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
|
||||
// else branch
|
||||
returnIfZero.BuildConst4(resultIfZero, 3);
|
||||
returnIfZero.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfZero));
|
||||
// then branch
|
||||
returnIfZero.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
|
||||
returnIfZero.BuildConst4(resultIfZero, 5);
|
||||
returnIfZero.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfZero));
|
||||
}
|
||||
returnIfZero.Encode();
|
||||
|
||||
// int returnIfNotZero(int x) { if (x != 0) { return 5; } else { return 3; } }
|
||||
MethodBuilder returnIfNotZero{cbuilder.CreateMethod(
|
||||
"returnIfNotZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
|
||||
{
|
||||
LiveRegister resultIfNotZero{returnIfNotZero.AllocRegister()};
|
||||
Value else_target{returnIfNotZero.MakeLabel()};
|
||||
returnIfNotZero.AddInstruction(Instruction::OpWithArgs(
|
||||
Instruction::Op::kBranchNEqz, /*dest=*/{}, Value::Parameter(0), else_target));
|
||||
// else branch
|
||||
returnIfNotZero.BuildConst4(resultIfNotZero, 3);
|
||||
returnIfNotZero.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfNotZero));
|
||||
// then branch
|
||||
returnIfNotZero.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
|
||||
returnIfNotZero.BuildConst4(resultIfNotZero, 5);
|
||||
returnIfNotZero.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfNotZero));
|
||||
}
|
||||
returnIfNotZero.Encode();
|
||||
|
||||
// Make sure backwards branches work too.
|
||||
//
|
||||
// Pseudo code for test:
|
||||
// {
|
||||
// zero = 0;
|
||||
// result = 1;
|
||||
// if (zero == 0) goto B;
|
||||
// A:
|
||||
// return result;
|
||||
// B:
|
||||
// result = 2;
|
||||
// if (zero == 0) goto A;
|
||||
// result = 3;
|
||||
// return result;
|
||||
// }
|
||||
// If it runs correctly, this test should return 2.
|
||||
MethodBuilder backwardsBranch{
|
||||
cbuilder.CreateMethod("backwardsBranch", Prototype{TypeDescriptor::Int()})};
|
||||
[](MethodBuilder& method) {
|
||||
LiveRegister zero = method.AllocRegister();
|
||||
LiveRegister result = method.AllocRegister();
|
||||
Value labelA = method.MakeLabel();
|
||||
Value labelB = method.MakeLabel();
|
||||
method.BuildConst4(zero, 0);
|
||||
method.BuildConst4(result, 1);
|
||||
method.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kBranchEqz, /*dest=*/{}, zero, labelB));
|
||||
|
||||
method.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, labelA));
|
||||
method.BuildReturn(result);
|
||||
|
||||
method.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, labelB));
|
||||
method.BuildConst4(result, 2);
|
||||
method.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kBranchEqz, /*dest=*/{}, zero, labelA));
|
||||
|
||||
method.BuildConst4(result, 3);
|
||||
method.BuildReturn(result);
|
||||
}(backwardsBranch);
|
||||
backwardsBranch.Encode();
|
||||
|
||||
// Test that we can make a null value. Basically:
|
||||
//
|
||||
// public static String returnNull() { return null; }
|
||||
MethodBuilder returnNull{cbuilder.CreateMethod("returnNull", Prototype{string_type})};
|
||||
[](MethodBuilder& method) {
|
||||
LiveRegister zero = method.AllocRegister();
|
||||
method.BuildConst4(zero, 0);
|
||||
method.BuildReturn(zero, /*is_object=*/true);
|
||||
}(returnNull);
|
||||
returnNull.Encode();
|
||||
|
||||
// Test that we can make String literals. Basically:
|
||||
//
|
||||
// public static String makeString() { return "Hello, World!"; }
|
||||
MethodBuilder makeString{cbuilder.CreateMethod("makeString", Prototype{string_type})};
|
||||
[](MethodBuilder& method) {
|
||||
LiveRegister string = method.AllocRegister();
|
||||
method.BuildConstString(string, "Hello, World!");
|
||||
method.BuildReturn(string, /*is_object=*/true);
|
||||
}(makeString);
|
||||
makeString.Encode();
|
||||
|
||||
// Make sure strings are sorted correctly.
|
||||
//
|
||||
// int returnStringIfZeroAB(int x) { if (x == 0) { return "a"; } else { return "b"; } }
|
||||
MethodBuilder returnStringIfZeroAB{
|
||||
cbuilder.CreateMethod("returnStringIfZeroAB", Prototype{string_type, TypeDescriptor::Int()})};
|
||||
[&](MethodBuilder& method) {
|
||||
LiveRegister resultIfZero{method.AllocRegister()};
|
||||
Value else_target{method.MakeLabel()};
|
||||
method.AddInstruction(Instruction::OpWithArgs(
|
||||
Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
|
||||
// else branch
|
||||
method.BuildConstString(resultIfZero, "b");
|
||||
method.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
|
||||
// then branch
|
||||
method.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
|
||||
method.BuildConstString(resultIfZero, "a");
|
||||
method.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
|
||||
method.Encode();
|
||||
}(returnStringIfZeroAB);
|
||||
// int returnStringIfZeroAB(int x) { if (x == 0) { return "b"; } else { return "a"; } }
|
||||
MethodBuilder returnStringIfZeroBA{
|
||||
cbuilder.CreateMethod("returnStringIfZeroBA", Prototype{string_type, TypeDescriptor::Int()})};
|
||||
[&](MethodBuilder& method) {
|
||||
LiveRegister resultIfZero{method.AllocRegister()};
|
||||
Value else_target{method.MakeLabel()};
|
||||
method.AddInstruction(Instruction::OpWithArgs(
|
||||
Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
|
||||
// else branch
|
||||
method.BuildConstString(resultIfZero, "a");
|
||||
method.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
|
||||
// then branch
|
||||
method.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
|
||||
method.BuildConstString(resultIfZero, "b");
|
||||
method.AddInstruction(
|
||||
Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
|
||||
method.Encode();
|
||||
}(returnStringIfZeroBA);
|
||||
|
||||
// Make sure we can invoke static methods that return an object
|
||||
// String invokeStaticReturnObject(int n, int radix) { return java.lang.Integer.toString(n,
|
||||
// radix); }
|
||||
MethodBuilder invokeStaticReturnObject{
|
||||
cbuilder.CreateMethod("invokeStaticReturnObject",
|
||||
Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})};
|
||||
[&](MethodBuilder& method) {
|
||||
LiveRegister result{method.AllocRegister()};
|
||||
MethodDeclData to_string{dex_file.GetOrDeclareMethod(
|
||||
TypeDescriptor::FromClassname("java.lang.Integer"),
|
||||
"toString",
|
||||
Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})};
|
||||
method.AddInstruction(Instruction::InvokeStaticObject(
|
||||
to_string.id, result, Value::Parameter(0), Value::Parameter(1)));
|
||||
method.BuildReturn(result, /*is_object=*/true);
|
||||
method.Encode();
|
||||
}(invokeStaticReturnObject);
|
||||
|
||||
// Make sure we can invoke virtual methods that return an object
|
||||
// String invokeVirtualReturnObject(String s, int n) { return s.substring(n); }
|
||||
MethodBuilder invokeVirtualReturnObject{cbuilder.CreateMethod(
|
||||
"invokeVirtualReturnObject", Prototype{string_type, string_type, TypeDescriptor::Int()})};
|
||||
[&](MethodBuilder& method) {
|
||||
LiveRegister result{method.AllocRegister()};
|
||||
MethodDeclData substring{dex_file.GetOrDeclareMethod(
|
||||
string_type, "substring", Prototype{string_type, TypeDescriptor::Int()})};
|
||||
method.AddInstruction(Instruction::InvokeVirtualObject(
|
||||
substring.id, result, Value::Parameter(0), Value::Parameter(1)));
|
||||
method.BuildReturn(result, /*is_object=*/true);
|
||||
method.Encode();
|
||||
}(invokeVirtualReturnObject);
|
||||
|
||||
// Make sure we can cast objects
|
||||
// String castObjectToString(Object o) { return (String)o; }
|
||||
MethodBuilder castObjectToString{cbuilder.CreateMethod(
|
||||
"castObjectToString",
|
||||
Prototype{string_type, TypeDescriptor::FromClassname("java.lang.Object")})};
|
||||
[&](MethodBuilder& method) {
|
||||
const ir::Type* type_def = dex_file.GetOrAddType(string_type.descriptor());
|
||||
method.AddInstruction(
|
||||
Instruction::Cast(Value::Parameter(0), Value::Type(type_def->orig_index)));
|
||||
method.BuildReturn(Value::Parameter(0), /*is_object=*/true);
|
||||
method.Encode();
|
||||
}(castObjectToString);
|
||||
|
||||
TypeDescriptor test_class = TypeDescriptor::FromClassname("android.startop.test.TestClass");
|
||||
|
||||
// Read a static field
|
||||
// int readStaticField() { return TestClass.staticInteger; }
|
||||
MethodBuilder readStaticField{
|
||||
cbuilder.CreateMethod("readStaticField", Prototype{TypeDescriptor::Int()})};
|
||||
[&](MethodBuilder& method) {
|
||||
const ir::FieldDecl* field =
|
||||
dex_file.GetOrAddField(test_class, "staticInteger", TypeDescriptor::Int());
|
||||
LiveRegister result{method.AllocRegister()};
|
||||
method.AddInstruction(Instruction::GetStaticField(field->orig_index, result));
|
||||
method.BuildReturn(result, /*is_object=*/false);
|
||||
method.Encode();
|
||||
}(readStaticField);
|
||||
|
||||
// Set a static field
|
||||
// void setStaticField() { TestClass.staticInteger = 7; }
|
||||
MethodBuilder setStaticField{
|
||||
cbuilder.CreateMethod("setStaticField", Prototype{TypeDescriptor::Void()})};
|
||||
[&](MethodBuilder& method) {
|
||||
const ir::FieldDecl* field =
|
||||
dex_file.GetOrAddField(test_class, "staticInteger", TypeDescriptor::Int());
|
||||
LiveRegister number{method.AllocRegister()};
|
||||
method.BuildConst4(number, 7);
|
||||
method.AddInstruction(Instruction::SetStaticField(field->orig_index, number));
|
||||
method.BuildReturn();
|
||||
method.Encode();
|
||||
}(setStaticField);
|
||||
|
||||
// Read an instance field
|
||||
// int readInstanceField(TestClass obj) { return obj.instanceField; }
|
||||
MethodBuilder readInstanceField{
|
||||
cbuilder.CreateMethod("readInstanceField", Prototype{TypeDescriptor::Int(), test_class})};
|
||||
[&](MethodBuilder& method) {
|
||||
const ir::FieldDecl* field =
|
||||
dex_file.GetOrAddField(test_class, "instanceField", TypeDescriptor::Int());
|
||||
LiveRegister result{method.AllocRegister()};
|
||||
method.AddInstruction(Instruction::GetField(field->orig_index, result, Value::Parameter(0)));
|
||||
method.BuildReturn(result, /*is_object=*/false);
|
||||
method.Encode();
|
||||
}(readInstanceField);
|
||||
|
||||
// Set an instance field
|
||||
// void setInstanceField(TestClass obj) { obj.instanceField = 7; }
|
||||
MethodBuilder setInstanceField{
|
||||
cbuilder.CreateMethod("setInstanceField", Prototype{TypeDescriptor::Void(), test_class})};
|
||||
[&](MethodBuilder& method) {
|
||||
const ir::FieldDecl* field =
|
||||
dex_file.GetOrAddField(test_class, "instanceField", TypeDescriptor::Int());
|
||||
LiveRegister number{method.AllocRegister()};
|
||||
method.BuildConst4(number, 7);
|
||||
method.AddInstruction(Instruction::SetField(field->orig_index, Value::Parameter(0), number));
|
||||
method.BuildReturn();
|
||||
method.Encode();
|
||||
}(setInstanceField);
|
||||
|
||||
slicer::MemView image{dex_file.CreateImage()};
|
||||
std::ofstream out_file(outdir + "/simple.dex");
|
||||
out_file.write(image.ptr<const char>(), image.size());
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
CHECK_EQ(argc, 2);
|
||||
|
||||
string outdir = argv[1];
|
||||
|
||||
GenerateTrivialDexFile(outdir);
|
||||
GenerateSimpleTestCases(outdir);
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "java_lang_builder.h"
|
||||
|
||||
#include "android-base/stringprintf.h"
|
||||
|
||||
using android::base::StringPrintf;
|
||||
using std::string;
|
||||
|
||||
void JavaLangViewBuilder::Start() const {
|
||||
out_ << StringPrintf("package %s;\n", package_.c_str())
|
||||
<< "import android.content.Context;\n"
|
||||
"import android.content.res.Resources;\n"
|
||||
"import android.content.res.XmlResourceParser;\n"
|
||||
"import android.util.AttributeSet;\n"
|
||||
"import android.util.Xml;\n"
|
||||
"import android.view.*;\n"
|
||||
"import android.widget.*;\n"
|
||||
"\n"
|
||||
"public final class CompiledView {\n"
|
||||
"\n"
|
||||
"static <T extends View> T createView(Context context, AttributeSet attrs, View parent, "
|
||||
"String name, LayoutInflater.Factory factory, LayoutInflater.Factory2 factory2) {"
|
||||
"\n"
|
||||
" if (factory2 != null) {\n"
|
||||
" return (T)factory2.onCreateView(parent, name, context, attrs);\n"
|
||||
" } else if (factory != null) {\n"
|
||||
" return (T)factory.onCreateView(name, context, attrs);\n"
|
||||
" }\n"
|
||||
// TODO: find a way to call the private factory
|
||||
" return null;\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
" public static View inflate(Context context) {\n"
|
||||
" try {\n"
|
||||
" LayoutInflater inflater = LayoutInflater.from(context);\n"
|
||||
" LayoutInflater.Factory factory = inflater.getFactory();\n"
|
||||
" LayoutInflater.Factory2 factory2 = inflater.getFactory2();\n"
|
||||
" Resources res = context.getResources();\n"
|
||||
<< StringPrintf(" XmlResourceParser xml = res.getLayout(%s.R.layout.%s);\n",
|
||||
package_.c_str(),
|
||||
layout_name_.c_str())
|
||||
<< " AttributeSet attrs = Xml.asAttributeSet(xml);\n"
|
||||
// The Java-language XmlPullParser needs a call to next to find the start document tag.
|
||||
" xml.next(); // start document\n";
|
||||
}
|
||||
|
||||
void JavaLangViewBuilder::Finish() const {
|
||||
out_ << " } catch (Exception e) {\n"
|
||||
" return null;\n"
|
||||
" }\n" // end try
|
||||
" }\n" // end inflate
|
||||
"}\n"; // end CompiledView
|
||||
}
|
||||
|
||||
void JavaLangViewBuilder::StartView(const string& class_name, bool /*is_viewgroup*/) {
|
||||
const string view_var = MakeVar("view");
|
||||
const string layout_var = MakeVar("layout");
|
||||
std::string parent = "null";
|
||||
if (!view_stack_.empty()) {
|
||||
const StackEntry& parent_entry = view_stack_.back();
|
||||
parent = parent_entry.view_var;
|
||||
}
|
||||
out_ << " xml.next(); // <" << class_name << ">\n"
|
||||
<< StringPrintf(" %s %s = createView(context, attrs, %s, \"%s\", factory, factory2);\n",
|
||||
class_name.c_str(),
|
||||
view_var.c_str(),
|
||||
parent.c_str(),
|
||||
class_name.c_str())
|
||||
<< StringPrintf(" if (%s == null) %s = new %s(context, attrs);\n",
|
||||
view_var.c_str(),
|
||||
view_var.c_str(),
|
||||
class_name.c_str());
|
||||
if (!view_stack_.empty()) {
|
||||
out_ << StringPrintf(" ViewGroup.LayoutParams %s = %s.generateLayoutParams(attrs);\n",
|
||||
layout_var.c_str(),
|
||||
parent.c_str());
|
||||
}
|
||||
view_stack_.push_back({class_name, view_var, layout_var});
|
||||
}
|
||||
|
||||
void JavaLangViewBuilder::FinishView() {
|
||||
const StackEntry var = view_stack_.back();
|
||||
view_stack_.pop_back();
|
||||
if (!view_stack_.empty()) {
|
||||
const string& parent = view_stack_.back().view_var;
|
||||
out_ << StringPrintf(" xml.next(); // </%s>\n", var.class_name.c_str())
|
||||
<< StringPrintf(" %s.addView(%s, %s);\n",
|
||||
parent.c_str(),
|
||||
var.view_var.c_str(),
|
||||
var.layout_params_var.c_str());
|
||||
} else {
|
||||
out_ << StringPrintf(" return %s;\n", var.view_var.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
const std::string JavaLangViewBuilder::MakeVar(std::string prefix) {
|
||||
std::stringstream v;
|
||||
v << prefix << view_id_++;
|
||||
return v.str();
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef JAVA_LANG_BUILDER_H_
|
||||
#define JAVA_LANG_BUILDER_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
// Build Java language code to instantiate views.
|
||||
//
|
||||
// This has a very small interface to make it easier to generate additional
|
||||
// backends, such as a direct-to-DEX version.
|
||||
class JavaLangViewBuilder {
|
||||
public:
|
||||
JavaLangViewBuilder(std::string package, std::string layout_name, std::ostream& out = std::cout)
|
||||
: package_(package), layout_name_(layout_name), out_(out) {}
|
||||
|
||||
// Begin generating a class. Adds the package boilerplate, etc.
|
||||
void Start() const;
|
||||
// Finish generating a class, closing off any open curly braces, etc.
|
||||
void Finish() const;
|
||||
|
||||
// Begin creating a view (i.e. process the opening tag)
|
||||
void StartView(const std::string& class_name, bool is_viewgroup);
|
||||
// Finish a view, after all of its child nodes have been processed.
|
||||
void FinishView();
|
||||
|
||||
private:
|
||||
const std::string MakeVar(std::string prefix);
|
||||
|
||||
std::string const package_;
|
||||
std::string const layout_name_;
|
||||
|
||||
std::ostream& out_;
|
||||
|
||||
size_t view_id_ = 0;
|
||||
|
||||
struct StackEntry {
|
||||
// The class name for this view object
|
||||
const std::string class_name;
|
||||
|
||||
// The variable name that is holding the view object
|
||||
const std::string view_var;
|
||||
|
||||
// The variable name that holds the object's layout parameters
|
||||
const std::string layout_params_var;
|
||||
};
|
||||
std::vector<StackEntry> view_stack_;
|
||||
};
|
||||
|
||||
#endif // JAVA_LANG_BUILDER_H_
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "layout_validation.h"
|
||||
|
||||
#include "android-base/stringprintf.h"
|
||||
|
||||
namespace startop {
|
||||
|
||||
void LayoutValidationVisitor::VisitStartTag(const std::u16string& name) {
|
||||
if (0 == name.compare(u"merge")) {
|
||||
message_ = "Merge tags are not supported";
|
||||
can_compile_ = false;
|
||||
}
|
||||
if (0 == name.compare(u"include")) {
|
||||
message_ = "Include tags are not supported";
|
||||
can_compile_ = false;
|
||||
}
|
||||
if (0 == name.compare(u"view")) {
|
||||
message_ = "View tags are not supported";
|
||||
can_compile_ = false;
|
||||
}
|
||||
if (0 == name.compare(u"fragment")) {
|
||||
message_ = "Fragment tags are not supported";
|
||||
can_compile_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace startop
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef LAYOUT_VALIDATION_H_
|
||||
#define LAYOUT_VALIDATION_H_
|
||||
|
||||
#include "dex_builder.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace startop {
|
||||
|
||||
// This visitor determines whether a layout can be compiled. Since we do not currently support all
|
||||
// features, such as includes and merges, we need to pre-validate the layout before we start
|
||||
// compiling.
|
||||
class LayoutValidationVisitor {
|
||||
public:
|
||||
void VisitStartDocument() const {}
|
||||
void VisitEndDocument() const {}
|
||||
void VisitStartTag(const std::u16string& name);
|
||||
void VisitEndTag() const {}
|
||||
|
||||
const std::string& message() const { return message_; }
|
||||
bool can_compile() const { return can_compile_; }
|
||||
|
||||
private:
|
||||
std::string message_{"Okay"};
|
||||
bool can_compile_{true};
|
||||
};
|
||||
|
||||
} // namespace startop
|
||||
|
||||
#endif // LAYOUT_VALIDATION_H_
|
@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "tinyxml_layout_parser.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using startop::CanCompileLayout;
|
||||
using std::string;
|
||||
|
||||
namespace {
|
||||
void ValidateXmlText(const string& xml, bool expected) {
|
||||
tinyxml2::XMLDocument doc;
|
||||
doc.Parse(xml.c_str());
|
||||
EXPECT_EQ(CanCompileLayout(doc), expected);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(LayoutValidationTest, SingleButtonLayout) {
|
||||
const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<Button xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="Hello, World!">
|
||||
|
||||
</Button>)";
|
||||
ValidateXmlText(xml, /*expected=*/true);
|
||||
}
|
||||
|
||||
TEST(LayoutValidationTest, SmallConstraintLayout) {
|
||||
const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button6"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="Button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button7"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="Button2"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/button6" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button8"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="Button1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/button7" />
|
||||
</android.support.constraint.ConstraintLayout>)";
|
||||
ValidateXmlText(xml, /*expected=*/true);
|
||||
}
|
||||
|
||||
TEST(LayoutValidationTest, MergeNode) {
|
||||
const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TextView" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button9"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Button" />
|
||||
</merge>)";
|
||||
ValidateXmlText(xml, /*expected=*/false);
|
||||
}
|
||||
|
||||
TEST(LayoutValidationTest, IncludeLayout) {
|
||||
const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include
|
||||
layout="@layout/single_button_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</android.support.constraint.ConstraintLayout>)";
|
||||
ValidateXmlText(xml, /*expected=*/false);
|
||||
}
|
||||
|
||||
TEST(LayoutValidationTest, ViewNode) {
|
||||
const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<view
|
||||
class="android.support.design.button.MaterialButton"
|
||||
id="@+id/view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</android.support.constraint.ConstraintLayout>)";
|
||||
ValidateXmlText(xml, /*expected=*/false);
|
||||
}
|
||||
|
||||
TEST(LayoutValidationTest, FragmentNode) {
|
||||
// This test case is from https://developer.android.com/guide/components/fragments
|
||||
const string xml = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<fragment android:name="com.example.news.ArticleListFragment"
|
||||
android:id="@+id/list"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent" />
|
||||
<fragment android:name="com.example.news.ArticleReaderFragment"
|
||||
android:id="@+id/viewer"
|
||||
android:layout_weight="2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>)";
|
||||
ValidateXmlText(xml, /*expected=*/false);
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
|
||||
#include "android-base/stringprintf.h"
|
||||
#include "apk_layout_compiler.h"
|
||||
#include "dex_builder.h"
|
||||
#include "dex_layout_compiler.h"
|
||||
#include "java_lang_builder.h"
|
||||
#include "layout_validation.h"
|
||||
#include "tinyxml_layout_parser.h"
|
||||
#include "util.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace tinyxml2;
|
||||
using android::base::StringPrintf;
|
||||
using startop::dex::ClassBuilder;
|
||||
using startop::dex::DexBuilder;
|
||||
using startop::dex::MethodBuilder;
|
||||
using startop::dex::Prototype;
|
||||
using startop::dex::TypeDescriptor;
|
||||
using namespace startop::util;
|
||||
using std::string;
|
||||
|
||||
constexpr char kStdoutFilename[]{"stdout"};
|
||||
|
||||
DEFINE_bool(apk, false, "Compile layouts in an APK");
|
||||
DEFINE_bool(dex, false, "Generate a DEX file instead of Java");
|
||||
DEFINE_int32(infd, -1, "Read input from the given file descriptor");
|
||||
DEFINE_string(out, kStdoutFilename, "Where to write the generated class");
|
||||
DEFINE_string(package, "", "The package name for the generated class (required)");
|
||||
|
||||
template <typename Visitor>
|
||||
class XmlVisitorAdapter : public XMLVisitor {
|
||||
public:
|
||||
explicit XmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {}
|
||||
|
||||
bool VisitEnter(const XMLDocument& /*doc*/) override {
|
||||
visitor_->VisitStartDocument();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VisitExit(const XMLDocument& /*doc*/) override {
|
||||
visitor_->VisitEndDocument();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VisitEnter(const XMLElement& element, const XMLAttribute* /*firstAttribute*/) override {
|
||||
visitor_->VisitStartTag(
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(
|
||||
element.Name()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VisitExit(const XMLElement& /*element*/) override {
|
||||
visitor_->VisitEndTag();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Visitor* visitor_;
|
||||
};
|
||||
|
||||
template <typename Builder>
|
||||
void CompileLayout(XMLDocument* xml, Builder* builder) {
|
||||
startop::LayoutCompilerVisitor visitor{builder};
|
||||
XmlVisitorAdapter<decltype(visitor)> adapter{&visitor};
|
||||
xml->Accept(&adapter);
|
||||
}
|
||||
|
||||
} // end namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
constexpr size_t kProgramName = 0;
|
||||
constexpr size_t kFileNameParam = 1;
|
||||
constexpr size_t kNumRequiredArgs = 1;
|
||||
|
||||
gflags::SetUsageMessage(
|
||||
"Compile XML layout files into equivalent Java language code\n"
|
||||
"\n"
|
||||
" example usage: viewcompiler layout.xml --package com.example.androidapp");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, /*remove_flags*/ true);
|
||||
|
||||
gflags::CommandLineFlagInfo cmd = gflags::GetCommandLineFlagInfoOrDie("package");
|
||||
if (argc < kNumRequiredArgs || cmd.is_default) {
|
||||
gflags::ShowUsageWithFlags(argv[kProgramName]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const bool is_stdout = FLAGS_out == kStdoutFilename;
|
||||
|
||||
std::ofstream outfile;
|
||||
if (!is_stdout) {
|
||||
outfile.open(FLAGS_out);
|
||||
}
|
||||
|
||||
if (FLAGS_apk) {
|
||||
const startop::CompilationTarget target =
|
||||
FLAGS_dex ? startop::CompilationTarget::kDex : startop::CompilationTarget::kJavaLanguage;
|
||||
if (FLAGS_infd >= 0) {
|
||||
startop::CompileApkLayoutsFd(
|
||||
android::base::unique_fd{FLAGS_infd}, target, is_stdout ? std::cout : outfile);
|
||||
} else {
|
||||
if (argc < 2) {
|
||||
gflags::ShowUsageWithFlags(argv[kProgramName]);
|
||||
return 1;
|
||||
}
|
||||
const char* const filename = argv[kFileNameParam];
|
||||
startop::CompileApkLayouts(filename, target, is_stdout ? std::cout : outfile);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* const filename = argv[kFileNameParam];
|
||||
const string layout_name = startop::util::FindLayoutNameFromFilename(filename);
|
||||
|
||||
XMLDocument xml;
|
||||
xml.LoadFile(filename);
|
||||
|
||||
string message{};
|
||||
if (!startop::CanCompileLayout(xml, &message)) {
|
||||
LOG(ERROR) << "Layout not supported: " << message;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (FLAGS_dex) {
|
||||
DexBuilder dex_file;
|
||||
string class_name = StringPrintf("%s.CompiledView", FLAGS_package.c_str());
|
||||
ClassBuilder compiled_view{dex_file.MakeClass(class_name)};
|
||||
MethodBuilder method{compiled_view.CreateMethod(
|
||||
layout_name,
|
||||
Prototype{TypeDescriptor::FromClassname("android.view.View"),
|
||||
TypeDescriptor::FromClassname("android.content.Context"),
|
||||
TypeDescriptor::Int()})};
|
||||
startop::DexViewBuilder builder{&method};
|
||||
CompileLayout(&xml, &builder);
|
||||
method.Encode();
|
||||
|
||||
slicer::MemView image{dex_file.CreateImage()};
|
||||
|
||||
(is_stdout ? std::cout : outfile).write(image.ptr<const char>(), image.size());
|
||||
} else {
|
||||
// Generate Java language output.
|
||||
JavaLangViewBuilder builder{FLAGS_package, layout_name, is_stdout ? std::cout : outfile};
|
||||
|
||||
CompileLayout(&xml, &builder);
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "tinyxml_layout_parser.h"
|
||||
|
||||
#include "layout_validation.h"
|
||||
|
||||
namespace startop {
|
||||
|
||||
bool CanCompileLayout(const tinyxml2::XMLDocument& xml, std::string* message) {
|
||||
LayoutValidationVisitor validator;
|
||||
TinyXmlVisitorAdapter adapter{&validator};
|
||||
xml.Accept(&adapter);
|
||||
|
||||
if (message != nullptr) {
|
||||
*message = validator.message();
|
||||
}
|
||||
|
||||
return validator.can_compile();
|
||||
}
|
||||
|
||||
} // namespace startop
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef TINYXML_LAYOUT_PARSER_H_
|
||||
#define TINYXML_LAYOUT_PARSER_H_
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#include <string>
|
||||
|
||||
namespace startop {
|
||||
|
||||
template <typename Visitor>
|
||||
class TinyXmlVisitorAdapter : public tinyxml2::XMLVisitor {
|
||||
public:
|
||||
explicit TinyXmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {}
|
||||
|
||||
bool VisitEnter(const tinyxml2::XMLDocument& /*doc*/) override {
|
||||
visitor_->VisitStartDocument();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VisitExit(const tinyxml2::XMLDocument& /*doc*/) override {
|
||||
visitor_->VisitEndDocument();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VisitEnter(const tinyxml2::XMLElement& element,
|
||||
const tinyxml2::XMLAttribute* /*firstAttribute*/) override {
|
||||
visitor_->VisitStartTag(
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(
|
||||
element.Name()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VisitExit(const tinyxml2::XMLElement& /*element*/) override {
|
||||
visitor_->VisitEndTag();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Visitor* visitor_;
|
||||
};
|
||||
|
||||
// Returns whether a layout resource represented by a TinyXML document is supported by the layout
|
||||
// compiler.
|
||||
bool CanCompileLayout(const tinyxml2::XMLDocument& xml, std::string* message = nullptr);
|
||||
|
||||
} // namespace startop
|
||||
|
||||
#endif // TINYXML_LAYOUT_PARSER_H_
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "util.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace startop {
|
||||
namespace util {
|
||||
|
||||
// TODO: see if we can borrow this from somewhere else, like aapt2.
|
||||
string FindLayoutNameFromFilename(const string& filename) {
|
||||
size_t start = filename.rfind('/');
|
||||
if (start == string::npos) {
|
||||
start = 0;
|
||||
} else {
|
||||
start++; // advance past '/' character
|
||||
}
|
||||
size_t end = filename.find('.', start);
|
||||
|
||||
return filename.substr(start, end - start);
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace startop
|
@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef VIEW_COMPILER_UTIL_H_
|
||||
#define VIEW_COMPILER_UTIL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace startop {
|
||||
namespace util {
|
||||
|
||||
std::string FindLayoutNameFromFilename(const std::string& filename);
|
||||
|
||||
} // namespace util
|
||||
} // namespace startop
|
||||
|
||||
#endif // VIEW_COMPILER_UTIL_H_
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace startop {
|
||||
namespace util {
|
||||
|
||||
TEST(UtilTest, FindLayoutNameFromFilename) {
|
||||
EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("foo/bar.xml"));
|
||||
EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("bar.xml"));
|
||||
EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("./foo/bar.xml"));
|
||||
EXPECT_EQ("bar", startop::util::FindLayoutNameFromFilename("/foo/bar.xml"));
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace startop
|
Loading…
x
Reference in New Issue
Block a user