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