I sent the question to Issuetracker.google.com and they are investigating the issue. This is their answer:
COMMENTS
All comments
sg...@google.comsg...@google.com #2May 17, 2023 11:02AM
This is caused by the code in DesugarCollections.java. The code is
trying to acquire the lock of the underlying SynchronizedCollection
through reflection of fields and methods which are internal to the
platform.
If this reflection does not succeed, then the methods removeIf,
forEach, replaceAll and sort will access the internal collection field
directly.
According to the hiddenapi-flags.csv the mutex field is not accessible
after Android 8.
This is not optimal, and could lead to undefined behavior. I suggest
that you avoid using SynchronizedCollection and instead use concurrent
data structures if at all possible.
sg...@google.comsg...@google.com #3May 17, 2023 11:04AM
Tested the following code on emulators, and it succeeds on Android
O_MR1 and fails on Android P. ALso tested on an Android U phone where
it also fails.
public class SynchronizedCollectionReflection {
public static String test() {
Class<? extends Collection> SYNCHRONIZED_COLLECTION;
SYNCHRONIZED_COLLECTION = Collections.synchronizedCollection(new ArrayList<>()).getClass();
Class<? extends List> SYNCHRONIZED_LIST;
SYNCHRONIZED_LIST = Collections.synchronizedList(new LinkedList<>()).getClass();
Field MUTEX_FIELD;
MUTEX_FIELD = getField(SYNCHRONIZED_COLLECTION, "mutex");
if (MUTEX_FIELD != null) {
MUTEX_FIELD.setAccessible(true);
} else {
return "Failed MUTEX_FIELD";
}
Field COLLECTION_FIELD;
COLLECTION_FIELD = getField(SYNCHRONIZED_COLLECTION, "c");
if (COLLECTION_FIELD != null) {
COLLECTION_FIELD.setAccessible(true);
} else {
return "Failed COLLECTION_FIELD";
}
Class<? extends Set> synchronizedSet = Collections.synchronizedSet(new HashSet<>()).getClass();
Constructor<? extends Set> SYNCHRONIZED_SET_CONSTRUCTOR;
SYNCHRONIZED_SET_CONSTRUCTOR = getConstructor(synchronizedSet, Set.class, Object.class);
if (SYNCHRONIZED_SET_CONSTRUCTOR != null) {
SYNCHRONIZED_SET_CONSTRUCTOR.setAccessible(true);
} else {
return "Failed SYNCHRONIZED_SET_CONSTRUCTOR";
}
Constructor<? extends Collection> SYNCHRONIZED_COLLECTION_CONSTRUCTOR;
SYNCHRONIZED_COLLECTION_CONSTRUCTOR =
getConstructor(SYNCHRONIZED_COLLECTION, Collection.class, Object.class);
if (SYNCHRONIZED_COLLECTION_CONSTRUCTOR != null) {
SYNCHRONIZED_COLLECTION_CONSTRUCTOR.setAccessible(true);
} else {
return "Failed SYNCHRONIZED_COLLECTION_CONSTRUCTOR";
}
return "SUCCESS!";
}
private static Field getField(Class<?> clazz, String name) {
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException e) {
// For Desugar: Some fields are not available on instrumented devices.
return null;
}
}
private static <E> Constructor<? extends E> getConstructor(
Class<? extends E> clazz, Class<?>... parameterTypes) {
try {
return clazz.getDeclaredConstructor(parameterTypes);
} catch (NoSuchMethodException e) {
// For Desugar: Some constructors are not available on instrumented devices.
return null;
}
}
}
sg...@google.comsg...@google.com #4May 17, 2023 11:05AM
Reassigned to cl...@google.com.
We should take another look at this, and consider failing the
operations which cannot be safely desugared.