Merge commit 'a9f4c6589326e50d38c30c0023ab30a5cae289dd' into dev-release
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
index ca219b2..260c31c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
@@ -26,6 +26,14 @@
public static final String consequences = "consequences";
}
+ public static final class UsesReflection {
+ public static final Class<com.android.tools.r8.keepanno.annotations.UsesReflection> CLASS =
+ com.android.tools.r8.keepanno.annotations.UsesReflection.class;
+ public static final String DESCRIPTOR = getDescriptor(CLASS);
+ public static final String value = "value";
+ public static final String additionalPreconditions = "additionalPreconditions";
+ }
+
// Implicit hidden item which is "super type" of Condition and Target.
public static final class Item {
public static final String classConstant = "classConstant";
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java
new file mode 100644
index 0000000..22fcb36
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsesReflection.java
@@ -0,0 +1,62 @@
+// 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.keepanno.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to declare the reflective usages made by an item.
+ *
+ * <p>The annotation's 'value' is a list of targets to be kept if the annotated item is used. The
+ * annotated item is a precondition for keeping any of the specified targets. Thus, if an annotated
+ * method is determined to be unused by the program, the annotation itself will not be in effect and
+ * the targets will not be kept (assuming nothing else is otherwise keeping them).
+ *
+ * <p>The annotation's 'additionalPreconditions' is optional and can specify additional conditions
+ * that should be satisfied for the annotation to be in effect.
+ *
+ * <p>The translation of the @UsesReflection annotation into a @KeepEdge is as follows:
+ *
+ * <p>Assume the item of the annotation is denoted by 'CTX' and referred to as its context.
+ *
+ * <pre>
+ * @UsesReflection(value = targets, [additionalPreconditions = preconditions])
+ * ==>
+ * @KeepEdge(
+ * consequences = targets,
+ * preconditions = {createConditionFromContext(CTX)} + preconditions
+ * )
+ *
+ * where
+ * KeepCondition createConditionFromContext(ctx) {
+ * if (ctx.isClass()) {
+ * return new KeepCondition(classTypeName = ctx.getClassTypeName());
+ * }
+ * if (ctx.isMethod()) {
+ * return new KeepCondition(
+ * classTypeName = ctx.getClassTypeName(),
+ * methodName = ctx.getMethodName(),
+ * methodReturnType = ctx.getMethodReturnType(),
+ * methodParameterTypes = ctx.getMethodParameterTypes());
+ * }
+ * if (ctx.isField()) {
+ * return new KeepCondition(
+ * classTypeName = ctx.getClassTypeName(),
+ * fieldName = ctx.getFieldName()
+ * fieldType = ctx.getFieldType());
+ * }
+ * // unreachable
+ * }
+ * </pre>
+ */
+@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface UsesReflection {
+ KeepTarget[] value();
+
+ KeepCondition[] additionalPreconditions() default {};
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index dd5be82..3545a25 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.asm;
+import com.android.tools.r8.keepanno.annotations.KeepConstants;
import com.android.tools.r8.keepanno.annotations.KeepConstants.Condition;
import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
import com.android.tools.r8.keepanno.annotations.KeepConstants.Item;
@@ -43,66 +44,143 @@
private static class KeepEdgeClassVisitor extends ClassVisitor {
private final Parent<KeepEdge> parent;
+ private String className;
KeepEdgeClassVisitor(Parent<KeepEdge> parent) {
super(ASM_VERSION);
this.parent = parent;
}
+ private static String binaryNameToTypeName(String binaryName) {
+ return binaryName.replace('/', '.');
+ }
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ this.className = binaryNameToTypeName(name);
+ }
+
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ if (visible) {
+ return null;
+ }
// Skip any visible annotations as @KeepEdge is not runtime visible.
- if (!visible && descriptor.equals(Edge.DESCRIPTOR)) {
+ if (descriptor.equals(Edge.DESCRIPTOR)) {
return new KeepEdgeVisitor(parent);
}
+ if (descriptor.equals(KeepConstants.UsesReflection.DESCRIPTOR)) {
+ KeepItemPattern classItem =
+ KeepItemPattern.builder()
+ .setClassPattern(KeepQualifiedClassNamePattern.exact(className))
+ .build();
+ return new UsesReflectionVisitor(parent, classItem);
+ }
return null;
}
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
- return new KeepEdgeMethodVisitor(parent);
+ return new KeepEdgeMethodVisitor(parent, className, name, descriptor);
}
@Override
public FieldVisitor visitField(
int access, String name, String descriptor, String signature, Object value) {
- return new KeepEdgeFieldVisitor(parent);
+ return new KeepEdgeFieldVisitor(parent, className, name, descriptor);
}
}
private static class KeepEdgeMethodVisitor extends MethodVisitor {
private final Parent<KeepEdge> parent;
+ private final String className;
+ private final String methodName;
+ private final String methodDescriptor;
- KeepEdgeMethodVisitor(Parent<KeepEdge> parent) {
+ KeepEdgeMethodVisitor(
+ Parent<KeepEdge> parent, String className, String methodName, String methodDescriptor) {
super(ASM_VERSION);
this.parent = parent;
+ this.className = className;
+ this.methodName = methodName;
+ this.methodDescriptor = methodDescriptor;
+ }
+
+ private KeepItemPattern createItemContext() {
+ Type returnType = Type.getReturnType(methodDescriptor);
+ Type[] argumentTypes = Type.getArgumentTypes(methodDescriptor);
+ // TODO(b/248408342): Defaults are "any", support setting actual return type and params.
+ return KeepItemPattern.builder()
+ .setClassPattern(KeepQualifiedClassNamePattern.exact(className))
+ .setMemberPattern(
+ KeepMethodPattern.builder()
+ .setNamePattern(KeepMethodNamePattern.exact(methodName))
+ .build())
+ .build();
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
// Skip any visible annotations as @KeepEdge is not runtime visible.
- if (!visible && descriptor.equals(Edge.DESCRIPTOR)) {
+ if (visible) {
+ return null;
+ }
+ if (descriptor.equals(Edge.DESCRIPTOR)) {
return new KeepEdgeVisitor(parent);
}
+ if (descriptor.equals(KeepConstants.UsesReflection.DESCRIPTOR)) {
+ return new UsesReflectionVisitor(parent, createItemContext());
+ }
return null;
}
}
private static class KeepEdgeFieldVisitor extends FieldVisitor {
private final Parent<KeepEdge> parent;
+ private final String className;
+ private final String fieldName;
+ private final String fieldDescriptor;
- KeepEdgeFieldVisitor(Parent<KeepEdge> parent) {
+ KeepEdgeFieldVisitor(
+ Parent<KeepEdge> parent, String className, String fieldName, String fieldDescriptor) {
super(ASM_VERSION);
this.parent = parent;
+ this.className = className;
+ this.fieldName = fieldName;
+ this.fieldDescriptor = fieldDescriptor;
+ }
+
+ private KeepItemPattern createItemContext() {
+ // TODO(b/248408342): Default type is "any", support setting actual field type.
+ return KeepItemPattern.builder()
+ .setClassPattern(KeepQualifiedClassNamePattern.exact(className))
+ .setMemberPattern(
+ KeepFieldPattern.builder()
+ .setNamePattern(KeepFieldNamePattern.exact(fieldName))
+ .build())
+ .build();
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
// Skip any visible annotations as @KeepEdge is not runtime visible.
- if (!visible && descriptor.equals(Edge.DESCRIPTOR)) {
+ if (visible) {
+ return null;
+ }
+ if (descriptor.equals(Edge.DESCRIPTOR)) {
return new KeepEdgeVisitor(parent);
}
+ if (descriptor.equals(KeepConstants.UsesReflection.DESCRIPTOR)) {
+ return new UsesReflectionVisitor(parent, createItemContext());
+ }
return null;
}
}
@@ -164,6 +242,36 @@
}
}
+ private static class UsesReflectionVisitor extends AnnotationVisitorBase {
+ private final Parent<KeepEdge> parent;
+ private final KeepEdge.Builder builder = KeepEdge.builder();
+ private final KeepPreconditions.Builder preconditions = KeepPreconditions.builder();
+
+ UsesReflectionVisitor(Parent<KeepEdge> parent, KeepItemPattern context) {
+ this.parent = parent;
+ preconditions.addCondition(KeepCondition.builder().setItem(context).build());
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ if (name.equals(KeepConstants.UsesReflection.value)) {
+ return new KeepConsequencesVisitor(builder::setConsequences);
+ }
+ if (name.equals(KeepConstants.UsesReflection.additionalPreconditions)) {
+ return new KeepPreconditionsVisitor(
+ additionalPreconditions -> {
+ additionalPreconditions.forEach(preconditions::addCondition);
+ });
+ }
+ return super.visitArray(name);
+ }
+
+ @Override
+ public void visitEnd() {
+ parent.accept(builder.setPreconditions(preconditions.build()).build());
+ }
+ }
+
private static class KeepPreconditionsVisitor extends AnnotationVisitorBase {
private final Parent<KeepPreconditions> parent;
private final KeepPreconditions.Builder builder = KeepPreconditions.builder();
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidBasicFileAttributeView.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidBasicFileAttributeView.java
new file mode 100644
index 0000000..b8be0e2
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidBasicFileAttributeView.java
@@ -0,0 +1,32 @@
+// 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 desugar.sun.nio.fs;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Map;
+
+class DesugarAndroidBasicFileAttributeView extends DesugarBasicFileAttributeView {
+
+ private final Path path;
+
+ public DesugarAndroidBasicFileAttributeView(Path path) {
+ super(path);
+ this.path = path;
+ }
+
+ @Override
+ public BasicFileAttributes readAttributes() throws IOException {
+ path.getFileSystem().provider().checkAccess(path);
+ return super.readAttributes();
+ }
+
+ @Override
+ public Map<String, Object> readAttributes(String[] requested) throws IOException {
+ path.getFileSystem().provider().checkAccess(path);
+ return super.readAttributes(requested);
+ }
+}
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 42a3f91..6b4a062 100644
--- a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
@@ -12,12 +12,16 @@
import java.nio.file.CopyOption;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
+import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
+import java.util.Map;
import java.util.Set;
/** Linux implementation of {@link FileSystemProvider} for desugar support. */
@@ -72,6 +76,62 @@
}
@Override
+ public <V extends FileAttributeView> V getFileAttributeView(
+ Path path, Class<V> type, LinkOption... options) {
+ if (type == null) {
+ throw new NullPointerException();
+ }
+ if (type == BasicFileAttributeView.class) {
+ return type.cast(new DesugarAndroidBasicFileAttributeView(path));
+ }
+ return null;
+ }
+
+ @Override
+ public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options)
+ throws IOException {
+ int attributesTypeIndexEnd = attributes.indexOf(":");
+ final Class<? extends BasicFileAttributeView> attributeViewType;
+ final String[] requestedAttributes;
+ if (attributesTypeIndexEnd == -1) {
+ attributeViewType = BasicFileAttributeView.class;
+ requestedAttributes = attributes.split(",");
+ } else {
+ String attributeTypeSpec = attributes.substring(0, attributesTypeIndexEnd);
+ if ("basic".equals(attributeTypeSpec)) {
+ attributeViewType = BasicFileAttributeView.class;
+ } else {
+ throw new UnsupportedOperationException(
+ String.format("Requested attribute type for: %s is not available.", attributeTypeSpec));
+ }
+ requestedAttributes = attributes.substring(attributesTypeIndexEnd + 1).split(",");
+ }
+ if (attributeViewType == BasicFileAttributeView.class) {
+ DesugarBasicFileAttributeView attrView = new DesugarAndroidBasicFileAttributeView(path);
+ return attrView.readAttributes(requestedAttributes);
+ }
+ throw new AssertionError("Unexpected View '" + attributeViewType + "' requested");
+ }
+
+ private boolean exists(Path file) {
+ try {
+ checkAccess(file);
+ return true;
+ } catch (IOException ioe) {
+ return false;
+ }
+ }
+
+ @Override
+ public void delete(Path path) throws IOException {
+ if (exists(path)) {
+ deleteIfExists(path);
+ return;
+ }
+ throw new NoSuchFileException(path.toString());
+ }
+
+ @Override
public SeekableByteChannel newByteChannel(
Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
if (path.toFile().isDirectory()) {
@@ -90,4 +150,17 @@
}
return DesugarChannels.openEmulatedFileChannel(path, options, attrs);
}
+
+ @Override
+ public boolean isSameFile(Path path, Path path2) throws IOException {
+ // If the paths are equals, then it answers true even if they do not exist.
+ if (path.equals(path2)) {
+ return true;
+ }
+ // If the paths are not equal, they could still be equal due to symbolic link and so on, but
+ // in that case accessibility is checked.
+ checkAccess(path);
+ checkAccess(path2);
+ return super.isSameFile(path, path2);
+ }
}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarBasicFileAttributeView.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarBasicFileAttributeView.java
new file mode 100644
index 0000000..c62a952
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarBasicFileAttributeView.java
@@ -0,0 +1,27 @@
+// 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 desugar.sun.nio.fs;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.util.Map;
+
+public class DesugarBasicFileAttributeView {
+
+ public DesugarBasicFileAttributeView(Path path) {}
+
+ public BasicFileAttributes readAttributes() throws IOException {
+ return null;
+ }
+
+ public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime)
+ throws IOException {}
+
+ public Map<String, Object> readAttributes(String[] requested) throws IOException {
+ return null;
+ }
+}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarBasicFileAttributes.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarBasicFileAttributes.java
new file mode 100644
index 0000000..8121b78
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarBasicFileAttributes.java
@@ -0,0 +1,61 @@
+// 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 desugar.sun.nio.fs;
+
+import java.io.File;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+
+public class DesugarBasicFileAttributes implements BasicFileAttributes {
+
+ static DesugarBasicFileAttributes create(File file) {
+ return null;
+ }
+
+ @Override
+ public FileTime lastModifiedTime() {
+ return null;
+ }
+
+ @Override
+ public FileTime lastAccessTime() {
+ return null;
+ }
+
+ @Override
+ public FileTime creationTime() {
+ return null;
+ }
+
+ @Override
+ public boolean isRegularFile() {
+ return false;
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return false;
+ }
+
+ @Override
+ public boolean isSymbolicLink() {
+ return false;
+ }
+
+ @Override
+ public boolean isOther() {
+ return false;
+ }
+
+ @Override
+ public long size() {
+ return 0;
+ }
+
+ @Override
+ public Object fileKey() {
+ return null;
+ }
+}
diff --git a/src/library_desugar/java/java/nio/channels/DesugarChannels.java b/src/library_desugar/java/java/nio/channels/DesugarChannels.java
index 4ca851d..15040ef 100644
--- a/src/library_desugar/java/java/nio/channels/DesugarChannels.java
+++ b/src/library_desugar/java/java/nio/channels/DesugarChannels.java
@@ -9,6 +9,8 @@
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
@@ -20,17 +22,6 @@
public class DesugarChannels {
- /** Special conversion for Channel to answer a converted FileChannel if required. */
- public static Channel convertMaybeLegacyChannelFromLibrary(Channel raw) {
- if (raw == null) {
- return null;
- }
- if (raw instanceof FileChannel) {
- return convertMaybeLegacyFileChannelFromLibrary((FileChannel) raw);
- }
- return raw;
- }
-
/**
* Below Api 24 FileChannel does not implement SeekableByteChannel. When we get one from the
* library, we wrap it to implement the interface.
@@ -46,32 +37,41 @@
}
/**
- * We unwrap when going to the library since we cannot intercept the calls to final methods in the
- * library.
+ * All FileChannels below 24 are wrapped to support the new interface SeekableByteChannel.
+ * FileChannels between 24 and 26 are wrapped only to improve the emulation of program opened
+ * FileChannels, especially with the append and delete on close options.
*/
- public static FileChannel convertMaybeLegacyFileChannelToLibrary(FileChannel raw) {
- if (raw == null) {
- return null;
- }
- if (raw instanceof WrappedFileChannel) {
- return ((WrappedFileChannel) raw).delegate;
- }
- return raw;
- }
-
static class WrappedFileChannel extends FileChannel implements SeekableByteChannel {
final FileChannel delegate;
+ final boolean deleteOnClose;
+ final boolean appendMode;
+ final Path path;
public static FileChannel wrap(FileChannel channel) {
if (channel instanceof WrappedFileChannel) {
return channel;
}
- return new WrappedFileChannel(channel);
+ return new WrappedFileChannel(channel, false, false, null);
}
- private WrappedFileChannel(FileChannel delegate) {
+ public static FileChannel withExtraOptions(
+ FileChannel channel, Set<? extends OpenOption> options, Path path) {
+ FileChannel raw =
+ channel instanceof WrappedFileChannel ? ((WrappedFileChannel) channel).delegate : channel;
+ return new WrappedFileChannel(
+ raw,
+ options.contains(StandardOpenOption.DELETE_ON_CLOSE),
+ options.contains(StandardOpenOption.APPEND),
+ path);
+ }
+
+ private WrappedFileChannel(
+ FileChannel delegate, boolean deleteOnClose, boolean appendMode, Path path) {
this.delegate = delegate;
+ this.deleteOnClose = deleteOnClose;
+ this.appendMode = appendMode;
+ this.path = deleteOnClose ? path : null;
}
@Override
@@ -86,6 +86,9 @@
@Override
public int write(ByteBuffer src) throws IOException {
+ if (appendMode) {
+ return delegate.write(src, size());
+ }
return delegate.write(src);
}
@@ -146,20 +149,57 @@
return delegate.map(mode, position, size);
}
+ // When using lock or tryLock the extra options are lost.
@Override
public FileLock lock(long position, long size, boolean shared) throws IOException {
- return delegate.lock(position, size, shared);
+ return wrapLock(delegate.lock(position, size, shared));
}
@Override
public FileLock tryLock(long position, long size, boolean shared) throws IOException {
- return delegate.tryLock(position, size, shared);
+ return wrapLock(delegate.tryLock(position, size, shared));
+ }
+
+ private FileLock wrapLock(FileLock lock) {
+ if (lock == null) {
+ return null;
+ }
+ return new WrappedFileChannelFileLock(lock, this);
}
@Override
public void implCloseChannel() throws IOException {
// We cannot call the protected method, this should be effectively equivalent.
delegate.close();
+ if (deleteOnClose) {
+ Files.deleteIfExists(path);
+ }
+ }
+ }
+
+ /**
+ * The FileLock state is final and duplicated in the wrapper, besides the FileChannel where the
+ * wrapped file channel is used. All methods in FileLock, even channel(), use the duplicated and
+ * corrected state. Only 2 methods require to dispatch to the delegate which is effectively
+ * holding the lock.
+ */
+ static class WrappedFileChannelFileLock extends FileLock {
+
+ private final FileLock delegate;
+
+ WrappedFileChannelFileLock(FileLock delegate, WrappedFileChannel wrappedFileChannel) {
+ super(wrappedFileChannel, delegate.position(), delegate.size(), delegate.isShared());
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean isValid() {
+ return delegate.isValid();
+ }
+
+ @Override
+ public void release() throws IOException {
+ delegate.release();
}
}
@@ -196,23 +236,35 @@
RandomAccessFile randomAccessFile =
new RandomAccessFile(path.toFile(), getFileAccessModeText(openOptions));
- if (openOptions.contains(StandardOpenOption.TRUNCATE_EXISTING)) {
+ // TRUNCATE_EXISTING is ignored if the file is not writable.
+ // TRUNCATE_EXISTING is not compatible with APPEND, so we just need to check for WRITE.
+ if (openOptions.contains(StandardOpenOption.TRUNCATE_EXISTING)
+ && openOptions.contains(StandardOpenOption.WRITE)) {
randomAccessFile.setLength(0);
}
- if (!openOptions.contains(StandardOpenOption.APPEND)) {
+ if (!openOptions.contains(StandardOpenOption.APPEND)
+ && !openOptions.contains(StandardOpenOption.DELETE_ON_CLOSE)) {
// This one may be retargeted, below 24, to support SeekableByteChannel.
return randomAccessFile.getChannel();
}
- // TODO(b/259056135): Consider subclassing UnsupportedOperationException for desugared library.
- // RandomAccessFile does not support APPEND.
- // We could hack a wrapper to support APPEND in simple cases such as Files.write().
- throw new UnsupportedOperationException();
+ return WrappedFileChannel.withExtraOptions(randomAccessFile.getChannel(), openOptions, path);
}
private static void validateOpenOptions(Path path, Set<? extends OpenOption> openOptions)
- throws NoSuchFileException {
+ throws IOException {
+ if (Files.exists(path)) {
+ if (openOptions.contains(StandardOpenOption.CREATE_NEW)
+ && openOptions.contains(StandardOpenOption.WRITE)) {
+ throw new FileAlreadyExistsException(path.toString());
+ }
+ } else {
+ if (!(openOptions.contains(StandardOpenOption.CREATE)
+ || openOptions.contains(StandardOpenOption.CREATE_NEW))) {
+ throw new NoSuchFileException(path.toString());
+ }
+ }
// Validations that resemble sun.nio.fs.UnixChannelFactory#newFileChannel.
if (openOptions.contains(StandardOpenOption.READ)
&& openOptions.contains(StandardOpenOption.APPEND)) {
@@ -222,13 +274,11 @@
&& openOptions.contains(StandardOpenOption.TRUNCATE_EXISTING)) {
throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
}
- if (openOptions.contains(StandardOpenOption.APPEND) && !path.toFile().exists()) {
- throw new NoSuchFileException(path.toString());
- }
}
private static String getFileAccessModeText(Set<? extends OpenOption> options) {
- if (!options.contains(StandardOpenOption.WRITE)) {
+ if (!options.contains(StandardOpenOption.WRITE)
+ && !options.contains(StandardOpenOption.APPEND)) {
return "r";
}
if (options.contains(StandardOpenOption.SYNC)) {
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index f4a3aef..4bcf1a3 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -355,12 +355,9 @@
"java.util.stream.LongStream java.util.stream.Stream#flatMapToLong(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.Function)"],
"java.util.stream.LongStream java.util.stream.LongStream#flatMap(java.util.function.LongFunction)": [0, "java.util.function.LongFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.LongFunction)"],
"java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.StackWalkerApiFlips#flipFunctionStream(java.util.function.Function)"],
- "java.nio.channels.FileChannel java.nio.channels.FileLock#channel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
- "java.nio.channels.Channel java.nio.channels.FileLock#acquiredBy()": [-1, "java.nio.channels.Channel java.nio.channels.DesugarChannels#convertMaybeLegacyChannelFromLibrary(java.nio.channels.Channel)"],
"java.nio.channels.FileChannel java.io.RandomAccessFile#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
"java.nio.channels.FileChannel java.io.FileInputStream#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
- "java.nio.channels.FileChannel java.io.FileOutputStream#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
- "void java.nio.channels.FileLock#<init>(java.nio.channels.FileChannel,long, long, boolean)": [0, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelToLibrary(java.nio.channels.FileChannel)"]
+ "java.nio.channels.FileChannel java.io.FileOutputStream#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"]
},
"never_outline_api": [
"java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)"
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
index 8c90b56..826b8dc 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
@@ -119,6 +119,9 @@
VirtualFile virtualFile = virtualFiles.get(i);
Timing fileTiming = Timing.create("VirtualFile " + virtualFile.getId(), options);
assert forcedStrings.size() == 0;
+ if (virtualFile.isEmpty()) {
+ continue;
+ }
DexContainerSection section =
writeVirtualFileSection(
virtualFile,
@@ -146,11 +149,15 @@
fileTiming.end();
timings.add(fileTiming);
}
- updateStringIdsSizeAndOffset(dexOutputBuffer, sections);
-
merger.add(timings);
merger.end();
+ if (sections.isEmpty()) {
+ return;
+ }
+
+ updateStringIdsSizeAndOffset(dexOutputBuffer, sections);
+
ByteBufferResult result =
new ByteBufferResult(
dexOutputBuffer.stealByteBuffer(),
@@ -205,9 +212,7 @@
int offset,
DexOutputBuffer outputBuffer,
boolean last) {
- if (virtualFile.isEmpty()) {
- return null;
- }
+ assert !virtualFile.isEmpty();
printItemUseInfo(virtualFile);
timing.begin("Reindex for lazy strings");
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
index 78f3611..e32fa57 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
@@ -1296,10 +1296,32 @@
CfLabel label1 = new CfLabel();
return new CfCode(
method.holder,
- 2,
+ 4,
2,
ImmutableList.of(
- label0, new CfConstNumber(-1, ValueType.LONG), new CfReturn(ValueType.LONG), label1),
+ label0,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInstanceFieldRead(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.createType("Lsun/misc/Unsafe;"),
+ factory.createString("U"))),
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInstanceFieldRead(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.longType,
+ factory.createString("offset"))),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Lsun/misc/Unsafe;"),
+ factory.createProto(factory.longType, factory.objectType, factory.longType),
+ factory.createString("getLong")),
+ false),
+ new CfReturn(ValueType.LONG),
+ label1),
ImmutableList.of(),
ImmutableList.of());
}
@@ -1310,6 +1332,8 @@
CfLabel label2 = new CfLabel();
CfLabel label3 = new CfLabel();
CfLabel label4 = new CfLabel();
+ CfLabel label5 = new CfLabel();
+ CfLabel label6 = new CfLabel();
return new CfCode(
method.holder,
5,
@@ -1347,7 +1371,7 @@
factory.createProto(factory.voidType, factory.objectType, factory.intType),
factory.createString("set")),
false),
- new CfGoto(label3),
+ new CfGoto(label5),
label2,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
@@ -1362,6 +1386,48 @@
new CfInstanceFieldRead(
factory.createField(
factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.classType,
+ factory.createString("type"))),
+ new CfStaticFieldRead(
+ factory.createField(
+ factory.createType("Ljava/lang/Long;"),
+ factory.classType,
+ factory.createString("TYPE"))),
+ new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4),
+ label3,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.OBJECT, 2),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.createProto(factory.longType, factory.objectType),
+ factory.createString("toLongIfPossible")),
+ false),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.createProto(factory.voidType, factory.objectType, factory.longType),
+ factory.createString("set")),
+ false),
+ new CfGoto(label5),
+ label4,
+ new CfFrame(
+ new Int2ObjectAVLTreeMap<>(
+ new int[] {0, 1, 2},
+ new FrameType[] {
+ FrameType.initializedNonNullReference(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+ FrameType.initializedNonNullReference(factory.objectType),
+ FrameType.initializedNonNullReference(factory.objectType)
+ })),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInstanceFieldRead(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
factory.createType("Lsun/misc/Unsafe;"),
factory.createString("U"))),
new CfLoad(ValueType.OBJECT, 1),
@@ -1380,7 +1446,7 @@
factory.voidType, factory.objectType, factory.longType, factory.objectType),
factory.createString("putObject")),
false),
- label3,
+ label5,
new CfFrame(
new Int2ObjectAVLTreeMap<>(
new int[] {0, 1, 2},
@@ -1391,7 +1457,7 @@
FrameType.initializedNonNullReference(factory.objectType)
})),
new CfReturnVoid(),
- label4),
+ label6),
ImmutableList.of(),
ImmutableList.of());
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 5e95467..e46ea51 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -354,7 +354,7 @@
assert !prefix.contains(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
DexType context =
dexItemFactory.createType(DescriptorUtils.getDescriptorFromClassBinaryName(prefix));
- assert isNotSyntheticType(context) || context == dexItemFactory.desugarVarHandleType;
+ assert isNotSyntheticType(context) || synthetics.isGlobalSyntheticClass(context);
});
return true;
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 323c745..4ea11dc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -401,12 +401,16 @@
return isSyntheticClass(clazz.type);
}
- public boolean isGlobalSyntheticClass(DexProgramClass clazz) {
- SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(clazz.type);
+ public boolean isGlobalSyntheticClass(DexType type) {
+ SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
if (definition != null) {
return definition.getKind().isGlobal();
}
- return isGlobalReferences(committed.getClasses().get(clazz.type));
+ return isGlobalReferences(committed.getClasses().get(type));
+ }
+
+ public boolean isGlobalSyntheticClass(DexProgramClass clazz) {
+ return isGlobalSyntheticClass(clazz.getType());
}
private static boolean isGlobalReferences(List<SyntheticProgramClassReference> references) {
diff --git a/src/test/examplesJava9/varhandle/InstanceLongField.java b/src/test/examplesJava9/varhandle/InstanceLongField.java
index dcca70e..fac0ef3 100644
--- a/src/test/examplesJava9/varhandle/InstanceLongField.java
+++ b/src/test/examplesJava9/varhandle/InstanceLongField.java
@@ -10,14 +10,69 @@
private long field;
+ private static void checkJavaLangInvokeWrongMethodTypeException(RuntimeException e) {
+ if (e.getClass().getCanonicalName().equals("java.lang.invoke.WrongMethodTypeException")
+ || e.getMessage().equals("java.lang.invoke.WrongMethodTypeException")) {
+ return;
+ }
+ throw e;
+ }
+
public static void testSet(VarHandle varHandle) {
- System.out.println("testGet");
+ System.out.println("testSet");
InstanceLongField instance = new InstanceLongField();
+ System.out.println((long) varHandle.get(instance));
+ // Long value.
+ varHandle.set(instance, (long) 1);
+ System.out.println((long) varHandle.get(instance));
+ varHandle.set(instance, Long.valueOf(2));
System.out.println(varHandle.get(instance));
- varHandle.set(instance, 1);
- System.out.println(varHandle.get(instance));
+
+ // Long compatible values.
+ varHandle.set(instance, (byte) 3);
+ System.out.println((long) varHandle.get(instance));
+ varHandle.set(instance, Byte.valueOf((byte) 4));
+ System.out.println((long) varHandle.get(instance));
+ varHandle.set(instance, '0');
+ System.out.println((long) varHandle.get(instance));
+ varHandle.set(instance, Character.valueOf('1'));
+ System.out.println((long) varHandle.get(instance));
+ varHandle.set(instance, (short) 5);
+ System.out.println((long) varHandle.get(instance));
+ varHandle.set(instance, Short.valueOf((short) 6));
+ System.out.println((long) varHandle.get(instance));
+ varHandle.set(instance, (int) 7);
+ System.out.println((long) varHandle.get(instance));
+ varHandle.set(instance, Integer.valueOf(8));
+ System.out.println((long) varHandle.get(instance));
+
+ // Long non-compatible values.
+ try {
+ varHandle.set(instance, true);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.set(instance, "3");
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.set(instance, 3.0f);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.set(instance, 3.0);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
}
public static void testCompareAndSet(VarHandle varHandle) {
@@ -25,10 +80,108 @@
InstanceLongField instance = new InstanceLongField();
- varHandle.compareAndSet(instance, 1, 2);
+ // Long value.
+ varHandle.compareAndSet(instance, 1L, 2L);
+ System.out.println((long) varHandle.get(instance));
+ varHandle.compareAndSet(instance, 0L, 1L);
+ System.out.println((long) varHandle.get(instance));
+ varHandle.compareAndSet(instance, Long.valueOf(1), 2);
+ System.out.println((long) varHandle.get(instance));
+ varHandle.compareAndSet(instance, 2, Long.valueOf(3));
+ System.out.println((long) varHandle.get(instance));
+ varHandle.compareAndSet(instance, Long.valueOf(3), Long.valueOf(4));
+ System.out.println((long) varHandle.get(instance));
+
+ // Long compatible values.
+ varHandle.compareAndSet(instance, (byte) 4, 5);
System.out.println(varHandle.get(instance));
- varHandle.compareAndSet(instance, 0, 1);
+ varHandle.compareAndSet(instance, 5, (byte) 6);
System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, (byte) 6, (byte) 7);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, Byte.valueOf((byte) 7), (byte) 8);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, (byte) 8, Byte.valueOf((byte) 9));
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, Byte.valueOf((byte) 9), Byte.valueOf((byte) 10));
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, 10, '0');
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, '0', 49);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, '1', '2');
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, (byte) 50, '3');
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, '3', (byte) 52);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, '4', '5');
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, '5', (int) 11);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, (int) 11, (int) 12);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, (int) 12, Integer.valueOf(13));
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, Integer.valueOf(13), Integer.valueOf(14));
+ System.out.println(varHandle.get(instance));
+
+ // Long non-compatible values.
+ try {
+ varHandle.compareAndSet(instance, 6, 7.0f);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6.0f, 7);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6.0f, 7.0f);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6, 7.0);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6.0, 7);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6.0, 7.0);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+
+ try {
+ varHandle.compareAndSet(instance, 6, "7");
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, "6", 7);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, "6", "7");
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java
index 0584f1c..c50db54 100644
--- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java
@@ -13,7 +13,54 @@
public class VarHandleDesugaringInstanceLongFieldTest extends VarHandleDesugaringTestBase {
private static final String EXPECTED_OUTPUT =
- StringUtils.lines("testGet", "0", "1", "testCompareAndSet", "0", "1");
+ StringUtils.lines(
+ "testSet",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "48",
+ "49",
+ "5",
+ "6",
+ "7",
+ "8",
+ "8",
+ "8",
+ "8",
+ "8",
+ "testCompareAndSet",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "10",
+ "48",
+ "49",
+ "50",
+ "51",
+ "52",
+ "53",
+ "11",
+ "12",
+ "13",
+ "14",
+ "14",
+ "14",
+ "14",
+ "14",
+ "14",
+ "14",
+ "14",
+ "14",
+ "14");
private static final String MAIN_CLASS = VarHandle.InstanceLongField.typeName();
private static final String JAR_ENTRY = "varhandle/InstanceLongField.class";
@@ -36,4 +83,9 @@
protected String getExpectedOutput() {
return EXPECTED_OUTPUT;
}
+
+ @Override
+ protected boolean getTestWithDesugaring() {
+ return true;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileLockTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileLockTest.java
new file mode 100644
index 0000000..ae36385
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileLockTest.java
@@ -0,0 +1,163 @@
+// 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.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.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+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;
+
+@RunWith(Parameterized.class)
+public class FileLockTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "true",
+ "true",
+ "pos:2;sz:7;sh:false",
+ "true",
+ "true",
+ "true",
+ "pos:2;sz:7;sh:false",
+ "false",
+ "true",
+ "true",
+ "pos:2;sz:7;sh:false",
+ "true");
+
+ 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 FileLockTest(
+ 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, "10000")
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .withArt6Plus64BitsLib()
+ .run(
+ parameters.getRuntime(),
+ TestClass.class,
+ String.valueOf(parameters.getApiLevel().getLevel()))
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ public static class TestClass {
+
+ private static int minApi;
+
+ public static void main(String[] args) throws Throwable {
+ minApi = Integer.parseInt(args[0]);
+ appendTest();
+ deleteTest();
+ fosTest();
+ }
+
+ private static void checkChannel(FileChannel channel) throws IOException {
+ FileLock lock = channel.lock(2, 7, false);
+ System.out.println(lock.channel() == channel);
+ checkAcquiredBy(lock, channel);
+ System.out.println(
+ "pos:" + lock.position() + ";sz:" + lock.size() + ";sh:" + lock.isShared());
+ lock.release();
+ channel.close();
+ }
+
+ private static void checkAcquiredBy(FileLock lock, FileChannel channel) {
+ // The method acquiredBy was introduced in 24, we do not backport or check below.
+ if (minApi >= 24) {
+ System.out.println((lock.acquiredBy() == channel));
+ } else {
+ System.out.println("true");
+ }
+ }
+
+ private static void appendTest() throws IOException {
+ Path tmpAppend = Files.createTempFile("tmp_append", ".txt");
+ Files.write(tmpAppend, "There will be dragons!".getBytes(StandardCharsets.UTF_8));
+ FileChannel appendChannel =
+ FileChannel.open(tmpAppend, StandardOpenOption.APPEND, StandardOpenOption.WRITE);
+ checkChannel(appendChannel);
+ System.out.println(Files.exists(tmpAppend));
+ Files.delete(tmpAppend);
+ }
+
+ private static void deleteTest() throws IOException {
+ Path tmpDelete = Files.createTempFile("tmp_delete", ".txt");
+ Files.write(tmpDelete, "There will be dragons!".getBytes(StandardCharsets.UTF_8));
+ FileChannel deleteChannel =
+ FileChannel.open(tmpDelete, StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.WRITE);
+ checkChannel(deleteChannel);
+ System.out.println(Files.exists(tmpDelete));
+ Files.deleteIfExists(tmpDelete);
+ }
+
+ private static void fosTest() throws IOException {
+ Path tmpFOS = Files.createTempFile("tmp_fos", ".txt");
+ Files.write(tmpFOS, "There will be dragons!".getBytes(StandardCharsets.UTF_8));
+ FileOutputStream fileOutputStream = new FileOutputStream(tmpFOS.toFile());
+ FileChannel fosChannel = fileOutputStream.getChannel();
+ checkChannel(fosChannel);
+ System.out.println(Files.exists(tmpFOS));
+ Files.delete(tmpFOS);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesAttributes2Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesAttributes2Test.java
new file mode 100644
index 0000000..8bbd564
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesAttributes2Test.java
@@ -0,0 +1,318 @@
+// 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.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.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.List;
+import java.util.Set;
+import org.junit.Assume;
+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 FilesAttributes2Test extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "PRESENT FILE",
+ "fileStore:%s",
+ "lastModifiedTime:true",
+ "lastModifiedTime:true",
+ "owner:%s",
+ "owner:%s",
+ "posix:%s",
+ "posix:%s",
+ "dir:false",
+ "dir:false",
+ "hidden:false",
+ "readable:true",
+ "writable:true",
+ "executable:false",
+ "regular:true",
+ "regular:true",
+ "same:true",
+ "same:false",
+ "symlink:%s",
+ "PRESENT DIR",
+ "fileStore:%s",
+ "lastModifiedTime:true",
+ "lastModifiedTime:true",
+ "owner:%s",
+ "owner:%s",
+ "posix:%s",
+ "posix:%s",
+ "dir:true",
+ "dir:true",
+ "hidden:false",
+ "readable:true",
+ "writable:true",
+ "executable:true",
+ "regular:false",
+ "regular:false",
+ "same:true",
+ "same:false",
+ "symlink:%s",
+ "ABSENT FILE",
+ "fileStore:%s",
+ "lastModifiedTime:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "lastModifiedTime:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "owner:%s",
+ "owner:%s",
+ "posix:%s",
+ "posix:%s",
+ "dir:false",
+ "dir:false",
+ "hidden:false",
+ "readable:false",
+ "writable:false",
+ "executable:false",
+ "regular:false",
+ "regular:false",
+ "same:true",
+ "same:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "symlink:false");
+
+ 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 FilesAttributes2Test(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ this.compilationSpecification = compilationSpecification;
+ }
+
+ private String getExpectedResult() {
+ // On Android 24, everything seems to be considered a symlink due to canonicalFile being
+ // invalid. It seems it does not reproduce on real device, so this may be an issue from our
+ // test set-up.
+ boolean invalidSymlink =
+ parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isEqualTo(Version.V7_0_0);
+ // On Dex, the security manager is not working in our test set-up.
+ boolean invalidFileStore = parameters.isDexRuntime();
+ // Posix attributes are not available on desugared nio.
+ boolean invalidPosix =
+ parameters.isDexRuntime()
+ && !libraryDesugaringSpecification.usesPlatformFileSystem(parameters);
+
+ String invalidFileStoreString = "class java.lang.SecurityException :: getFileStore";
+ String invalidPosixString = "class java.lang.UnsupportedOperationException :: no-message";
+ String missingFile = "class java.nio.file.NoSuchFileException :: notExisting.txt";
+ String success = "true";
+
+ Object[] vars = {
+ invalidFileStore ? invalidFileStoreString : success,
+ invalidPosix ? invalidPosixString : success,
+ invalidPosix ? invalidPosixString : success,
+ invalidPosix ? invalidPosixString : success,
+ invalidPosix ? invalidPosixString : success,
+ String.valueOf(invalidSymlink),
+ invalidFileStore ? invalidFileStoreString : success,
+ invalidPosix ? invalidPosixString : success,
+ invalidPosix ? invalidPosixString : success,
+ invalidPosix ? invalidPosixString : success,
+ invalidPosix ? invalidPosixString : success,
+ String.valueOf(invalidSymlink),
+ invalidFileStore ? invalidFileStoreString : missingFile,
+ invalidPosix ? invalidPosixString : missingFile,
+ invalidPosix ? invalidPosixString : missingFile,
+ invalidPosix ? invalidPosixString : missingFile,
+ invalidPosix ? invalidPosixString : missingFile,
+ };
+ return String.format(EXPECTED_RESULT, vars);
+ }
+
+ @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(getExpectedResult());
+ return;
+ }
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .withArt6Plus64BitsLib()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(getExpectedResult());
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) throws Throwable {
+ System.out.println("PRESENT FILE");
+ Path path = Files.createTempFile("example", ".txt");
+ testProperties(path);
+ System.out.println("PRESENT DIR");
+ Path dir = Files.createTempDirectory("dir");
+ testProperties(dir);
+ System.out.println("ABSENT FILE");
+ Path notExisting = Paths.get("notExisting.txt");
+ testProperties(notExisting);
+ }
+
+ private static void printError(Throwable t) {
+ String[] split =
+ t.getMessage() == null ? new String[] {"no-message"} : t.getMessage().split("/");
+ System.out.println(t.getClass() + " :: " + split[split.length - 1]);
+ }
+
+ private static void testProperties(Path path) {
+ try {
+ System.out.print("fileStore:");
+ System.out.println(Files.getFileStore(path) != null);
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("lastModifiedTime:");
+ System.out.println(Files.getLastModifiedTime(path).toMillis() > 0);
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("lastModifiedTime:");
+ System.out.println(
+ Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS).toMillis() > 0);
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("owner:");
+ System.out.println(Files.getOwner(path) != null);
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("owner:");
+ System.out.println(Files.getOwner(path, LinkOption.NOFOLLOW_LINKS) != null);
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("posix:");
+ Set<PosixFilePermission> perms = Files.getPosixFilePermissions(path);
+ System.out.println(perms == null ? "null" : perms.contains(PosixFilePermission.OWNER_READ));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("posix:");
+ Set<PosixFilePermission> perms =
+ Files.getPosixFilePermissions(path, LinkOption.NOFOLLOW_LINKS);
+ System.out.println(perms == null ? "null" : perms.contains(PosixFilePermission.OWNER_READ));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("dir:");
+ System.out.println(Files.isDirectory(path));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("dir:");
+ System.out.println(Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("hidden:");
+ System.out.println(Files.isHidden(path));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("readable:");
+ System.out.println(Files.isReadable(path));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("writable:");
+ System.out.println(Files.isWritable(path));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("executable:");
+ System.out.println(Files.isExecutable(path));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("regular:");
+ System.out.println(Files.isRegularFile(path));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("regular:");
+ System.out.println(Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("same:");
+ System.out.println(Files.isSameFile(path, path));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("same:");
+ System.out.println(Files.isSameFile(path, Paths.get("/")));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("symlink:");
+ System.out.println(Files.isSymbolicLink(path));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesAttributesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesAttributesTest.java
new file mode 100644
index 0000000..2ccaa08
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesAttributesTest.java
@@ -0,0 +1,302 @@
+// 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.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
+
+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.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.DosFileAttributeView;
+import java.nio.file.attribute.DosFileAttributes;
+import java.nio.file.attribute.FileOwnerAttributeView;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.UserDefinedFileAttributeView;
+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;
+
+@RunWith(Parameterized.class)
+public class FilesAttributesTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT_JVM_LINUX =
+ StringUtils.lines(
+ "PRESENT FILE",
+ "basic:true",
+ "posix:true",
+ "dos:false",
+ "acl:null",
+ "fileOwner:true",
+ "userDefined:user",
+ "basic:true",
+ "posix:true",
+ "dos:false",
+ "ABSENT FILE",
+ "basic:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "posix:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "dos:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "acl:null",
+ "fileOwner:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "userDefined:user",
+ "basic:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "posix:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "dos:class java.nio.file.NoSuchFileException :: notExisting.txt");
+ private static final String EXPECTED_RESULT_ANDROID =
+ StringUtils.lines(
+ "PRESENT FILE",
+ "basic:true",
+ "posix:true",
+ "dos:null",
+ "acl:null",
+ "fileOwner:true",
+ "userDefined:null",
+ "basic:true",
+ "posix:true",
+ "dos:class java.lang.UnsupportedOperationException :: no-message",
+ "ABSENT FILE",
+ "basic:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "posix:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "dos:null",
+ "acl:null",
+ "fileOwner:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "userDefined:null",
+ "basic:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "posix:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "dos:class java.lang.UnsupportedOperationException :: no-message");
+ private static final String EXPECTED_RESULT_ANDROID_DESUGARING =
+ StringUtils.lines(
+ "PRESENT FILE",
+ "basic:true",
+ "posix:null",
+ "dos:null",
+ "acl:null",
+ "fileOwner:null",
+ "userDefined:null",
+ "basic:true",
+ "posix:class java.lang.UnsupportedOperationException :: no-message",
+ "dos:class java.lang.UnsupportedOperationException :: no-message",
+ "ABSENT FILE",
+ "basic:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "posix:null",
+ "dos:null",
+ "acl:null",
+ "fileOwner:null",
+ "userDefined:null",
+ "basic:class java.nio.file.NoSuchFileException :: notExisting.txt",
+ "posix:class java.lang.UnsupportedOperationException :: no-message",
+ "dos:class java.lang.UnsupportedOperationException :: no-message");
+
+ 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 FilesAttributesTest(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ this.compilationSpecification = compilationSpecification;
+ }
+
+ private String getExpectedResult() {
+ if (parameters.isCfRuntime()) {
+ return EXPECTED_RESULT_JVM_LINUX;
+ }
+ if (libraryDesugaringSpecification.usesPlatformFileSystem(parameters)) {
+ return EXPECTED_RESULT_ANDROID;
+ }
+ return EXPECTED_RESULT_ANDROID_DESUGARING;
+ }
+
+ @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(getExpectedResult());
+ return;
+ }
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .withArt6Plus64BitsLib()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(getExpectedResult());
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) throws Throwable {
+ System.out.println("PRESENT FILE");
+ Path path = Files.createTempFile("example", ".txt");
+ attributeViewAccess(path);
+ attributeAccess(path);
+ System.out.println("ABSENT FILE");
+ Path notExisting = Paths.get("notExisting.txt");
+ Files.deleteIfExists(notExisting);
+ attributeViewAccess(notExisting);
+ attributeAccess(notExisting);
+ }
+
+ private static void printError(Throwable t) {
+ String[] split =
+ t.getMessage() == null ? new String[] {"no-message"} : t.getMessage().split("/");
+ System.out.println(t.getClass() + " :: " + split[split.length - 1]);
+ }
+
+ private static void attributeViewAccess(Path path) {
+ try {
+ System.out.print("basic:");
+ BasicFileAttributeView basicView =
+ Files.getFileAttributeView(path, BasicFileAttributeView.class);
+ if (basicView != null) {
+ System.out.println(basicView.readAttributes().isRegularFile());
+ } else {
+ System.out.println("null");
+ }
+ } catch (Throwable t) {
+ printError(t);
+ }
+
+ try {
+ System.out.print("posix:");
+ PosixFileAttributeView posixView =
+ Files.getFileAttributeView(path, PosixFileAttributeView.class);
+ if (posixView != null) {
+ System.out.println(posixView.readAttributes().permissions().contains(OWNER_READ));
+ } else {
+ System.out.println("null");
+ }
+ } catch (Throwable t) {
+ printError(t);
+ }
+
+ try {
+ System.out.print("dos:");
+ DosFileAttributeView dosView = Files.getFileAttributeView(path, DosFileAttributeView.class);
+ if (dosView != null) {
+ System.out.println(dosView.readAttributes().isReadOnly());
+ } else {
+ System.out.println("null");
+ }
+ } catch (Throwable t) {
+ printError(t);
+ }
+
+ try {
+ System.out.print("acl:");
+ AclFileAttributeView aclView = Files.getFileAttributeView(path, AclFileAttributeView.class);
+ if (aclView != null) {
+ System.out.println(aclView.getAcl().isEmpty());
+ } else {
+ System.out.println("null");
+ }
+ } catch (Throwable t) {
+ printError(t);
+ }
+ try {
+ System.out.print("fileOwner:");
+ FileOwnerAttributeView foView =
+ Files.getFileAttributeView(path, FileOwnerAttributeView.class);
+ if (foView != null) {
+ System.out.println(foView.getOwner() != null);
+ } else {
+ System.out.println("null");
+ }
+ } catch (Throwable t) {
+ printError(t);
+ }
+
+ try {
+ System.out.print("userDefined:");
+ UserDefinedFileAttributeView udView =
+ Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+ if (udView != null) {
+ System.out.println(udView.name());
+ } else {
+ System.out.println("null");
+ }
+ } catch (Throwable t) {
+ printError(t);
+ }
+ }
+
+ private static void attributeAccess(Path path) {
+ try {
+ System.out.print("basic:");
+ BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
+ if (attributes != null) {
+ System.out.println(attributes.isRegularFile());
+ } else {
+ System.out.println("null");
+ }
+ } catch (Throwable t) {
+ printError(t);
+ }
+
+ try {
+ System.out.print("posix:");
+ PosixFileAttributes posixAttributes = Files.readAttributes(path, PosixFileAttributes.class);
+ if (posixAttributes != null) {
+ System.out.println(posixAttributes.permissions().contains(OWNER_READ));
+ } else {
+ System.out.println("null");
+ }
+ } catch (Throwable t) {
+ printError(t);
+ }
+
+ try {
+ System.out.print("dos:");
+ DosFileAttributes dosFileAttributes = Files.readAttributes(path, DosFileAttributes.class);
+ if (dosFileAttributes != null) {
+ System.out.println(dosFileAttributes.isReadOnly());
+ } else {
+ System.out.println("null");
+ }
+ } catch (Throwable t) {
+ printError(t);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesCreateTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesCreateTest.java
index af5df8a..5f7fd4b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesCreateTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesCreateTest.java
@@ -41,14 +41,7 @@
"ind3f class java.nio.file.NoSuchFileException :: dir",
"ind4f class java.nio.file.NoSuchFileException :: dir",
"ind5f class java.nio.file.NoSuchFileException :: f.txt",
- "ind6f class java.nio.file.NoSuchFileException :: f.txt",
- "true",
- "true",
- "notExisting1 class java.nio.file.NoSuchFileException :: notExisting1.txt",
- "false",
- "false",
- "readAttributes class java.nio.file.NoSuchFileException :: f1.txt",
- "readAttributes class java.nio.file.NoSuchFileException :: f1.txt");
+ "ind6f class java.nio.file.NoSuchFileException :: f.txt");
private static final String EXPECTED_RESULT_DESUGARING =
StringUtils.lines(
"ind3f class java.nio.file.NoSuchFileException :: dir",
@@ -56,17 +49,24 @@
"ind5f class java.io.FileNotFoundException :: f.txt: open failed: ENOENT (No such file or"
+ " directory)",
"ind6f class java.io.FileNotFoundException :: f.txt: open failed: ENOENT (No such file or"
- + " directory)",
- "true",
- "true",
- "notExisting1 class java.io.IOException :: notExisting1.txt before deletion.",
- "false",
- "true",
- "1970-01-01T00:00:00Z",
- "1970-01-01T00:00:00Z");
- private static final String EXPECTED_FILES =
+ + " directory)");
+ private static final String COMMON_EXPECTATIONS =
StringUtils.lines(
- "ind2s/dir", "ind2s", "ind1s/dir", "ind1s", "f4.txt", "f3.txt", "dir2s", "dir1s");
+ "true",
+ "true",
+ "notExisting1 class java.nio.file.NoSuchFileException :: notExisting1.txt",
+ "false",
+ "false",
+ "readAttributes class java.nio.file.NoSuchFileException :: f1.txt",
+ "readAttributes class java.nio.file.NoSuchFileException :: f1.txt",
+ "ind2s/dir",
+ "ind2s",
+ "ind1s/dir",
+ "ind1s",
+ "f4.txt",
+ "f3.txt",
+ "dir2s",
+ "dir1s");
private final TestParameters parameters;
private final LibraryDesugaringSpecification libraryDesugaringSpecification;
@@ -122,12 +122,12 @@
private String getExpectedResult() {
if (parameters.isCfRuntime()) {
- return EXPECTED_RESULT + EXPECTED_FILES;
+ return EXPECTED_RESULT + COMMON_EXPECTATIONS;
}
return (libraryDesugaringSpecification.usesPlatformFileSystem(parameters)
? EXPECTED_RESULT
: EXPECTED_RESULT_DESUGARING)
- + EXPECTED_FILES;
+ + COMMON_EXPECTATIONS;
}
public static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesInOutTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesInOutTest.java
new file mode 100644
index 0000000..fe483f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesInOutTest.java
@@ -0,0 +1,295 @@
+// 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.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.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.function.Supplier;
+import org.junit.Assume;
+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 FilesInOutTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "PRESENT FILE",
+ "buffRead:cHello!",
+ "inStream[READ]:cHello",
+ "outStream[READ]:class java.lang.IllegalArgumentException :: READ not allowed",
+ "buffWrite[READ]:class java.lang.IllegalArgumentException :: READ not allowed",
+ "newByte[READ]:c6",
+ "inStream[WRITE]:class java.lang.UnsupportedOperationException :: 'WRITE' not allowed",
+ "outStream[WRITE]:cwGame over!",
+ "buffWrite[WRITE]:cwHello!",
+ "newByte[WRITE]:c6",
+ "inStream[APPEND]:class java.lang.UnsupportedOperationException :: 'APPEND' not allowed",
+ "outStream[APPEND]:cwHello!Game over!",
+ "buffWrite[APPEND]:cwHello!",
+ "newByte[APPEND]:c6",
+ "inStream[TRUNCATE_EXISTING]:cHello",
+ "outStream[TRUNCATE_EXISTING]:cwGame over!",
+ "buffWrite[TRUNCATE_EXISTING]:cwclass java.lang.IndexOutOfBoundsException :: index 0,"
+ + " size 0",
+ "newByte[TRUNCATE_EXISTING]:c6",
+ "inStream[CREATE]:cHello",
+ "outStream[CREATE]:cwGame over!",
+ "buffWrite[CREATE]:cwHello!",
+ "newByte[CREATE]:c6",
+ "inStream[CREATE_NEW]:cHello",
+ "outStream[CREATE_NEW]:class java.nio.file.FileAlreadyExistsException :: example",
+ "buffWrite[CREATE_NEW]:class java.nio.file.FileAlreadyExistsException :: example",
+ "newByte[CREATE_NEW]:c6",
+ "inStream[DELETE_ON_CLOSE]:cHello",
+ "outStream[DELETE_ON_CLOSE]:%s",
+ "buffWrite[DELETE_ON_CLOSE]:%s",
+ "newByte[DELETE_ON_CLOSE]:c6",
+ "inStream[SPARSE]:cHello",
+ "outStream[SPARSE]:cwGame over!",
+ "buffWrite[SPARSE]:cwHello!",
+ "newByte[SPARSE]:c6",
+ "inStream[SYNC]:cHello",
+ "outStream[SYNC]:cwGame over!",
+ "buffWrite[SYNC]:cwHello!",
+ "newByte[SYNC]:c6",
+ "inStream[DSYNC]:cHello",
+ "outStream[DSYNC]:cwGame over!",
+ "buffWrite[DSYNC]:cwHello!",
+ "newByte[DSYNC]:c6",
+ "ABSENT FILE",
+ "buffRead:class java.nio.file.NoSuchFileException :: notExisting",
+ "inStream[READ]:class java.nio.file.NoSuchFileException :: notExisting",
+ "outStream[READ]:class java.lang.IllegalArgumentException :: READ not allowed",
+ "buffWrite[READ]:class java.lang.IllegalArgumentException :: READ not allowed",
+ "newByte[READ]:class java.nio.file.NoSuchFileException :: notExisting",
+ "inStream[WRITE]:class java.lang.UnsupportedOperationException :: 'WRITE' not allowed",
+ "outStream[WRITE]:class java.nio.file.NoSuchFileException :: notExisting",
+ "buffWrite[WRITE]:class java.nio.file.NoSuchFileException :: notExisting",
+ "newByte[WRITE]:class java.nio.file.NoSuchFileException :: notExisting",
+ "inStream[APPEND]:class java.lang.UnsupportedOperationException :: 'APPEND' not allowed",
+ "outStream[APPEND]:class java.nio.file.NoSuchFileException :: notExisting",
+ "buffWrite[APPEND]:class java.nio.file.NoSuchFileException :: notExisting",
+ "newByte[APPEND]:class java.nio.file.NoSuchFileException :: notExisting",
+ "inStream[TRUNCATE_EXISTING]:class java.nio.file.NoSuchFileException :: notExisting",
+ "outStream[TRUNCATE_EXISTING]:class java.nio.file.NoSuchFileException :: notExisting",
+ "buffWrite[TRUNCATE_EXISTING]:class java.nio.file.NoSuchFileException :: notExisting",
+ "newByte[TRUNCATE_EXISTING]:class java.nio.file.NoSuchFileException :: notExisting",
+ "inStream[CREATE]:%s",
+ "outStream[CREATE]:cwGame over!",
+ "buffWrite[CREATE]:cwclass java.lang.IndexOutOfBoundsException :: index 0, size 0",
+ "newByte[CREATE]:%s",
+ "inStream[CREATE_NEW]:%s",
+ "outStream[CREATE_NEW]:cwGame over!",
+ "buffWrite[CREATE_NEW]:cwclass java.lang.IndexOutOfBoundsException :: index 0, size 0",
+ "newByte[CREATE_NEW]:%s",
+ "inStream[DELETE_ON_CLOSE]:class java.nio.file.NoSuchFileException :: notExisting",
+ "outStream[DELETE_ON_CLOSE]:class java.nio.file.NoSuchFileException :: notExisting",
+ "buffWrite[DELETE_ON_CLOSE]:class java.nio.file.NoSuchFileException :: notExisting",
+ "newByte[DELETE_ON_CLOSE]:class java.nio.file.NoSuchFileException :: notExisting",
+ "inStream[SPARSE]:class java.nio.file.NoSuchFileException :: notExisting",
+ "outStream[SPARSE]:class java.nio.file.NoSuchFileException :: notExisting",
+ "buffWrite[SPARSE]:class java.nio.file.NoSuchFileException :: notExisting",
+ "newByte[SPARSE]:class java.nio.file.NoSuchFileException :: notExisting",
+ "inStream[SYNC]:class java.nio.file.NoSuchFileException :: notExisting",
+ "outStream[SYNC]:class java.nio.file.NoSuchFileException :: notExisting",
+ "buffWrite[SYNC]:class java.nio.file.NoSuchFileException :: notExisting",
+ "newByte[SYNC]:class java.nio.file.NoSuchFileException :: notExisting",
+ "inStream[DSYNC]:class java.nio.file.NoSuchFileException :: notExisting",
+ "outStream[DSYNC]:class java.nio.file.NoSuchFileException :: notExisting",
+ "buffWrite[DSYNC]:class java.nio.file.NoSuchFileException :: notExisting",
+ "newByte[DSYNC]:class java.nio.file.NoSuchFileException :: notExisting");
+
+ private static final String[] EXPECTED_RESULT_NO_DESUGARING =
+ new String[] {
+ "cwclass java.nio.file.NoSuchFileException :: example",
+ "cwclass java.nio.file.NoSuchFileException :: example",
+ "class java.nio.file.NoSuchFileException :: notExisting",
+ "class java.nio.file.NoSuchFileException :: notExisting",
+ "class java.nio.file.NoSuchFileException :: notExisting",
+ "class java.nio.file.NoSuchFileException :: notExisting"
+ };
+
+ private static final String[] EXPECTED_RESULT_DESUGARING =
+ new String[] {
+ "cwGame over!",
+ "cwHello!",
+ // In some cases the desugaring version raises FileNotFoundException instead of
+ // NoSuchFileException on the same file.
+ "class java.io.FileNotFoundException :: notExisting",
+ "class java.io.FileNotFoundException :: notExisting",
+ "class java.io.FileNotFoundException :: notExisting",
+ "class java.io.FileNotFoundException :: notExisting"
+ };
+
+ 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 FilesInOutTest(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ this.compilationSpecification = compilationSpecification;
+ }
+
+ private String getExpectedResult() {
+ if (parameters.isCfRuntime()
+ || libraryDesugaringSpecification.usesPlatformFileSystem(parameters)) {
+ return String.format(EXPECTED_RESULT, EXPECTED_RESULT_NO_DESUGARING);
+ }
+ return String.format(EXPECTED_RESULT, EXPECTED_RESULT_DESUGARING);
+ }
+
+ @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(getExpectedResult());
+ return;
+ }
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .withArt6Plus64BitsLib()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(getExpectedResult());
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) throws Throwable {
+ System.out.println("PRESENT FILE");
+ Supplier<Path> presentFileSupplier =
+ () -> {
+ try {
+ Path path = Files.createTempFile("example_", ".txt");
+ Files.write(path, "Hello!".getBytes(StandardCharsets.UTF_8));
+ return path;
+ } catch (IOException t) {
+ // For the test it should never happen so this is enough.
+ throw new RuntimeException(t);
+ }
+ };
+ testProperties(presentFileSupplier);
+ System.out.println("ABSENT FILE");
+ testProperties(() -> Paths.get("notExisting_XXX.txt"));
+ }
+
+ private static void printError(Throwable t) {
+ String[] split =
+ t.getMessage() == null ? new String[] {"no-message_XXX"} : t.getMessage().split("/");
+ split = split[split.length - 1].split("_");
+ if (t instanceof IndexOutOfBoundsException) {
+ // IndexOutOfBoundsException are printed slightly differently across platform and it's not
+ // really relevant for the test.
+ split = new String[] {"index 0, size 0"};
+ }
+ System.out.println(t.getClass() + " :: " + split[0]);
+ }
+
+ private static void testProperties(Supplier<Path> pathSupplier) throws IOException {
+ Path path = pathSupplier.get();
+ System.out.print("buffRead:");
+ try (BufferedReader bufferedReader = Files.newBufferedReader(path)) {
+ System.out.print("c");
+ System.out.println(bufferedReader.readLine());
+ } catch (Throwable t) {
+ printError(t);
+ }
+ Files.deleteIfExists(path);
+ for (StandardOpenOption value : StandardOpenOption.values()) {
+ path = pathSupplier.get();
+ System.out.print("inStream[" + value + "]:");
+ try (InputStream inputStream = Files.newInputStream(path, value)) {
+ System.out.print("c");
+ byte[] read = new byte[5];
+ inputStream.read(read);
+ System.out.println(new String(read));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ Files.deleteIfExists(path);
+ path = pathSupplier.get();
+ System.out.print("outStream[" + value + "]:");
+ try (OutputStream outputStream = Files.newOutputStream(path, value)) {
+ System.out.print("c");
+ outputStream.write("Game over!".getBytes(StandardCharsets.UTF_8));
+ System.out.print("w");
+ System.out.println(Files.readAllLines(path).get(0));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ Files.deleteIfExists(path);
+ path = pathSupplier.get();
+ System.out.print("buffWrite[" + value + "]:");
+ try (BufferedWriter bufferedWriter = Files.newBufferedWriter(path, value)) {
+ System.out.print("c");
+ bufferedWriter.write("Game over!");
+ System.out.print("w");
+ System.out.println(Files.readAllLines(path).get(0));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ Files.deleteIfExists(path);
+ path = pathSupplier.get();
+ System.out.print("newByte[" + value + "]:");
+ try (SeekableByteChannel seekableByteChannel = Files.newByteChannel(path, value)) {
+ System.out.print("c");
+ System.out.println(seekableByteChannel.size());
+ } catch (Throwable t) {
+ printError(t);
+ }
+ Files.deleteIfExists(path);
+ }
+ }
+ }
+}
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
index e6eeff2..59564e6 100644
--- 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
@@ -108,8 +108,7 @@
// Skip Android 4.4.4 due to missing libjavacrypto.
getTestParameters()
.withCfRuntime(CfVm.JDK11)
- .withDexRuntime(Version.V4_0_4)
- .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+ .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
.withAllApiLevels()
.build(),
ImmutableList.of(JDK11_PATH),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesSymLinkTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesSymLinkTest.java
new file mode 100644
index 0000000..9a4521c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesSymLinkTest.java
@@ -0,0 +1,127 @@
+// 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.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.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+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;
+
+@RunWith(Parameterized.class)
+public class FilesSymLinkTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines("link created.", "true", "Symlink:true", "true");
+ private static final String EXPECTED_RESULT_DESUGARING =
+ StringUtils.lines(
+ "class java.lang.UnsupportedOperationException :: null",
+ "true",
+ "class java.lang.UnsupportedOperationException :: null",
+ "true");
+
+ 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 FilesSymLinkTest(
+ 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, "10000")
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .withArt6Plus64BitsLib()
+ .run(
+ parameters.getRuntime(),
+ TestClass.class,
+ String.valueOf(parameters.getApiLevel().getLevel()))
+ .assertSuccessWithOutput(
+ libraryDesugaringSpecification.usesPlatformFileSystem(parameters)
+ ? EXPECTED_RESULT
+ : EXPECTED_RESULT_DESUGARING);
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) throws Throwable {
+ Path target = Files.createTempFile("guinea_pig_", ".txt");
+
+ Path link = Paths.get("link");
+ try {
+ Files.createLink(link, target);
+ System.out.println("link created.");
+ } catch (Throwable t) {
+ printError(t);
+ }
+ Files.deleteIfExists(link);
+ System.out.println((Files.exists(target)));
+
+ Path symLink = Paths.get("symLink");
+ try {
+ Files.createSymbolicLink(symLink, target);
+ Path path = Files.readSymbolicLink(symLink);
+ System.out.println("Symlink:" + path.equals(target));
+ } catch (Throwable t) {
+ printError(t);
+ }
+ Files.deleteIfExists(symLink);
+ System.out.println((Files.exists(target)));
+ Files.delete(target);
+ }
+
+ private static void printError(Throwable t) {
+ System.out.println(t.getClass() + " :: " + t.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
index 27f42f0..58af649 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
@@ -27,17 +27,9 @@
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.AclFileAttributeView;
-import java.nio.file.attribute.BasicFileAttributeView;
-import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributeView;
-import java.nio.file.attribute.DosFileAttributes;
-import java.nio.file.attribute.FileOwnerAttributeView;
import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
@@ -63,15 +55,6 @@
"String read: Hello World",
"bytes read: 11",
"String read: Hello World",
- "true",
- "%s",
- "null",
- "null",
- "%s",
- "null",
- "true",
- "%s",
- "unsupported",
"tmp",
"/",
"true",
@@ -85,17 +68,11 @@
"%s");
private static final List<String> EXPECTED_RESULT_POSIX =
ImmutableList.of(
- "true",
- "true",
- "true",
"Succeeded with POSIX RO:false",
"Successfully set RO with POSIX",
"Succeeded with POSIX RO:true");
private static final List<String> EXPECTED_RESULT_DESUGARING_NON_POSIX =
ImmutableList.of(
- "null",
- "null",
- "unsupported",
"Fail to understand if the file is read-only: class"
+ " java.lang.UnsupportedOperationException",
"Fail to set file as read-only: class java.lang.UnsupportedOperationException",
@@ -161,8 +138,6 @@
Path path = Files.createTempFile("example", ".txt");
readWriteThroughFilesAPI(path);
readThroughFileChannelAPI(path);
- attributeViewAccess(path);
- attributeAccess(path);
Files.setAttribute(path, "basic:lastModifiedTime", FileTime.from(Instant.EPOCH));
pathGeneric();
lines(path);
@@ -256,101 +231,6 @@
System.out.println(mapping.values().iterator().next().getClass().getPackage().getName());
}
- private static void attributeViewAccess(Path path) throws IOException {
- BasicFileAttributeView basicView =
- Files.getFileAttributeView(path, BasicFileAttributeView.class);
- if (basicView != null) {
- System.out.println(basicView.readAttributes().isRegularFile());
- } else {
- System.out.println("null");
- }
-
- PosixFileAttributeView posixView =
- Files.getFileAttributeView(path, PosixFileAttributeView.class);
- if (posixView != null) {
- System.out.println(posixView.readAttributes().permissions().contains(OWNER_READ));
- } else {
- System.out.println("null");
- }
-
- try {
- DosFileAttributeView dosView = Files.getFileAttributeView(path, DosFileAttributeView.class);
- if (dosView != null) {
- System.out.println(dosView.readAttributes().isReadOnly());
- } else {
- System.out.println("null");
- }
- } catch (UnsupportedOperationException e) {
- System.out.println("unsupported");
- }
-
- try {
- AclFileAttributeView aclView = Files.getFileAttributeView(path, AclFileAttributeView.class);
- if (aclView != null) {
- System.out.println(aclView.getAcl().isEmpty());
- } else {
- System.out.println("null");
- }
- } catch (UnsupportedOperationException e) {
- System.out.println("unsupported");
- }
-
- try {
- FileOwnerAttributeView foView =
- Files.getFileAttributeView(path, FileOwnerAttributeView.class);
- if (foView != null) {
- System.out.println(foView.getOwner() != null);
- } else {
- System.out.println("null");
- }
- } catch (UnsupportedOperationException e) {
- System.out.println("unsupported");
- }
-
- try {
- UserDefinedFileAttributeView udView =
- Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
- if (udView != null) {
- System.out.println(udView.name());
- } else {
- System.out.println("null");
- }
- } catch (UnsupportedOperationException e) {
- System.out.println("unsupported");
- }
- }
-
- private static void attributeAccess(Path path) throws IOException {
- BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
- if (attributes != null) {
- System.out.println(attributes.isRegularFile());
- } else {
- System.out.println("null");
- }
-
- try {
- PosixFileAttributes posixAttributes = Files.readAttributes(path, PosixFileAttributes.class);
- if (posixAttributes != null) {
- System.out.println(posixAttributes.permissions().contains(OWNER_READ));
- } else {
- System.out.println("null");
- }
- } catch (UnsupportedOperationException e) {
- System.out.println("unsupported");
- }
-
- try {
- DosFileAttributes dosFileAttributes = Files.readAttributes(path, DosFileAttributes.class);
- if (dosFileAttributes != null) {
- System.out.println(dosFileAttributes.isReadOnly());
- } else {
- System.out.println("null");
- }
- } catch (UnsupportedOperationException e) {
- System.out.println("unsupported");
- }
- }
-
private static void lines(Path path) throws IOException {
Files.write(path, "This\nis\nfun!".getBytes(StandardCharsets.UTF_8));
Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8);
diff --git a/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatEmptyTest.java b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatEmptyTest.java
new file mode 100644
index 0000000..b2e6aec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatEmptyTest.java
@@ -0,0 +1,68 @@
+// 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.dex.container;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DexContainerFormatEmptyTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @Test
+ public void testNonContainerD8() throws Exception {
+ Path outputA = testForD8(Backend.DEX).setMinApi(AndroidApiLevel.L).compile().writeToZip();
+ assertEquals(0, unzipContent(outputA).size());
+
+ Path outputB = testForD8(Backend.DEX).setMinApi(AndroidApiLevel.L).compile().writeToZip();
+ assertEquals(0, unzipContent(outputB).size());
+
+ Path outputMerged =
+ testForD8(Backend.DEX)
+ .addProgramFiles(outputA, outputB)
+ .setMinApi(AndroidApiLevel.L)
+ .compile()
+ .writeToZip();
+ assertEquals(0, unzipContent(outputMerged).size());
+ }
+
+ @Test
+ public void testD8Experiment() throws Exception {
+ Path outputFromDexing =
+ testForD8(Backend.DEX)
+ .setMinApi(AndroidApiLevel.L)
+ .addOptionsModification(
+ options -> options.getTestingOptions().dexContainerExperiment = true)
+ .compile()
+ .writeToZip();
+ assertEquals(0, unzipContent(outputFromDexing).size());
+ }
+
+ private List<byte[]> unzipContent(Path zip) throws IOException {
+ List<byte[]> result = new ArrayList<>();
+ ZipUtils.iter(zip, (entry, inputStream) -> result.add(ByteStreams.toByteArray(inputStream)));
+ return result;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
index 57c13dd..af2bb5b 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
@@ -135,14 +135,15 @@
}
long getLong(Object ct1) {
- // TODO(b/247076137): Implement.
- return -1L;
+ return U.getLong(ct1, offset);
}
// set variants.
void set(Object ct1, Object newValue) {
if (type == int.class) {
setInt(ct1, toIntIfPossible(newValue));
+ } else if (type == long.class) {
+ setLong(ct1, toLongIfPossible(newValue));
} else {
U.putObject(ct1, offset, newValue);
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
index 01ec5ac..8743b48 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
@@ -193,7 +193,7 @@
.assertSuccessWithOutput(getExpected());
}
- private List<String> getKeepRulesForClass(Class<?> clazz) throws IOException {
+ public static List<String> getKeepRulesForClass(Class<?> clazz) throws IOException {
Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(ToolHelper.getClassAsBytes(clazz));
List<String> rules = new ArrayList<>();
KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add);
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
new file mode 100644
index 0000000..9bee408
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
@@ -0,0 +1,117 @@
+// 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.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepUsesReflectionAnnotationTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public KeepUsesReflectionAnnotationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithRuleExtraction() throws Exception {
+ List<String> rules = getExtractedKeepRules();
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getInputClassesWithoutAnnotations())
+ .addKeepRules(rules)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, A.class, B.class);
+ }
+
+ public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
+ List<Class<?>> classes = getInputClasses();
+ List<byte[]> transformed = new ArrayList<>(classes.size());
+ for (Class<?> clazz : classes) {
+ transformed.add(transformer(clazz).removeAllAnnotations().transform());
+ }
+ return transformed;
+ }
+
+ public List<String> getExtractedKeepRules() throws Exception {
+ List<Class<?>> classes = getInputClasses();
+ List<String> rules = new ArrayList<>();
+ for (Class<?> clazz : classes) {
+ rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
+ }
+ return rules;
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isPresent());
+ assertThat(inspector.clazz(B.class).uniqueMethodWithOriginalName("bar"), isPresent());
+ }
+
+ static class A {
+
+ @UsesReflection({
+ // Ensure that the class A remains as we are assuming the contents of its name.
+ @KeepTarget(classConstant = A.class),
+ // Ensure that the class B remains as we are looking it up by reflected name.
+ @KeepTarget(classConstant = B.class),
+ // Ensure the method 'bar' remains as we are invoking it by reflected name.
+ @KeepTarget(classConstant = B.class, methodName = "bar")
+ })
+ public void foo() throws Exception {
+ Class<?> clazz = Class.forName(A.class.getTypeName().replace("$A", "$B"));
+ clazz.getDeclaredMethod("bar").invoke(clazz);
+ }
+ }
+
+ static class B {
+ public static void bar() {
+ System.out.println("Hello, world");
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ new A().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationWithAdditionalPreconditionTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationWithAdditionalPreconditionTest.java
new file mode 100644
index 0000000..732f2d2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationWithAdditionalPreconditionTest.java
@@ -0,0 +1,128 @@
+// 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.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepCondition;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepUsesReflectionAnnotationWithAdditionalPreconditionTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public KeepUsesReflectionAnnotationWithAdditionalPreconditionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithRuleExtraction() throws Exception {
+ List<String> rules = getExtractedKeepRules();
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getInputClassesWithoutAnnotations())
+ .addKeepRules(rules)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, A.class, B.class);
+ }
+
+ public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
+ List<Class<?>> classes = getInputClasses();
+ List<byte[]> transformed = new ArrayList<>(classes.size());
+ for (Class<?> clazz : classes) {
+ transformed.add(transformer(clazz).removeAllAnnotations().transform());
+ }
+ return transformed;
+ }
+
+ public List<String> getExtractedKeepRules() throws Exception {
+ List<Class<?>> classes = getInputClasses();
+ List<String> rules = new ArrayList<>();
+ for (Class<?> clazz : classes) {
+ rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
+ }
+ return rules;
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isPresent());
+ assertThat(inspector.clazz(B.class).uniqueMethodWithOriginalName("<init>"), isPresent());
+ assertThat(inspector.clazz(B.class).uniqueMethodWithOriginalName("bar"), isPresent());
+ }
+
+ static class A {
+
+ @UsesReflection(
+ value = {
+ // Ensure B's constructor and method 'bar' remain as they are invoked by reflection.
+ @KeepTarget(classConstant = B.class, methodName = "<init>"),
+ @KeepTarget(classConstant = B.class, methodName = "bar")
+ },
+ additionalPreconditions = {
+ // The reflection depends on the class constant being used in the program in addition to
+ // this method. In rule extraction, this will lead to an over-approximation as the rules
+ // will need to keep the above live if either of the two conditions are met. There is no
+ // way to express the conjunction with multiple distinct precondition classes in the
+ // rule language. With direct annotation interpretation this limitation is avoided and
+ // a more precise shrinking is possible.
+ // TODO(b/248408342): Check this once direct interpretation is supported.
+ @KeepCondition(classConstant = B.class)
+ })
+ public void foo(Class<B> clazz) throws Exception {
+ if (clazz != null) {
+ clazz.getDeclaredMethod("bar").invoke(clazz.getDeclaredConstructor().newInstance());
+ }
+ }
+ }
+
+ static class B {
+ public static void bar() {
+ System.out.println("Hello, world");
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ new A().foo(System.nanoTime() > 0 ? B.class : null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index afc975a..bcd5360 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1463,6 +1463,10 @@
});
}
+ public ClassFileTransformer removeAllAnnotations() {
+ return removeClassAnnotations().removeMethodAnnotations().removeFieldAnnotations();
+ }
+
public ClassFileTransformer removeClassAnnotations() {
return addClassTransformer(
new ClassTransformer() {