Desugared library: files move and copy test
Fix library overrides in all definitions
Fix private fields in all definitions
Fix some desugared library errors
Change-Id: I92d7bf3afbf609fdea21683941cfd15c90305cf2
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
index 027d74f..61af0ad 100644
--- a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
@@ -9,8 +9,12 @@
import java.nio.channels.DesugarChannels;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
+import java.nio.file.CopyOption;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.spi.FileSystemProvider;
import java.util.Set;
@@ -28,6 +32,37 @@
}
@Override
+ public void copy(Path source, Path target, CopyOption... options) throws IOException {
+ if (!containsCopyOption(options, StandardCopyOption.REPLACE_EXISTING) && Files.exists(target)) {
+ throw new FileAlreadyExistsException(target.toString());
+ }
+ if (containsCopyOption(options, StandardCopyOption.ATOMIC_MOVE)) {
+ throw new UnsupportedOperationException("Unsupported copy option");
+ }
+ super.copy(source, target, options);
+ }
+
+ @Override
+ public void move(Path source, Path target, CopyOption... options) throws IOException {
+ if (!containsCopyOption(options, StandardCopyOption.REPLACE_EXISTING) && Files.exists(target)) {
+ throw new FileAlreadyExistsException(target.toString());
+ }
+ if (containsCopyOption(options, StandardCopyOption.COPY_ATTRIBUTES)) {
+ throw new UnsupportedOperationException("Unsupported copy option");
+ }
+ super.move(source, target, options);
+ }
+
+ private boolean containsCopyOption(CopyOption[] options, CopyOption option) {
+ for (CopyOption copyOption : options) {
+ if (copyOption == option) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
public SeekableByteChannel newByteChannel(
Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
if (path.toFile().isDirectory()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index aeab5fe..3463789 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -41,6 +41,11 @@
}
@Override
+ public LookupTarget toLookupTarget(DexClassAndMethod classAndMethod) {
+ return classAndMethod;
+ }
+
+ @Override
public MethodAccessFlags getAccessFlags() {
return getDefinition().getAccessFlags();
}
diff --git a/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java b/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
index e1209e9..3ada6dc 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
@@ -29,6 +29,11 @@
}
@Override
+ public LookupTarget toLookupTarget(DexClassAndMethod classAndMethod) {
+ return new LookupLambdaTarget(lambda, classAndMethod);
+ }
+
+ @Override
public void accept(
Consumer<LookupMethodTarget> methodConsumer, Consumer<LookupLambdaTarget> lambdaConsumer) {
lambdaConsumer.accept(this);
@@ -37,4 +42,9 @@
public DexClassAndMethod getImplementationMethod() {
return method;
}
+
+ @Override
+ public DexClassAndMethod getTargetOrImplementationMethod() {
+ return getImplementationMethod();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java b/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java
index b7a6aa0..6df0bbd 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java
@@ -30,4 +30,9 @@
DexEncodedMethod getDefinition();
DexClassAndMethod getTarget();
+
+ @Override
+ default DexClassAndMethod getTargetOrImplementationMethod() {
+ return getTarget();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java b/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java
index d69c5b3..c21584e 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java
@@ -15,6 +15,11 @@
}
@Override
+ public LookupTarget toLookupTarget(DexClassAndMethod classAndMethod) {
+ return new LookupMethodTargetWithAccessOverride(classAndMethod, accessOverride);
+ }
+
+ @Override
public DexClassAndMethod getAccessOverride() {
return accessOverride;
}
diff --git a/src/main/java/com/android/tools/r8/graph/LookupTarget.java b/src/main/java/com/android/tools/r8/graph/LookupTarget.java
index 4530c0f..a77ed1d 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupTarget.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupTarget.java
@@ -26,6 +26,10 @@
return null;
}
+ LookupTarget toLookupTarget(DexClassAndMethod classAndMethod);
+
void accept(
Consumer<LookupMethodTarget> methodConsumer, Consumer<LookupLambdaTarget> lambdaConsumer);
+
+ DexClassAndMethod getTargetOrImplementationMethod();
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index 94bb52d..8c949fe 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -348,9 +348,11 @@
if (type == null) {
return null;
}
- return appInfo
- .contextIndependentDefinitionForWithResolutionResult(type)
- .toSingleClassWithLibraryOverProgram();
+ ClassResolutionResult resolutionResult =
+ appInfo.contextIndependentDefinitionForWithResolutionResult(type);
+ return appInfo.options().lookupLibraryBeforeProgram
+ ? resolutionResult.toSingleClassWithLibraryOverProgram()
+ : resolutionResult.toSingleClassWithProgramOverLibrary();
}
/**
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
index e5d00a9..40ee16e 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InitClassLens;
import com.android.tools.r8.graph.InnerClassAttribute;
@@ -135,7 +136,11 @@
// For fields and methods that cannot be found the chance of recovering by repackaging is
// pretty slim thus we allow for repackaging the callers.
if (resolutionResult.isFieldResolutionResult()) {
- assert resolutionResult.asFieldResolutionResult().isFailedResolution();
+ FieldResolutionResult fieldResolutionResult = resolutionResult.asFieldResolutionResult();
+ assert fieldResolutionResult.isFailedResolution()
+ || (fieldResolutionResult.isMultiFieldResolutionResult()
+ && fieldResolutionResult.getProgramField() != null
+ && fieldResolutionResult.getProgramField().getDefinition().isPrivate());
return;
}
MethodResolutionResult methodResult = resolutionResult.asMethodResolutionResult();
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 12c3c41..7b630b6 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -41,6 +41,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexDefinition;
@@ -146,6 +147,7 @@
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.OptionalBool;
@@ -1092,15 +1094,24 @@
DexField field, ProgramMethod context, boolean isRead, boolean isReflective) {
FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
if (info == null) {
- DexEncodedField encodedField = resolveField(field, context).getResolvedField();
+ Box<DexClassAndField> seenResult = new Box<>();
+ resolveField(field, context)
+ .forEachSuccessfulFieldResolutionResult(
+ singleResolutionResult -> {
+ DexClassAndField resolutionPair = singleResolutionResult.getResolutionPair();
+ if (!seenResult.isSet() || resolutionPair.isProgramField()) {
+ seenResult.set(resolutionPair);
+ }
+ });
// If the field does not exist, then record this in the mapping, such that we don't have to
// resolve the field the next time.
- if (encodedField == null) {
+ if (!seenResult.isSet()) {
fieldAccessInfoCollection.extend(field, MISSING_FIELD_ACCESS_INFO);
return true;
}
+ DexEncodedField encodedField = seenResult.get().getDefinition();
info = getOrCreateFieldAccessInfo(encodedField);
// If `field` is an indirect reference, then create a mapping for it, such that we don't have
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
index 1344816..021aa9d 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferences.java
@@ -30,6 +30,7 @@
public static void run(TraceReferencesCommand command) throws CompilationFailedException {
InternalOptions options = new InternalOptions();
options.loadAllClassDefinitions = true;
+ options.lookupLibraryBeforeProgram = true;
ExceptionUtils.withCompilationHandler(
command.getReporter(), () -> runInternal(command, options));
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveCopyTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveCopyTest.java
new file mode 100644
index 0000000..3ef9f34
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveCopyTest.java
@@ -0,0 +1,210 @@
+// Copyright (c) 2022, 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.desugaredlibrary.jdk11;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.COPY;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.MOVE;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.NewFile.EXISTING;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.NewFile.NEW;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.CopyOption;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests Files#copy and Files#move methods. Known limitations/details below 26: - ATOMIC MOVE is a
+ * no-op, moves are effectively file renaming which seem to be atomic on Android. - COPY_ATTRIBUTES
+ * is a no-op, except if unsupported in which case it throws appropriately, now it seems only basic
+ * file attributes are supported below 26 anyway and such attributes are not meaningfully copied
+ * around anyway. Creation/last modification/last access times are set to the time of the move/copy,
+ * and the other ones such as "isDirectory" cannot be easily abused.
+ */
+@RunWith(Parameterized.class)
+public class FilesMoveCopyTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "Testing MOVE NEW NONE",
+ "MOVE_NONE",
+ "Testing COPY NEW NONE",
+ "COPY_NONE",
+ "Testing MOVE EXISTING NONE",
+ "Failure: class java.nio.file.FileAlreadyExistsException :: dest",
+ "Testing COPY EXISTING NONE",
+ "Failure: class java.nio.file.FileAlreadyExistsException :: dest",
+ "Testing MOVE NEW ATOMIC_MOVE",
+ "MOVE_ATOMIC_MOVE",
+ "Testing COPY NEW ATOMIC_MOVE",
+ "Failure: class java.lang.UnsupportedOperationException :: Unsupported copy option",
+ "Testing MOVE NEW COPY_ATTRIBUTES",
+ "Failure: class java.lang.UnsupportedOperationException :: Unsupported copy option",
+ "Testing COPY NEW COPY_ATTRIBUTES",
+ "COPY_COPY_ATTRIBUTES",
+ "Testing MOVE NEW REPLACE_EXISTING",
+ "MOVE_REPLACE_EXISTING",
+ "Testing COPY NEW REPLACE_EXISTING",
+ "COPY_REPLACE_EXISTING",
+ "Testing MOVE EXISTING REPLACE_EXISTING",
+ "MOVE_REPLACE_EXISTING",
+ "Testing COPY EXISTING REPLACE_EXISTING",
+ "COPY_REPLACE_EXISTING",
+ "Testing MOVE NEW NOFOLLOW_LINKS",
+ "MOVE_NOFOLLOW_LINKS",
+ "Testing COPY NEW NOFOLLOW_LINKS",
+ "COPY_NOFOLLOW_LINKS");
+
+ private final TestParameters parameters;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+ private final CompilationSpecification compilationSpecification;
+
+ @Parameters(name = "{0}, spec: {1}, {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ // Skip Android 4.4.4 due to missing libjavacrypto.
+ getTestParameters()
+ .withCfRuntime(CfVm.JDK11)
+ .withDexRuntime(Version.V4_0_4)
+ .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+ .withAllApiLevels()
+ .build(),
+ ImmutableList.of(JDK11_PATH),
+ DEFAULT_SPECIFICATIONS);
+ }
+
+ public FilesMoveCopyTest(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ this.compilationSpecification = compilationSpecification;
+ }
+
+ @Test
+ public void test() throws Throwable {
+ if (parameters.isCfRuntime()) {
+ // Reference runtime, we use Jdk 11 since this is Jdk 11 desugared library, not that Jdk 8
+ // behaves differently on this test.
+ Assume.assumeTrue(parameters.isCfRuntime(CfVm.JDK11) && !ToolHelper.isWindows());
+ testForJvm()
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ Assume.assumeTrue(
+ "CopyOption conversion not in the desugared library code of 4.0.",
+ parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)
+ || parameters.getDexRuntimeVersion().isOlderThan(Version.V7_0_0));
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .withArt6Plus64BitsLib()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ public static class TestClass {
+
+ enum CopyOrMove {
+ COPY,
+ MOVE
+ }
+
+ enum NewFile {
+ EXISTING,
+ NEW
+ }
+
+ public static void main(String[] args) throws Throwable {
+ for (CopyOption copyOption : allOptions()) {
+ test(MOVE, NEW, copyOption);
+ test(COPY, NEW, copyOption);
+ if (copyOption == null || copyOption == StandardCopyOption.REPLACE_EXISTING) {
+ test(MOVE, EXISTING, copyOption);
+ test(COPY, EXISTING, copyOption);
+ }
+ }
+ }
+
+ private static CopyOption[] allOptions() {
+ return new CopyOption[] {
+ null,
+ StandardCopyOption.ATOMIC_MOVE,
+ StandardCopyOption.COPY_ATTRIBUTES,
+ StandardCopyOption.REPLACE_EXISTING,
+ LinkOption.NOFOLLOW_LINKS
+ };
+ }
+
+ private static void clearTmp(Path src, Path dest) throws IOException {
+ if (Files.exists(src)) {
+ Files.delete(src);
+ }
+ if (Files.exists(dest)) {
+ Files.delete(dest);
+ }
+ }
+
+ private static void test(CopyOrMove copyOrMove, NewFile newFile, CopyOption copyOption)
+ throws IOException {
+ String copyOptionString = copyOption == null ? "NONE" : copyOption.toString();
+ CopyOption[] opts = copyOption == null ? new CopyOption[0] : new CopyOption[] {copyOption};
+ System.out.println("Testing " + copyOrMove + " " + newFile + " " + copyOptionString);
+ Path src = Files.createTempFile("src_", ".txt");
+ Path dest =
+ newFile == NEW
+ ? src.getParent().resolve("dest_" + System.currentTimeMillis() + ".txt")
+ : Files.createTempFile("dest_", ".txt");
+ String toWrite = copyOrMove + "_" + copyOptionString;
+ Files.write(src, toWrite.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE);
+ try {
+ if (copyOrMove == COPY) {
+ Files.copy(src, dest, opts);
+ } else {
+ Files.move(src, dest, opts);
+ }
+ System.out.println(Files.readAllLines(dest).get(0));
+ } catch (Throwable t) {
+ String subMessage;
+ if (t instanceof FileAlreadyExistsException && t.getMessage() != null) {
+ // The message is the file path, however we cannot rely on temp path, so we check
+ // only against the known part of the path.
+ String[] split = t.getMessage().split("/");
+ subMessage = split[split.length - 1].substring(0, 4);
+ } else {
+ subMessage = t.getMessage();
+ }
+ System.out.println("Failure: " + t.getClass() + " :: " + subMessage);
+ }
+ clearTmp(src, dest);
+ }
+ }
+}