Merge "Separate interface method minification from class method minification"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 734e421..4f472c6 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -63,7 +63,6 @@
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.LineNumberOptimizer;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.SelfRetraceTest;
@@ -634,12 +633,7 @@
// When line number optimization is turned off the identity mapping for line numbers is
// used. We still run the line number optimizer to collect line numbers and inline frame
// information for the mapping file.
- ClassNameMapper classNameMapper =
- LineNumberOptimizer.run(
- application,
- appView.graphLense(),
- namingLens,
- options.lineNumberOptimization == LineNumberOptimization.OFF);
+ ClassNameMapper classNameMapper = LineNumberOptimizer.run(appView, application, namingLens);
timing.end();
proguardMapSupplier = ProguardMapSupplier.fromClassNameMapper(classNameMapper, options);
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
index e3fa01b..88b3b4b 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
@@ -15,6 +15,7 @@
TargetedBySuper,
InvokedFrom,
InvokedFromLambdaCreatedIn,
+ AnnotatedOn,
ReferencedFrom,
ReflectiveUseFrom,
ReachableFromLiveType,
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 a51ca83..f011931 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -177,10 +177,10 @@
* for these.
*/
private final Set<DexType> liveTypes = Sets.newIdentityHashSet();
- /**
- * Set of annotation types that are instantiated.
- */
- private final Set<DexType> instantiatedAnnotations = Sets.newIdentityHashSet();
+
+ /** Set of annotation types that are instantiated. */
+ private final SetWithReason<DexAnnotation> instantiatedAnnotations =
+ new SetWithReason<>(this::registerAnnotation);
/** Set of types that are actually instantiated. These cannot be abstract. */
private final SetWithReason<DexType> instantiatedTypes = new SetWithReason<>(this::registerType);
/**
@@ -803,9 +803,6 @@
}
}
}
- if (!holder.annotations.isEmpty()) {
- processAnnotations(holder.annotations.annotations);
- }
// We also need to add the corresponding <clinit> to the set of live methods, as otherwise
// static field initialization (and other class-load-time sideeffects) will not happen.
KeepReason reason = KeepReason.reachableFromLiveType(type);
@@ -821,10 +818,15 @@
enqueueFirstNonSerializableClassInitializer(holder, reason);
}
- // If this type has deferred annotations, we have to process those now, too.
- Set<DexAnnotation> annotations = deferredAnnotations.remove(type);
- if (annotations != null) {
- annotations.forEach(this::handleAnnotationOfLiveType);
+ if (!holder.isLibraryClass()) {
+ if (!holder.annotations.isEmpty()) {
+ processAnnotations(holder, holder.annotations.annotations);
+ }
+ // If this type has deferred annotations, we have to process those now, too.
+ Set<DexAnnotation> annotations = deferredAnnotations.remove(type);
+ if (annotations != null) {
+ annotations.forEach(annotation -> handleAnnotationOfLiveType(holder, annotation));
+ }
}
Map<DexReference, Set<ProguardKeepRule>> dependentItems = rootSet.getDependentItems(holder);
@@ -834,36 +836,33 @@
}
}
- private void handleAnnotationOfLiveType(DexAnnotation annotation) {
- DexType type = annotation.annotation.type;
- // Record that it is instantiated if it should be kept when its type is live.
- if (shouldKeepAnnotation(annotation, true, appInfo.dexItemFactory, options)) {
- instantiatedAnnotations.add(type);
- }
- AnnotationReferenceMarker referenceMarker = new AnnotationReferenceMarker(
- annotation.annotation.type, appInfo.dexItemFactory);
- annotation.annotation.collectIndexedItems(referenceMarker);
+ private void handleAnnotationOfLiveType(DexDefinition holder, DexAnnotation annotation) {
+ handleAnnotation(holder, annotation, true);
}
- private void processAnnotations(DexAnnotation[] annotations) {
+ private void processAnnotations(DexDefinition holder, DexAnnotation[] annotations) {
for (DexAnnotation annotation : annotations) {
- processAnnotation(annotation);
+ processAnnotation(holder, annotation);
}
}
- private void processAnnotation(DexAnnotation annotation) {
+ private void processAnnotation(DexDefinition holder, DexAnnotation annotation) {
+ handleAnnotation(holder, annotation, false);
+ }
+
+ private void handleAnnotation(DexDefinition holder, DexAnnotation annotation, boolean forceLive) {
+ assert !holder.isDexClass() || !holder.asDexClass().isLibraryClass();
DexType type = annotation.annotation.type;
- if (liveTypes.contains(type)) {
- // The type of this annotation is already live, so pick up its dependencies.
- handleAnnotationOfLiveType(annotation);
- } else {
- // Record that it is instantiated if it should be kept although its type is not live.
- if (shouldKeepAnnotation(annotation, false, appInfo.dexItemFactory, options)) {
- instantiatedAnnotations.add(type);
- }
+ boolean isLive = forceLive || liveTypes.contains(type);
+ if (!shouldKeepAnnotation(annotation, isLive, appInfo.dexItemFactory, options)) {
// Remember this annotation for later.
deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
+ return;
}
+ instantiatedAnnotations.add(annotation, KeepReason.annotatedOn(holder));
+ AnnotationReferenceMarker referenceMarker =
+ new AnnotationReferenceMarker(annotation.annotation.type, appInfo.dexItemFactory);
+ annotation.annotation.collectIndexedItems(referenceMarker);
}
private void handleInvokeOfStaticTarget(DexMethod method, KeepReason reason) {
@@ -956,8 +955,11 @@
}
markTypeAsLive(method.method.holder);
markParameterAndReturnTypesAsLive(method);
- processAnnotations(method.annotations.annotations);
- method.parameterAnnotationsList.forEachAnnotation(this::processAnnotation);
+ if (!appInfo.definitionFor(method.method.holder).isLibraryClass()) {
+ processAnnotations(method, method.annotations.annotations);
+ method.parameterAnnotationsList.forEachAnnotation(
+ annotation -> processAnnotation(method, annotation));
+ }
if (Log.ENABLED) {
Log.verbose(getClass(), "Method `%s` is targeted.", method.method);
}
@@ -1614,8 +1616,11 @@
}
}
markParameterAndReturnTypesAsLive(method);
- processAnnotations(method.annotations.annotations);
- method.parameterAnnotationsList.forEachAnnotation(this::processAnnotation);
+ if (!appInfo.definitionFor(method.method.holder).isLibraryClass()) {
+ processAnnotations(method, method.annotations.annotations);
+ method.parameterAnnotationsList.forEachAnnotation(
+ annotation -> processAnnotation(method, annotation));
+ }
method.registerCodeReferences(new UseRegistry(options.itemFactory, method));
// Add all dependent members to the workqueue.
enqueueRootItems(rootSet.getDependentItems(method));
@@ -2013,8 +2018,11 @@
super(appInfo);
this.liveTypes = ImmutableSortedSet.copyOf(
PresortedComparable<DexType>::slowCompareTo, enqueuer.liveTypes);
- this.instantiatedAnnotations = ImmutableSortedSet.copyOf(
- PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedAnnotations);
+ ImmutableSortedSet.Builder<DexType> builder =
+ ImmutableSortedSet.orderedBy(PresortedComparable<DexType>::slowCompareTo);
+ enqueuer.instantiatedAnnotations.items.forEach(
+ annotation -> builder.add(annotation.annotation.type));
+ this.instantiatedAnnotations = builder.build();
this.instantiatedTypes = ImmutableSortedSet.copyOf(
PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedTypes.getItems());
this.instantiatedLambdas =
@@ -2795,6 +2803,14 @@
registerEdge(getClassGraphNode(type), reason);
}
+ private void registerAnnotation(DexAnnotation annotation, KeepReason reason) {
+ assert getSourceNode(reason) != null;
+ if (keptGraphConsumer == null) {
+ return;
+ }
+ registerEdge(getAnnotationGraphNode(annotation.annotation.type), reason);
+ }
+
private void registerMethod(DexEncodedMethod method, KeepReason reason) {
if (reason.edgeKind() == EdgeKind.IsLibraryMethod) {
// Don't report edges to actual library methods.
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index a797742..2dd52eb 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexType;
@@ -17,6 +18,10 @@
public abstract GraphNode getSourceNode(Enqueuer enqueuer);
+ static KeepReason annotatedOn(DexDefinition definition) {
+ return new AnnotatedOn(definition);
+ }
+
static KeepReason dueToKeepRule(ProguardKeepRule rule) {
return new DueToKeepRule(rule);
}
@@ -302,6 +307,32 @@
}
}
+ private static class AnnotatedOn extends KeepReason {
+
+ private final DexDefinition holder;
+
+ private AnnotatedOn(DexDefinition holder) {
+ this.holder = holder;
+ }
+
+ @Override
+ public EdgeKind edgeKind() {
+ return EdgeKind.AnnotatedOn;
+ }
+
+ @Override
+ public GraphNode getSourceNode(Enqueuer enqueuer) {
+ if (holder.isDexClass()) {
+ return enqueuer.getClassGraphNode(holder.asDexClass().type);
+ } else if (holder.isDexEncodedField()) {
+ return enqueuer.getFieldGraphNode(holder.asDexEncodedField().field);
+ } else {
+ assert holder.isDexEncodedMethod();
+ return enqueuer.getMethodGraphNode(holder.asDexEncodedMethod().method);
+ }
+ }
+ }
+
private static class ReflectiveUseFrom extends BasedOnOtherMethod {
private ReflectiveUseFrom(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index f5a4b81..427341f 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -5,6 +5,8 @@
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexApplication;
@@ -39,6 +41,7 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.Range;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.google.common.base.Suppliers;
import java.util.ArrayList;
import java.util.IdentityHashMap;
@@ -66,13 +69,33 @@
}
private static class OptimizingPositionRemapper implements PositionRemapper {
- private int nextLineNumber = 1;
+ private final int maxLineDelta;
+ private DexMethod previousMethod = null;
+ private int previousSourceLine = -1;
+ private int nextOptimizedLineNumber = 1;
+
+ OptimizingPositionRemapper(InternalOptions options) {
+ // TODO(113198295): For dex using "Constants.DBG_LINE_RANGE + Constants.DBG_LINE_BASE"
+ // instead of 1 creates a ~30% smaller map file but the dex files gets larger due to reduced
+ // debug info canonicalization.
+ maxLineDelta = options.isGeneratingClassFiles() ? Integer.MAX_VALUE : 1;
+ }
@Override
public Position createRemappedPosition(
int line, DexString file, DexMethod method, Position callerPosition) {
- Position newPosition = new Position(nextLineNumber, file, method, null);
- ++nextLineNumber;
+ assert method != null;
+ if (previousMethod == method) {
+ assert previousSourceLine >= 0;
+ if (line > previousSourceLine && line - previousSourceLine <= maxLineDelta) {
+ nextOptimizedLineNumber += (line - previousSourceLine) - 1;
+ }
+ }
+
+ Position newPosition = new Position(nextOptimizedLineNumber, file, method, null);
+ ++nextOptimizedLineNumber;
+ previousSourceLine = line;
+ previousMethod = method;
return newPosition;
}
}
@@ -138,10 +161,9 @@
}
public static ClassNameMapper run(
+ AppView<AppInfoWithSubtyping> appView,
DexApplication application,
- GraphLense graphLense,
- NamingLens namingLens,
- boolean identityMapping) {
+ NamingLens namingLens) {
ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
// Collect which files contain which classes that need to have their line numbers optimized.
for (DexProgramClass clazz : application.classes()) {
@@ -151,7 +173,7 @@
// At this point we don't know if we really need to add this class to the builder.
// It depends on whether any methods/fields are renamed or some methods contain positions.
// Create a supplier which creates a new, cached ClassNaming.Builder on-demand.
- DexType originalType = graphLense.getOriginalType(clazz.type);
+ DexType originalType = appView.graphLense().getOriginalType(clazz.type);
DexString renamedClassName = namingLens.lookupDescriptor(clazz.getType());
Supplier<ClassNaming.Builder> onDemandClassNamingBuilder =
Suppliers.memoize(
@@ -164,7 +186,7 @@
addClassToClassNaming(originalType, renamedClassName, onDemandClassNamingBuilder);
// First transfer renamed fields to classNamingBuilder.
- addFieldsToClassNaming(graphLense, namingLens, clazz, onDemandClassNamingBuilder);
+ addFieldsToClassNaming(appView.graphLense(), namingLens, clazz, onDemandClassNamingBuilder);
// Then process the methods, ordered by renamed name.
List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
@@ -179,8 +201,12 @@
sortMethods(methods);
}
+ boolean identityMapping =
+ appView.options().lineNumberOptimization == LineNumberOptimization.OFF;
PositionRemapper positionRemapper =
- identityMapping ? new IdentityPositionRemapper() : new OptimizingPositionRemapper();
+ identityMapping
+ ? new IdentityPositionRemapper()
+ : new OptimizingPositionRemapper(appView.options());
for (DexEncodedMethod method : methods) {
List<MappedPosition> mappedPositions = new ArrayList<>();
@@ -194,7 +220,7 @@
}
}
- DexMethod originalMethod = graphLense.getOriginalMethodSignature(method.method);
+ DexMethod originalMethod = appView.graphLense().getOriginalMethodSignature(method.method);
MethodSignature originalSignature =
MethodSignature.fromDexMethod(originalMethod, originalMethod.holder != clazz.type);
@@ -217,7 +243,7 @@
signatures.put(originalMethod, originalSignature);
Function<DexMethod, MethodSignature> getOriginalMethodSignature =
m -> {
- DexMethod original = graphLense.getOriginalMethodSignature(m);
+ DexMethod original = appView.graphLense().getOriginalMethodSignature(m);
return signatures.computeIfAbsent(
original,
key ->
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/LineDeltaTest.java b/src/test/java/com/android/tools/r8/naming/retrace/LineDeltaTest.java
new file mode 100644
index 0000000..4e64e7e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/LineDeltaTest.java
@@ -0,0 +1,102 @@
+// 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.naming.retrace;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ForceInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+public class LineDeltaTest extends TestBase {
+ public String runTest(Backend backend) throws Exception {
+ return testForR8(backend)
+ .enableInliningAnnotations()
+ .addProgramClasses(LineDeltaTestClass.class)
+ .addKeepMainRule(LineDeltaTestClass.class)
+ .addKeepRules("-keepattributes LineNumberTable")
+ .run(LineDeltaTestClass.class)
+ .assertSuccessWithOutput(StringUtils.lines(
+ "In test1() - 1",
+ "In test1() - 2",
+ "In test1() - 3",
+ "In test1() - 4",
+ "In test2() - 1",
+ "In test2() - 2",
+ "In test2() - 3",
+ "In test2() - 4"
+ ))
+ .proguardMap();
+ }
+
+ private long mapLines(String map) {
+ return StringUtils.splitLines(map).stream().filter(line -> !line.startsWith("#")).count();
+ }
+
+ @Test
+ public void testDex() throws Exception {
+ assertEquals(17, mapLines(runTest(Backend.DEX)));
+ }
+
+ @Test
+ public void testCf() throws Exception {
+ assertEquals(5, mapLines(runTest(Backend.CF)));
+ }
+}
+
+class LineDeltaTestClass {
+ @ForceInline
+ static void test1() {
+ System.out.println("In test1() - 1");
+ // One line comment.
+ System.out.println("In test1() - 2");
+ // Two line comments.
+ //
+ System.out.println("In test1() - 3");
+ // Four line comments.
+ //
+ //
+ //
+ System.out.println("In test1() - 4");
+ }
+
+ @ForceInline
+ static void test2() {
+ System.out.println("In test2() - 1");
+ // Seven line comments.
+ //
+ //
+ //
+ //
+ //
+ //
+ System.out.println("In test2() - 2");
+ // Eight line comments.
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ System.out.println("In test2() - 3");
+ // Nine line comments.
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ System.out.println("In test2() - 4");
+ }
+
+ public static void main(String[] args) {
+ test1();
+ test2();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTest.java b/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTest.java
index 6a03a43..d199f57 100644
--- a/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTest.java
@@ -4,16 +4,21 @@
package com.android.tools.r8.shaking;
+import static junit.framework.TestCase.assertTrue;
+
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
-import org.junit.Ignore;
+import java.util.concurrent.ExecutionException;
import org.junit.Test;
class UnusedTypeInThrowing {
+ public static final String EXPECTED = "42";
+
public static void main(String[] args) throws UnusedTypeInThrowingThrowable {
- System.out.print("42");
+ System.out.print(EXPECTED);
}
}
@@ -25,14 +30,20 @@
static final Class MAIN_CLASS = UnusedTypeInThrowing.class;
@Test
- @Ignore("b/124019003")
- public void testTypeIsMarkedAsLive() throws IOException, CompilationFailedException {
- testForR8(Backend.CF)
- .addProgramClasses(MAIN_CLASS)
- .addProgramClasses(THROWABLE_CLASS)
- .addKeepMainRule(MAIN_CLASS)
- .addKeepRules("-keepattributes Exceptions")
- .run(MAIN_CLASS)
- .assertSuccessWithOutput("42");
+ public void testTypeIsMarkedAsLive()
+ throws IOException, CompilationFailedException, ExecutionException, NoSuchMethodException {
+ CodeInspector inspector =
+ testForR8(Backend.CF)
+ .enableGraphInspector()
+ .addProgramClasses(MAIN_CLASS)
+ .addProgramClasses(THROWABLE_CLASS)
+ .addKeepMainRule(MAIN_CLASS)
+ .addKeepRules("-keepattributes Exceptions")
+ .run(MAIN_CLASS)
+ .assertSuccessWithOutput(UnusedTypeInThrowing.EXPECTED)
+ .inspector();
+
+ assertTrue(inspector.clazz(THROWABLE_CLASS).isPresent());
+ // TODO(b/124217402) When done check that THROWABLE_CLASS is kept by the throwing annotation.
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTestRunner.java b/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTestRunner.java
deleted file mode 100644
index 2bf42a1..0000000
--- a/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTestRunner.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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.shaking;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.TestBase;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.nio.file.Path;
-import org.junit.Ignore;
-import org.junit.Test;
-
-public class UnusedTypeInThrowingTestRunner extends TestBase {
-
- static final Class THROWABLE_CLASS = UnusedTypeInThrowingThrowable.class;
- static final Class MAIN_CLASS = UnusedTypeInThrowingTest.class;
-
- @Test
- @Ignore("b/124019003")
- public void testTypeIsMarkedAsLive() throws IOException, CompilationFailedException {
- Path outDex = temp.newFile("out.zip").toPath();
- testForR8(Backend.CF)
- .addProgramClasses(MAIN_CLASS)
- .addProgramClasses(THROWABLE_CLASS)
- .addKeepMainRule(MAIN_CLASS)
- .addKeepRules(ImmutableList.of("-keepattributes Exceptions"))
- .setMode(CompilationMode.RELEASE)
- .enableInliningAnnotations()
- .minification(true)
- .compile()
- .run(MAIN_CLASS)
- .assertSuccessWithOutput("42");
- }
-}
diff --git a/third_party/openjdk/openjdk-9.0.4/osx.tar.gz.sha1 b/third_party/openjdk/openjdk-9.0.4/osx.tar.gz.sha1
index 7db6817..231fb73 100644
--- a/third_party/openjdk/openjdk-9.0.4/osx.tar.gz.sha1
+++ b/third_party/openjdk/openjdk-9.0.4/osx.tar.gz.sha1
@@ -1 +1 @@
-a68b718365f7ce214eec2f6bb89311885940b10e
\ No newline at end of file
+7084f18a3aad664361c52188d5f899d3050e3eb6
\ No newline at end of file