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:
Jared Duke 2023-10-19 23:13:57 +00:00
parent c8f659f266
commit 72fd8769f9
39 changed files with 72 additions and 4033 deletions

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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.
*

View File

@ -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();

View File

@ -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;
}

View File

@ -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;

View File

@ -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].

View File

@ -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;
}
}
}

View File

@ -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",
],
}

View File

@ -1,2 +0,0 @@
eholk@google.com
mathieuc@google.com

View File

@ -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.

View File

@ -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

View File

@ -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_

View File

@ -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{&register_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{&register_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

View File

@ -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_

View File

@ -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"],
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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_

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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