Merge commit '0b579d0bee06cfb404c5f18c4827aee0d3a4e771' into 1.7.9-dev
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index e2ad67e..773f4ad 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.ImmutableSet;
@@ -45,6 +46,10 @@
copyMetadataFromPrevious(previous);
}
+ protected InternalOptions options() {
+ return app.options;
+ }
+
public void copyMetadataFromPrevious(AppInfo previous) {
this.synthesizedClasses.putAll(previous.synthesizedClasses);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index ec90464..460885c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -325,7 +325,6 @@
assert checkIfObsolete();
if (!isSubtype(invocationContext, method.holder)) {
DexClass contextClass = definitionFor(invocationContext);
- isSubtype(invocationContext, method.holder);
throw new CompilationError(
"Illegal invoke-super to " + method.toSourceString() + " from class " + invocationContext,
contextClass != null ? contextClass.getOrigin() : Origin.unknown());
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index 2af1aa5..079b8bb 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -28,4 +28,6 @@
boolean isReadOnlyIn(DexEncodedMethod method);
boolean isWritten();
+
+ boolean isWrittenOutside(DexEncodedMethod method);
}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index b6c54cb..fcd4f5c 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -146,6 +146,23 @@
return writesWithContexts != null && !writesWithContexts.isEmpty();
}
+ /**
+ * Returns true if this field is written by a method in the program other than {@param method}.
+ */
+ @Override
+ public boolean isWrittenOutside(DexEncodedMethod method) {
+ if (writesWithContexts != null) {
+ for (Set<DexEncodedMethod> encodedWriteContexts : writesWithContexts.values()) {
+ for (DexEncodedMethod encodedWriteContext : encodedWriteContexts) {
+ if (encodedWriteContext != method) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
public boolean recordRead(DexField access, DexEncodedMethod context) {
if (readsWithContexts == null) {
readsWithContexts = new IdentityHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index ac6bcde..0ca8fac 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -144,13 +144,13 @@
boolean encodedValueStorageIsLive;
if (enqueuer.isFieldLive(encodedValueStorage)) {
- // Mark that the field is written by reflection such that we do not optimize field reads
- // into loading the default value of the field.
- enqueuer.registerFieldWrite(encodedValueStorage.field, dynamicMethod);
- // Map/required fields cannot be removed. Therefore, we mark such fields as both read and
- // written such that we cannot optimize any field reads or writes.
- if (reachesMapOrRequiredField(protoFieldInfo)) {
- enqueuer.registerFieldRead(encodedValueStorage.field, dynamicMethod);
+ if (enqueuer.isFieldRead(encodedValueStorage)
+ || enqueuer.isFieldWrittenOutsideDefaultConstructor(encodedValueStorage)) {
+ // Mark that the field is both read and written by reflection such that we do not
+ // (i) optimize field reads into loading the default value of the field or (ii) remove
+ // field writes to proto fields that could be read using reflection by the proto
+ // library.
+ enqueuer.registerFieldAccess(encodedValueStorage.field, dynamicMethod);
}
encodedValueStorageIsLive = true;
} else if (reachesMapOrRequiredField(protoFieldInfo)) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
index d1ca480..8078fe0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
@@ -134,10 +134,18 @@
}
private void addTarget(DexEncodedMethod callee, boolean likelySpuriousCallEdge) {
- if (!callee.accessFlags.isAbstract()) {
- assert callee.isProgramMethod(appView);
- getOrCreateNode(callee).addCallerConcurrently(caller, likelySpuriousCallEdge);
+ if (callee.accessFlags.isAbstract()) {
+ // Not a valid target.
+ return;
}
+ if (appView.appInfo().isPinned(callee.method)) {
+ // Since the callee is kept, we cannot inline it into the caller, and we also cannot collect
+ // any optimization info for the method. Therefore, we drop the call edge to reduce the
+ // total number of call graph edges, which should lead to fewer call graph cycles.
+ return;
+ }
+ assert callee.isProgramMethod(appView);
+ getOrCreateNode(callee).addCallerConcurrently(caller, likelySpuriousCallEdge);
}
private void processInvoke(Type originalType, DexMethod originalMethod) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index f0eda16..708cabb8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -139,6 +139,7 @@
// We look up everywhere to see if there is a supertype/interface implementing the method...
LinkedList<DexType> workList = new LinkedList<>();
Collections.addAll(workList, theClass.interfaces.values);
+ boolean foundOverrideToRewrite = false;
// There is no methods with desugared types on Object.
if (theClass.superType != factory.objectType) {
workList.add(theClass.superType);
@@ -158,10 +159,14 @@
}
DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method);
if (dexEncodedMethod != null) {
- return true;
+ if (appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface()
+ .containsKey(dexClass.type)) {
+ return false;
+ }
+ foundOverrideToRewrite = true;
}
}
- return false;
+ return foundOverrideToRewrite;
}
private synchronized void generateCallBack(DexClass dexClass, DexEncodedMethod originalMethod) {
@@ -169,7 +174,7 @@
methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
CfCode cfCode =
new APIConverterWrapperCfCodeProvider(
- appView, originalMethod.method, null, this, dexClass.isInterface())
+ appView, originalMethod.method, null, this, dexClass.isInterface())
.generateCfCode();
DexEncodedMethod newDexEncodedMethod =
wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
@@ -256,7 +261,7 @@
// Return conversion added only if return value is used.
if (invokeMethod.outValue() != null
&& invokeMethod.outValue().numberOfUsers() + invokeMethod.outValue().numberOfPhiUsers()
- > 0) {
+ > 0) {
returnConversion =
createReturnConversionAndReplaceUses(code, invokeMethod, returnType, newReturnType);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index 95db2cf..2bf43f2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -95,6 +95,7 @@
public class DesugaredLibraryWrapperSynthesizer {
public static final String WRAPPER_PREFIX = "$r8$wrapper$";
+ public static final String WRAPPER_DESCRIPTOR_PREFIX = "L" + WRAPPER_PREFIX;
public static final String TYPE_WRAPPER_SUFFIX = "$-WRP";
public static final String VIVIFIED_TYPE_WRAPPER_SUFFIX = "$-V-WRP";
@@ -118,6 +119,10 @@
this.converter = converter;
}
+ public static boolean isSynthesizedWrapper(DexType clazz) {
+ return clazz.descriptor.toString().startsWith(WRAPPER_DESCRIPTOR_PREFIX);
+ }
+
boolean hasSynthesized(DexType type) {
return generatedWrappers.contains(type);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 87350e7..263da6c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -264,14 +264,6 @@
return false;
}
- DexClass holder = appView.definitionFor(singleTarget.method.holder);
- if (holder.isInterface()) {
- // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
- // runtime.
- whyAreYouNotInliningReporter.reportIncompatibleClassChangeError();
- return false;
- }
-
// Don't inline if target is synchronized.
if (singleTarget.accessFlags.isSynchronized()) {
whyAreYouNotInliningReporter.reportSynchronizedMethod();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index 818b8c2..b5c96d4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -43,9 +43,6 @@
public void reportInaccessible() {}
@Override
- public void reportIncompatibleClassChangeError() {}
-
- @Override
public void reportIncorrectArity(int numberOfArguments, int arity) {}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index c560944..1e869eb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -60,8 +60,6 @@
public abstract void reportInaccessible();
- public abstract void reportIncompatibleClassChangeError();
-
public abstract void reportIncorrectArity(int numberOfArguments, int arity);
public abstract void reportInlineeDoesNotHaveCode();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index 6bc09fd..dcc122b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -88,11 +88,6 @@
}
@Override
- public void reportIncompatibleClassChangeError() {
- print("invoke may fail with an IncompatibleClassChangeError.");
- }
-
- @Override
public void reportIncorrectArity(int numberOfArguments, int arity) {
print(
"number of arguments ("
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 7728c1b..2751153 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -205,7 +205,7 @@
invoke.getLocalInfo());
affectedValues.addAll(invoke.outValue().affectedValues());
it.replaceCurrentInstruction(new ConstString(newOutValue, resultString, throwingInfo));
- numberOfSimplifiedConversions++;
+ numberOfSimplifiedOperations++;
continue;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 4762db0..45f5bed 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -880,6 +880,31 @@
}
}
+ private DexEncodedMethod validateSingleVirtualTarget(
+ DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) {
+ assert resolutionResult.isValidVirtualTarget(options());
+
+ if (singleTarget == null || singleTarget == DexEncodedMethod.SENTINEL) {
+ return null;
+ }
+
+ // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception
+ // at runtime.
+ if (isInvalidSingleVirtualTarget(singleTarget, resolutionResult)) {
+ return null;
+ }
+
+ return singleTarget;
+ }
+
+ private boolean isInvalidSingleVirtualTarget(
+ DexEncodedMethod singleTarget, DexEncodedMethod resolutionResult) {
+ assert resolutionResult.isValidVirtualTarget(options());
+ // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception
+ // at runtime.
+ return !singleTarget.accessFlags.isAtLeastAsVisibleAs(resolutionResult.accessFlags);
+ }
+
/** For mapping invoke virtual instruction to single target method. */
public DexEncodedMethod lookupSingleVirtualTarget(DexMethod method, DexType invocationContext) {
assert checkIfObsolete();
@@ -903,11 +928,13 @@
if (receiverLowerBoundType != null) {
if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
ResolutionResult resolutionResult = resolveMethod(method.holder, method, false);
- if (resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+ if (resolutionResult.hasSingleTarget()
+ && resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
if (refinedResolutionResult.hasSingleTarget()
&& refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
- return refinedResolutionResult.asSingleTarget();
+ return validateSingleVirtualTarget(
+ refinedResolutionResult.asSingleTarget(), resolutionResult.asSingleTarget());
}
}
return null;
@@ -942,29 +969,29 @@
// First get the target for the holder type.
ResolutionResult topMethod = resolveMethodOnClass(holder, method);
// We might hit none or multiple targets. Both make this fail at runtime.
- if (!topMethod.hasSingleTarget() || !topMethod.isValidVirtualTarget(app().options)) {
+ if (!topMethod.hasSingleTarget() || !topMethod.isValidVirtualTarget(options())) {
method.setSingleVirtualMethodCache(refinedReceiverType, null);
return null;
}
// Now, resolve the target with the refined receiver type.
- if (refinedReceiverIsStrictSubType) {
- topMethod = resolveMethodOnClass(refinedHolder, method);
- }
- DexEncodedMethod topSingleTarget = topMethod.asSingleTarget();
+ ResolutionResult refinedResolutionResult =
+ refinedReceiverIsStrictSubType ? resolveMethodOnClass(refinedHolder, method) : topMethod;
+ DexEncodedMethod topSingleTarget = refinedResolutionResult.asSingleTarget();
DexClass topHolder = definitionFor(topSingleTarget.method.holder);
// We need to know whether the top method is from an interface, as that would allow it to be
// shadowed by a default method from an interface further down.
boolean topIsFromInterface = topHolder.isInterface();
// Now look at all subtypes and search for overrides.
DexEncodedMethod result =
- findSingleTargetFromSubtypes(
- refinedReceiverType,
- method,
- topSingleTarget,
- !refinedHolder.accessFlags.isAbstract(),
- topIsFromInterface);
- // Map the failure case of SENTINEL to null.
- result = result == DexEncodedMethod.SENTINEL ? null : result;
+ validateSingleVirtualTarget(
+ findSingleTargetFromSubtypes(
+ refinedReceiverType,
+ method,
+ topSingleTarget,
+ !refinedHolder.accessFlags.isAbstract(),
+ topIsFromInterface),
+ topMethod.asSingleTarget());
+ assert result != DexEncodedMethod.SENTINEL;
method.setSingleVirtualMethodCache(refinedReceiverType, result);
return result;
}
@@ -1099,11 +1126,13 @@
if (receiverLowerBoundType != null) {
if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
ResolutionResult resolutionResult = resolveMethod(method.holder, method, true);
- if (resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+ if (resolutionResult.hasSingleTarget()
+ && resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
if (refinedResolutionResult.hasSingleTarget()
&& refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
- return refinedResolutionResult.asSingleTarget();
+ return validateSingleVirtualTarget(
+ refinedResolutionResult.asSingleTarget(), resolutionResult.asSingleTarget());
}
}
return null;
@@ -1119,11 +1148,8 @@
}
// First check that there is a target for this invoke-interface to hit. If there is none,
// this will fail at runtime.
- ResolutionResult topTarget = resolveMethodOnInterface(holder, method);
- if (!topTarget.isValidVirtualTarget(app().options)) {
- return null;
- }
- if (topTarget.asResultOfResolve() == null) {
+ DexEncodedMethod topTarget = resolveMethodOnInterface(holder, method).asSingleTarget();
+ if (topTarget == null || !topTarget.isValidVirtualTarget(options())) {
return null;
}
// For kept types we cannot ensure a single target.
@@ -1152,19 +1178,18 @@
// override them, so we ignore interface methods here. Otherwise, we would look up
// default methods that are factually never used.
} else if (!clazz.accessFlags.isAbstract()) {
- ResolutionResult resolutionResult = resolveMethodOnClass(clazz, method);
- if (resolutionResult.hasSingleTarget()) {
- if ((result != null) && (result != resolutionResult.asSingleTarget())) {
- return null;
- } else {
- result = resolutionResult.asSingleTarget();
- }
- } else {
+ DexEncodedMethod resolutionResult = resolveMethodOnClass(clazz, method).asSingleTarget();
+ if (resolutionResult == null || isInvalidSingleVirtualTarget(resolutionResult, topTarget)) {
// This will fail at runtime.
return null;
}
+ if (result != null && result != resolutionResult) {
+ return null;
+ }
+ result = resolutionResult;
}
}
+ assert result == null || !isInvalidSingleVirtualTarget(result, topTarget);
return result == null || !result.isVirtualMethod() ? null : result;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 1d209ea..0689533 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1786,6 +1786,23 @@
return liveFields.contains(field);
}
+ public boolean isFieldRead(DexEncodedField field) {
+ FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.field);
+ return info != null && info.isRead();
+ }
+
+ public boolean isFieldWrittenOutsideDefaultConstructor(DexEncodedField field) {
+ FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.field);
+ if (info == null) {
+ return false;
+ }
+ DexClass clazz = appView.definitionFor(field.field.holder);
+ DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
+ return defaultInitializer != null
+ ? info.isWrittenOutside(defaultInitializer)
+ : info.isWritten();
+ }
+
private boolean isInstantiatedOrHasInstantiatedSubtype(DexProgramClass clazz) {
return directAndIndirectlyInstantiatedTypes.contains(clazz);
}
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index 1aef50b..21f78a7 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
@@ -85,6 +86,7 @@
|| BackportedMethodRewriter.hasRewrittenMethodPrefix(a.type)
|| InterfaceMethodRewriter.hasDispatchClassSuffix(a.type)
|| NestBasedAccessDesugaring.isNestConstructor(a.type)
+ || DesugaredLibraryWrapperSynthesizer.isSynthesizedWrapper(a.type)
|| a.type.descriptor.toString().equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR);
}
}
diff --git a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
index 231f2c1..5cbb40d 100644
--- a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
+++ b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
@@ -5,21 +5,25 @@
package com.android.tools.r8.classlookup;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
import java.util.List;
+import java.util.Set;
+import org.hamcrest.CoreMatchers;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,6 +41,13 @@
@RunWith(Parameterized.class)
public class LibraryClassExtendsProgramClassTest extends TestBase {
+ private static String[] libraryClassesExtendingTestCase =
+ new String[] {
+ "android.test.InstrumentationTestCase",
+ "android.test.AndroidTestCase",
+ "android.test.suitebuilder.TestSuiteBuilder$FailedToCreateTests"
+ };
+
private static List<byte[]> junitClasses;
@BeforeClass
@@ -57,8 +68,15 @@
this.parameters = parameters;
}
+ // The default library in the test runner is chosen to be the first one with at least the API
+ // level. The junit testing framework was removed in P.
+ private boolean libraryContainsJUnit() {
+ return parameters.isDexRuntime()
+ && parameters.getApiLevel().getLevel() < AndroidApiLevel.P.getLevel();
+ }
+
private void checkClassesInResult(CodeInspector inspector) {
- if (parameters.isDexRuntime()) {
+ if (libraryContainsJUnit()) {
noClassesInResult(inspector);
} else {
testCaseClassInResult(inspector);
@@ -76,9 +94,21 @@
assertThat(inspector.clazz(TestClass.class), isPresent());
}
+ private void checkDiagnostics(List<Diagnostic> diagnostics) {
+ Builder<String> builder = ImmutableSet.builder();
+ for (String libraryClass : libraryClassesExtendingTestCase) {
+ builder.add(
+ "Library class " + libraryClass + " extends program class junit.framework.TestCase");
+ }
+ Set<String> expected = builder.build();
+ for (Diagnostic diagnostic : diagnostics) {
+ assertThat(expected, CoreMatchers.hasItem(diagnostic.getDiagnosticMessage()));
+ }
+ assertEquals(expected.size(), diagnostics.size());
+ }
+
@Test
public void testFullMode() throws Exception {
- assumeFalse(parameters.getApiLevel().getLevel() == 28);
testForR8(parameters.getBackend())
.setMinApi(parameters.getApiLevel())
.addProgramClasses(TestClass.class)
@@ -93,7 +123,6 @@
@Test
public void testCompatibilityMode() throws Exception {
- assumeFalse(parameters.getApiLevel().getLevel() == 28);
testForR8Compat(parameters.getBackend())
.setMinApi(parameters.getApiLevel())
.addProgramClasses(TestClass.class)
@@ -119,56 +148,52 @@
}
@Test
- public void testFullModeError() {
- assumeFalse(parameters.getApiLevel().getLevel() == 28);
- assumeTrue("Only run for Dex backend", parameters.isDexRuntime());
+ public void testFullModeError() throws Exception {
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(TestClass.class)
+ .addProgramClassFileData(junitClasses)
+ .addKeepAllClassesRule()
+ .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false);
+ if (!libraryContainsJUnit()) {
+ builder.compile().inspect(this::testCaseClassInResult).assertNoMessages();
+ return;
+ }
try {
- testForR8(parameters.getBackend())
- .setMinApi(parameters.getApiLevel())
- .addProgramClasses(TestClass.class)
- .addProgramClassFileData(junitClasses)
- .addKeepAllClassesRule()
- .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false)
- .compile();
- fail("Succeeded in full mode");
+ builder.compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ checkDiagnostics(diagnostics.getErrors());
+ });
+ fail("Expected compilation failure");
} catch (CompilationFailedException e) {
- // Ignore.
+ // Expected exception.
}
}
@Test
public void testCompatibilityModeWarning() throws Exception {
- assumeFalse(parameters.getApiLevel().getLevel() == 28);
- assumeTrue("Only run for Dex backend", parameters.isDexRuntime());
- R8TestCompileResult result =
- testForR8Compat(parameters.getBackend())
- .setMinApi(parameters.getApiLevel())
- .addProgramClasses(TestClass.class)
- .addProgramClassFileData(junitClasses)
- .addKeepAllClassesRule()
- .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false)
- .compile()
- .assertOnlyWarnings();
-
- String[] libraryClassesExtendingTestCase = new String[]{
- "android.test.InstrumentationTestCase",
- "android.test.AndroidTestCase",
- "android.test.suitebuilder.TestSuiteBuilder$FailedToCreateTests"
- };
-
- result.getDiagnosticMessages().assertWarningsCount(libraryClassesExtendingTestCase.length);
-
- for (String name : libraryClassesExtendingTestCase) {
- result
- .assertWarningMessageThatMatches(
- containsString(
- "Library class " + name + " extends program class junit.framework.TestCase"));
- }
+ testForR8Compat(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(TestClass.class)
+ .addProgramClassFileData(junitClasses)
+ .addKeepAllClassesRule()
+ .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (libraryContainsJUnit()) {
+ diagnostics.assertOnlyWarnings();
+ checkDiagnostics(diagnostics.getWarnings());
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ })
+ .inspect(this::testCaseClassInResult);
}
@Test
public void testWithDontWarn() throws Exception {
- assumeTrue("Only run for Dex backend", parameters.isDexRuntime());
testForR8(parameters.getBackend())
.setMinApi(parameters.getApiLevel())
.addProgramClassFileData(junitClasses)
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
index 9a8bb6b..71cbfcd 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
@@ -11,8 +11,10 @@
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -57,8 +59,6 @@
public void testCustomCollectionD8() throws Exception {
// TODO(b/142377475).
Assume.assumeTrue(!shrinkDesugaredLibrary);
- // TODO(b/142377161).
- Assume.assumeTrue(parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST));
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
D8TestRunResult d8TestRunResult =
testForD8()
@@ -66,7 +66,9 @@
.setMinApi(parameters.getApiLevel())
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
- .inspect(inspector -> this.assertCustomCollectionCallsCorrect(inspector, false))
+ .inspect(inspector -> {
+ this.assertNoWrappers(inspector);
+ this.assertCustomCollectionCallsCorrect(inspector, false);})
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary,
parameters.getApiLevel(),
@@ -89,8 +91,6 @@
@Test
public void testCustomCollectionR8() throws Exception {
- // TODO(b/142377161).
- Assume.assumeTrue(parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST));
KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
R8TestRunResult r8TestRunResult =
testForR8(Backend.DEX)
@@ -104,7 +104,9 @@
})
.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
.compile()
- .inspect(inspector -> this.assertCustomCollectionCallsCorrect(inspector, true))
+ .inspect(inspector -> {
+ this.assertNoWrappers(inspector);
+ this.assertCustomCollectionCallsCorrect(inspector, true);})
.addDesugaredCoreLibraryRunClassPath(
this::buildDesugaredLibrary,
parameters.getApiLevel(),
@@ -125,6 +127,11 @@
}
}
+ private void assertNoWrappers(CodeInspector inspector) {
+ assertTrue(inspector.allClasses().stream().noneMatch(cl -> cl.getOriginalName().startsWith(
+ DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)));
+ }
+
private void assertCustomCollectionCallsCorrect(CodeInspector inspector, boolean r8) {
MethodSubject direct = inspector.clazz(EXECUTOR).uniqueMethodWithName("directTypes");
// TODO(b/134732760): Due to memberRebinding, R8 is not as precise as D8 regarding
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/MoreFunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/MoreFunctionConversionTest.java
index 80a61e7..fd3af23 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/MoreFunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/MoreFunctionConversionTest.java
@@ -38,12 +38,12 @@
this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
.addRunClasspathFiles(customLib)
.run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
- // TODO(clement): Make output reliable and put back expected results.
- .assertSuccess();
+ .assertSuccessWithOutput(StringUtils.lines("6","6","6","6","6"));
}
// If we have the exact same lambda in both, but one implements j$..Function and the other
// java..Function, ART is obviously very confused.
+ // This happens for instance when String::length function is used both in CustomLib and Executor.
private void assertNoDuplicateLambdas(Path program, Path customLib) throws Exception {
CodeInspector programInspector = new CodeInspector(program);
CodeInspector customLibInspector = new CodeInspector(customLib);
@@ -76,30 +76,34 @@
}
public static void oneParameterReturn() {
- Function<Object, String> toString = Object::toString;
+ Function<Object, String> toString = getObjectStringConv();
Function<Object, Integer> oneParam = CustomLibClass.oneParameterReturn(toString);
System.out.println(oneParam.apply(new Object()));
}
public static void twoParametersReturn() {
- Function<Object, String> toString = Object::toString;
+ Function<Object, String> toString = getObjectStringConv();
Function<String, Integer> length = String::length;
Function<Object, Integer> twoParam = CustomLibClass.twoParametersReturn(toString, length);
System.out.println(twoParam.apply(new Object()));
}
public static void oneParameter() {
- Function<Object, String> toString = Object::toString;
+ Function<Object, String> toString = getObjectStringConv();
int res = CustomLibClass.oneParameter(toString);
System.out.println(res);
}
public static void twoParameters() {
- Function<Object, String> toString = Object::toString;
+ Function<Object, String> toString = getObjectStringConv();
Function<String, Integer> length = String::length;
int res = CustomLibClass.twoParameters(toString, length);
System.out.println(res);
}
+
+ public static Function<Object, String> getObjectStringConv() {
+ return (Object o) -> o.getClass().getSimpleName();
+ }
}
// This class will be put at compilation time as library and on the runtime class path.
@@ -130,13 +134,14 @@
return f1.andThen(f2).apply(new Object());
}
- // Following functions are defined to avoid name collision with the program.
+ // Following functions are defined to avoid name collision with the program. Name collision
+ // happens for instance when String::length function is used both in CustomLib and Executor.
public static Function<String, Integer> getStringIntConv() {
return (String s) -> 1 + s.length() - 1;
}
public static Function<Object, String> getObjectStringConv() {
- return (Object o) -> "" + o.toString() + "";
+ return (Object o) -> "" + o.getClass().getSimpleName() + "";
}
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/WrapperMergeTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/WrapperMergeTest.java
new file mode 100644
index 0000000..c4d1880
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/WrapperMergeTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.corelib.conversionTests;
+
+import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Arrays;
+import org.junit.Test;
+
+public class WrapperMergeTest extends APIConversionTestBase {
+
+ @Test
+ public void testWrapperMerge() throws Exception {
+ // Multiple wrapper classes have to be merged here.
+ Path path1 = testForD8()
+ .addProgramClasses(Executor1.class)
+ .setMinApi(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compile()
+ .inspect(this::assertWrappers)
+ .writeToZip();
+ Path path2 = testForD8()
+ .addProgramClasses(Executor2.class)
+ .setMinApi(AndroidApiLevel.B)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compile()
+ .inspect(this::assertWrappers)
+ .writeToZip();
+ testForD8()
+ .addProgramFiles(path1,path2)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+ .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor1.class)
+ .assertSuccessWithOutput(
+ StringUtils.lines("[1, 2, 3]"));
+
+ }
+
+ private void assertWrappers(CodeInspector inspector) {
+ assertEquals(2,inspector.allClasses().stream().filter(c -> c.getOriginalName().contains(
+ DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)).count());
+ }
+
+ static class Executor1 {
+
+ public static void main(String[] args) {
+ int[] ints = new int[3];
+ Arrays.setAll(ints,x->x+1);
+ System.out.println(Arrays.toString(ints));
+ }
+ }
+
+ static class Executor2 {
+
+ public static void main(String[] args) {
+ int[] ints = new int[3];
+ Arrays.setAll(ints,x->x+2);
+ System.out.println(Arrays.toString(ints));
+ }
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java
index 26a4f2f..df9ab8d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineDefaultInterfaceMethodTest.java
@@ -4,53 +4,58 @@
package com.android.tools.r8.ir.optimize.inliner.interfacemethods;
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.DEFAULT_METHOD_PREFIX;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverMerge;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class InlineDefaultInterfaceMethodTest extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InlineDefaultInterfaceMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
@Test
public void test() throws Exception {
String expectedOutput = StringUtils.lines("Hello world!");
CodeInspector inspector =
- testForR8(Backend.DEX)
+ testForR8(parameters.getBackend())
.addInnerClasses(InlineDefaultInterfaceMethodTest.class)
.addKeepMainRule(TestClass.class)
- .setMinApi(AndroidApiLevel.M)
+ .setMinApi(parameters.getApiLevel())
.enableClassInliningAnnotations()
.enableMergeAnnotations()
.noMinification()
- .run(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput)
.inspector();
- // TODO(b/124017330): interface methods should have been inlined into C.method().
- ClassSubject classSubject =
- inspector.clazz(I.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
- assertThat(classSubject, isPresent());
- assertThat(classSubject.uniqueMethodWithName(DEFAULT_METHOD_PREFIX + "hello"), isPresent());
- assertThat(classSubject.uniqueMethodWithName(DEFAULT_METHOD_PREFIX + "space"), isPresent());
- assertThat(classSubject.uniqueMethodWithName(DEFAULT_METHOD_PREFIX + "world"), isPresent());
+ // After inlining, only one class remains, namely TestClass.
+ assertEquals(1, inspector.allClasses().size());
}
static class TestClass {
public static void main(String[] args) {
- C obj = new C();
- obj.method();
+ new C().method();
}
}
@@ -73,7 +78,6 @@
@NeverClassInline
static class C implements I {
- @NeverInline
public void method() {
// invoke-virtual
hello();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineStaticInterfaceMethodTest.java
index 9eb57fb..49dee4b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineStaticInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/interfacemethods/InlineStaticInterfaceMethodTest.java
@@ -4,37 +4,47 @@
package com.android.tools.r8.ir.optimize.inliner.interfacemethods;
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class InlineStaticInterfaceMethodTest extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InlineStaticInterfaceMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
@Test
public void test() throws Exception {
String expectedOutput = StringUtils.lines("Hello world!");
CodeInspector inspector =
- testForR8(Backend.DEX)
+ testForR8(parameters.getBackend())
.addInnerClasses(InlineStaticInterfaceMethodTest.class)
.addKeepMainRule(TestClass.class)
- .setMinApi(AndroidApiLevel.M)
- .run(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput)
.inspector();
- // TODO(b/124017330): greet() should have been inlined into main().
- ClassSubject classSubject =
- inspector.clazz(I.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
- assertThat(classSubject, isPresent());
- assertThat(classSubject.uniqueMethodWithName("greet"), isPresent());
+ // After inlining of I.greet(), only one class remains, namely TestClass.
+ assertEquals(1, inspector.allClasses().size());
}
static class TestClass {
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 7ce3582..4d9273c 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -130,8 +130,6 @@
def prepare_google3():
- utils.check_prodacces()
-
# Check if an existing client exists.
if ':update-r8:' in subprocess.check_output('g4 myclients', shell=True):
print "Remove the existing 'update-r8' client before continuing."
@@ -217,7 +215,7 @@
result.add_argument('--version',
required=True,
help='The new version of R8 (e.g., 1.4.51)')
- result.add_argument('--no_sync',
+ result.add_argument('--no-sync', '--no_sync',
default=False,
action='store_true',
help='Do not sync repos before uploading')
@@ -246,6 +244,9 @@
def main():
args = parse_options()
targets_to_run = []
+ if args.google3 or args.studio:
+ utils.check_prodacces()
+
if args.google3:
targets_to_run.append(prepare_google3())
if args.studio: