blob: b61f8ed8e41a7743cfd25b49ad0ee900adfba69d [file]
// Copyright (c) 2026, 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.tracereferences;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.origin.MethodOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.utils.internal.ObjectUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;
class NativeReferencesTestingConsumer implements TraceReferencesNativeReferencesConsumer {
private static class LibraryAndOrigin {
private final String library;
private final MethodOrigin origin;
LibraryAndOrigin(String library, MethodOrigin origin) {
this.library = library;
this.origin = origin;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LibraryAndOrigin that = (LibraryAndOrigin) o;
return library.equals(that.library) && origin.equals(that.origin);
}
public String getLibrary() {
return library;
}
public MethodOrigin getOrigin() {
return origin;
}
@Override
public int hashCode() {
return ObjectUtils.hashLL(library, origin);
}
@Override
public String toString() {
return library + ": " + origin;
}
}
final List<LibraryAndOrigin> loadLibraryKnown = new ArrayList<>();
final List<MethodOrigin> loadLibraryAny = new ArrayList<>();
final List<LibraryAndOrigin> loadKnown = new ArrayList<>();
final List<MethodOrigin> loadAny = new ArrayList<>();
final Set<MethodReference> nativeMethods = ConcurrentHashMap.newKeySet();
private volatile boolean finished = false;
@Override
public void acceptLoadLibrary(String name, MethodOrigin origin, DiagnosticsHandler handler) {
synchronized (loadLibraryKnown) {
loadLibraryKnown.add(new LibraryAndOrigin(name, origin));
}
}
@Override
public void acceptLoadLibraryAny(MethodOrigin origin, DiagnosticsHandler handler) {
synchronized (loadLibraryAny) {
loadLibraryAny.add(origin);
}
}
@Override
public void acceptLoad(String name, MethodOrigin origin, DiagnosticsHandler handler) {
synchronized (loadKnown) {
loadKnown.add(new LibraryAndOrigin(name, origin));
}
}
@Override
public void acceptLoadAny(MethodOrigin origin, DiagnosticsHandler handler) {
synchronized (loadAny) {
loadAny.add(origin);
}
}
@Override
public void acceptNativeMethod(MethodReference methodReference, DiagnosticsHandler handler) {
boolean added = nativeMethods.add(methodReference);
assert added;
}
@Override
public void finished(DiagnosticsHandler handler) {
assert !finished;
finished = true;
}
public NativeReferencesTestingConsumer expectLoadLibrary(String name, Predicate<Origin> origin) {
for (LibraryAndOrigin element : loadLibraryKnown) {
if (element.getLibrary().equals(name) && origin.test(element.getOrigin())) {
loadLibraryKnown.remove(element);
return this;
}
}
fail(
"No predicate match. Content was: ["
+ loadLibraryKnown.stream().map(Object::toString).collect(Collectors.joining(", "))
+ "]");
return this;
}
public NativeReferencesTestingConsumer expectLoadLibrary(String name, Origin origin) {
return expectLoadLibrary(name, origin::equals);
}
public NativeReferencesTestingConsumer expectLoadLibraryAny(Predicate<Origin> origin) {
for (MethodOrigin element : loadLibraryAny) {
if (origin.test(element)) {
loadLibraryAny.remove(element);
return this;
}
}
fail(
"No predicate match. Content was: ["
+ loadLibraryAny.stream().map(Object::toString).collect(Collectors.joining(", "))
+ "]");
return this;
}
public NativeReferencesTestingConsumer expectLoadLibraryAny(Origin origin) {
return expectLoadLibraryAny(origin::equals);
}
public NativeReferencesTestingConsumer expectLoad(String name, Predicate<Origin> origin) {
for (LibraryAndOrigin element : loadKnown) {
if (element.getLibrary().equals(name) && origin.test(element.getOrigin())) {
loadKnown.remove(element);
return this;
}
}
fail(
"No predicate match. Content was: ["
+ loadKnown.stream().map(Object::toString).collect(Collectors.joining(", "))
+ "]");
return this;
}
public NativeReferencesTestingConsumer expectLoad(String name, Origin origin) {
return expectLoad(name, origin::equals);
}
public NativeReferencesTestingConsumer expectLoadAny(Predicate<Origin> origin) {
for (MethodOrigin element : loadAny) {
if (origin.test(element)) {
loadAny.remove(element);
return this;
}
}
fail(
"No predicate match. Content was: ["
+ loadAny.stream().map(Object::toString).collect(Collectors.joining(", "))
+ "]");
return this;
}
public NativeReferencesTestingConsumer expectLoadAny(Origin origin) {
return expectLoadAny(origin::equals);
}
public NativeReferencesTestingConsumer expectNativeMethod(MethodReference methodReference) {
for (MethodReference element : nativeMethods) {
if (element.equals(methodReference)) {
nativeMethods.remove(element);
return this;
}
}
fail(
"Expected to contain "
+ methodReference
+ ", but did not. Content was: ["
+ nativeMethods.stream().map(Object::toString).collect(Collectors.joining(", "))
+ "]");
return this;
}
public void thatsAll() {
assertTrue(
"Expected no more load library calls traced.",
loadLibraryKnown.isEmpty()
&& loadLibraryAny.isEmpty()
&& loadKnown.isEmpty()
&& loadAny.isEmpty()
&& nativeMethods.isEmpty()
&& finished);
}
}