GP-0: Various UI test fixes, incl. viewport syncing

GP-0: Fixed Listing tests wrt/ location label

GP-0: Trying to synchronize view creation and memory layout

GP-0: Trying to fix CME

GP-0: Got viewport synchronized, and it resolved test.

GP-0: Fixing regions tests - involved listing selections

GP-0: Fixed modules/sections provider tests

GP-0: Fixed static-mappings provider tests.
This commit is contained in:
Dan
2021-05-06 11:58:44 -04:00
parent e0b67c4218
commit 6bac39e79c
9 changed files with 215 additions and 133 deletions

View File

@@ -180,7 +180,6 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
actionNewListing = new NewListingAction();
}
@Override
public DebuggerListingProvider createListingIfMissing(LocationTrackingSpec spec,
boolean followsCurrentThread) {
synchronized (disconnectedProviders) {
@@ -331,7 +330,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
@Override
public void setCurrentSelection(ProgramSelection selection) {
getListingPanel().setSelection(selection);
getConnectedProvider().setSelection(selection);
}
@Override

View File

@@ -454,7 +454,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
return;
}
Set<TraceModule> modules = getSelectedModules(myActionContext);
if (modules.size() != 1) {
if (modules == null || modules.size() != 1) {
return;
}
TraceModule mod = modules.iterator().next();

View File

@@ -16,19 +16,16 @@
package ghidra.app.services;
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.listing.*;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramSelection;
@ServiceInfo( //
defaultProvider = DebuggerListingPlugin.class, //
description = "Replacement CodeViewerService for Debugger" //
defaultProvider = DebuggerListingPlugin.class, //
description = "Replacement CodeViewerService for Debugger" //
)
public interface DebuggerListingService extends CodeViewerService {
@Deprecated
DebuggerListingProvider createListingIfMissing(LocationTrackingSpec spec,
boolean followsCurrentThread);
void setTrackingSpec(LocationTrackingSpec spec);

View File

@@ -743,15 +743,13 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals(traceManager.getCurrentView(), listingProvider.getProgram());
assertEquals("dynamic-testCloseCurrentTraceBlanksListings",
listingProvider.locationLabel.getText());
assertEquals("(nowhere)", listingProvider.locationLabel.getText());
DebuggerListingProvider extraProvider = runSwing(
() -> listingPlugin.createListingIfMissing(trackNone, false));
waitForSwing();
assertEquals(traceManager.getCurrentView(), extraProvider.getProgram());
assertEquals("dynamic-testCloseCurrentTraceBlanksListings",
extraProvider.locationLabel.getText());
assertEquals("(nowhere)", extraProvider.locationLabel.getText());
traceManager.closeTrace(tb.trace);
waitForSwing();
@@ -1140,26 +1138,30 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals("dynamic-testLocationLabel", listingProvider.locationLabel.getText());
assertEquals("(nowhere)", listingProvider.locationLabel.getText());
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getMemoryManager()
.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x55550000, 0x555500ff),
.addRegion("test_region", Range.atLeast(0L), tb.range(0x55550000, 0x555502ff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
}
waitForDomainObject(tb.trace);
waitForPass(() -> assertEquals("dynamic-testLocationLabel (exe:.text)",
listingProvider.locationLabel.getText()));
waitForPass(() -> assertEquals("test_region", listingProvider.locationLabel.getText()));
TraceModule modExe;
try (UndoableTransaction tid = tb.startTransaction()) {
modExe = tb.trace.getModuleManager()
.addModule("modExe", "modExe",
tb.range(0x55550000, 0x555501ff), Range.atLeast(0L));
}
waitForDomainObject(tb.trace);
waitForPass(() -> assertEquals("modExe", listingProvider.locationLabel.getText()));
try (UndoableTransaction tid = tb.startTransaction()) {
TraceModule modExe = tb.trace.getModuleManager()
.addModule("modExe", "modExe",
tb.range(0x55550000, 0x555500ff), Range.atLeast(0L));
modExe.addSection(".text", tb.range(0x55550000, 0x555500ff));
}
waitForDomainObject(tb.trace);
waitForPass(() -> assertEquals("dynamic-testLocationLabel (modExe:.text)",
listingProvider.locationLabel.getText()));
waitForPass(() -> assertEquals("modExe:.text", listingProvider.locationLabel.getText()));
}
@Test

View File

@@ -15,12 +15,12 @@
*/
package ghidra.app.plugin.core.debug.gui.memory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.util.Set;
import org.junit.*;
import org.junit.Before;
import org.junit.Test;
import com.google.common.collect.Range;
@@ -186,6 +186,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForPass(() -> assertEquals(1, provider.regionTable.getRowCount()));
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData());
assertEquals(region, row.getRegion());
@@ -198,7 +199,6 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
}
@Test
@Ignore("TODO: Seems the error is in the listing")
public void testActionSelectAddresses() throws Exception {
addPlugin(tool, DebuggerListingPlugin.class);
DebuggerListingProvider listing = waitForComponentProvider(DebuggerListingProvider.class);
@@ -217,16 +217,17 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
waitForSwing();
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData());
waitForPass(() -> assertEquals(1, provider.regionTable.getRowCount()));
assertEquals(region, row.getRegion());
assertFalse(tb.trace.getProgramView().getMemory().isEmpty());
provider.setSelectedRegions(Set.of(region));
waitForSwing();
assertTrue(provider.actionSelectAddresses.isEnabled());
performAction(provider.actionSelectAddresses);
// TODO: This seems to me an error in the listing....
// When debugging, I see the selection in the listing, but this still returns empty...
assertEquals(tb.range(0x00400000, 0x0040ffff), new AddressSet(listing.getSelection()));
waitForPass(() -> assertEquals(tb.set(tb.range(0x00400000, 0x0040ffff)),
new AddressSet(listing.getSelection())));
}
@Test
@@ -249,12 +250,16 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData());
assertEquals(region, row.getRegion());
assertFalse(tb.trace.getProgramView().getMemory().isEmpty());
listing.setSelection(new ProgramSelection(tb.set(tb.range(0x00401234, 0x00404321))));
waitForPass(() -> assertEquals(tb.set(tb.range(0x00401234, 0x00404321)),
new AddressSet(listing.getSelection())));
waitForSwing();
assertTrue(provider.actionSelectRows.isEnabled());
performAction(provider.actionSelectRows);
assertEquals(Set.of(row), Set.copyOf(provider.getSelectedRows()));
waitForPass(() -> assertEquals(Set.of(row), Set.copyOf(provider.getSelectedRows())));
}
}

View File

@@ -222,6 +222,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
program.setName(modExe.getName());
}
waitForDomainObject(program);
waitForPass(() -> assertEquals(4, modulesProvider.sectionTable.getRowCount()));
modulesProvider.setSelectedSections(Set.of(secExeText));
performAction(modulesProvider.actionMapSections, false);
@@ -349,6 +350,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
addBlock(); // So the program has a size
}
waitForDomainObject(program);
waitForPass(() -> assertEquals(2, modulesProvider.moduleTable.getRowCount()));
modulesProvider.setSelectedModules(Set.of(modExe));
waitForSwing();
@@ -411,6 +413,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
program.setName(modExe.getName());
}
waitForDomainObject(program);
waitForPass(() -> assertEquals(4, modulesProvider.sectionTable.getRowCount()));
modulesProvider.setSelectedSections(Set.of(secExeText));
waitForSwing();
@@ -470,6 +473,8 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.activateTrace(tb.trace);
waitForSwing(); // NOTE: The table may select first by default, enabling action
waitForPass(() -> assertEquals(2, modulesProvider.moduleTable.getRowCount()));
waitForPass(() -> assertEquals(4, modulesProvider.sectionTable.getRowCount()));
modulesProvider.setSelectedModules(Set.of(modExe));
waitForSwing();
assertTrue(modulesProvider.actionSelectAddresses.isEnabled());
@@ -608,6 +613,8 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
try (UndoableTransaction tid = tb.startTransaction()) {
modExe.setName("/bin/echo"); // File has to exist
}
waitForPass(() -> assertEquals(2, modulesProvider.moduleTable.getRowCount()));
modulesProvider.setSelectedModules(Set.of(modExe));
waitForSwing();
performAction(modulesProvider.actionImportFromFileSystem, false);
@@ -616,6 +623,10 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
dialog.close();
}
protected Set<SectionRow> visibleSections() {
return Set.copyOf(modulesProvider.sectionFilterPanel.getTableFilterModel().getModelData());
}
@Test
public void testActionFilterSections() throws Exception {
addPlugin(tool, ImporterPlugin.class);
@@ -623,35 +634,29 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForPass(() -> assertEquals(2, modulesProvider.moduleTable.getRowCount()));
waitForPass(() -> assertEquals(4, modulesProvider.sectionTable.getRowCount()));
Set<SectionRow> visible =
Set.copyOf(modulesProvider.sectionFilterPanel.getTableFilterModel().getModelData());
assertEquals(4, visible.size());
assertEquals(4, visibleSections().size());
modulesProvider.setSelectedModules(Set.of(modExe));
waitForSwing();
visible =
Set.copyOf(modulesProvider.sectionFilterPanel.getTableFilterModel().getModelData());
assertEquals(4, visible.size());
assertEquals(4, visibleSections().size());
assertTrue(modulesProvider.actionFilterSectionsByModules.isEnabled());
performAction(modulesProvider.actionFilterSectionsByModules);
waitForSwing();
visible =
Set.copyOf(modulesProvider.sectionFilterPanel.getTableFilterModel().getModelData());
assertEquals(2, visible.size());
for (SectionRow row : visible) {
assertEquals(2, visibleSections().size());
for (SectionRow row : visibleSections()) {
assertEquals(modExe, row.getModule());
}
modulesProvider.setSelectedModules(Set.of());
waitForSwing();
visible =
Set.copyOf(modulesProvider.sectionFilterPanel.getTableFilterModel().getModelData());
assertEquals(4, visible.size());
waitForPass(() -> assertEquals(4, visibleSections().size()));
}
protected static final Set<String> POPUP_ACTIONS = Set.of(
@@ -693,6 +698,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
addModules();
traceManager.activateTrace(tb.trace);
waitForSwing();
waitForPass(() -> assertEquals(4, modulesProvider.sectionTable.getRowCount()));
clickTableCellWithButton(modulesProvider.sectionTable, 0, 0, MouseEvent.BUTTON3);
waitForSwing();

View File

@@ -171,9 +171,10 @@ public class DebuggerStaticMappingProviderTest extends AbstractGhidraHeadedDebug
waitForDomainObject(tb.trace);
// First check that all records are displayed
List<StaticMappingRow> correlationsDisplayed =
waitForPass(() -> assertEquals(3, mappingsProvider.mappingTable.getRowCount()));
List<StaticMappingRow> mappingsDisplayed =
mappingsProvider.mappingTableModel.getModelData();
assertEquals(3, correlationsDisplayed.size());
assertEquals(3, mappingsDisplayed.size());
// Select and remove the first 2 via the action
// NOTE: I'm not responsible for making the transaction here. The UI should do it.
@@ -182,9 +183,9 @@ public class DebuggerStaticMappingProviderTest extends AbstractGhidraHeadedDebug
waitForDomainObject(tb.trace);
// Now, check that only the final one remains
correlationsDisplayed = mappingsProvider.mappingTableModel.getModelData();
assertEquals(1, correlationsDisplayed.size());
StaticMappingRow record = correlationsDisplayed.get(0);
mappingsDisplayed = mappingsProvider.mappingTableModel.getModelData();
assertEquals(1, mappingsDisplayed.size());
StaticMappingRow record = mappingsDisplayed.get(0);
assertEquals(tb.addr(0xdeadbeef + 0x180), record.getTraceAddress());
// Check that they were removed from the trace as well

View File

@@ -525,19 +525,23 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
@Override
public DBTraceProgramView getFixedProgramView(long snap) {
DBTraceProgramView view = fixedProgramViews.computeIfAbsent(snap, t -> {
Msg.debug(this, "Creating fixed view at snap=" + snap);
return new DBTraceProgramView(this, snap, baseCompilerSpec);
});
return view;
synchronized (fixedProgramViews) {
DBTraceProgramView view = fixedProgramViews.computeIfAbsent(snap, t -> {
Msg.debug(this, "Creating fixed view at snap=" + snap);
return new DBTraceProgramView(this, snap, baseCompilerSpec);
});
return view;
}
}
@Override
public DBTraceVariableSnapProgramView createProgramView(long snap) {
DBTraceVariableSnapProgramView view =
new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
programViews.put(view, null);
return view;
synchronized (programViews) {
DBTraceVariableSnapProgramView view =
new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
programViews.put(view, null);
return view;
}
}
@Override
@@ -675,10 +679,14 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
}
protected void allViews(Consumer<DBTraceProgramView> action) {
for (DBTraceProgramView view : programViews.keySet()) {
action.accept(view);
Collection<DBTraceProgramView> all = new ArrayList<>();
synchronized (programViews) {
all.addAll(programViews.keySet());
}
for (DBTraceProgramView view : fixedProgramViews.values()) {
synchronized (fixedProgramViews) {
all.addAll(fixedProgramViews.values());
}
for (DBTraceProgramView view : all) {
action.accept(view);
}
}

View File

@@ -56,30 +56,20 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
}
private void snapshotAdded(TraceSnapshot snapshot) {
if (snapshot.getSchedule() == null) {
return;
}
if (spanSet.contains(snapshot.getKey())) {
recomputeSnapRanges();
return;
if (checkSnapshotAddedNeedsRefresh(snapshot)) {
refreshSnapRanges();
}
}
private void snapshotChanged(TraceSnapshot snapshot) {
if (isLower(snapshot.getKey())) {
recomputeSnapRanges();
return;
}
if (spanSet.contains(snapshot.getKey()) && snapshot.getSchedule() != null) {
recomputeSnapRanges();
return;
if (checkSnapshotChangedNeedsRefresh(snapshot)) {
refreshSnapRanges();
}
}
private void snapshotDeleted(TraceSnapshot snapshot) {
if (isLower(snapshot.getKey())) {
recomputeSnapRanges();
return;
if (checkSnapshotDeletedNeedsRefresh(snapshot)) {
refreshSnapRanges();
}
}
@@ -117,27 +107,31 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
@Override
public boolean containsAnyUpper(Range<Long> range) {
// NB. This should only ever visit the first range intersecting that given
for (Range<Long> intersecting : spanSet.subRangeSet(range).asRanges()) {
if (range.contains(intersecting.upperEndpoint())) {
return true;
synchronized (ordered) {
// NB. This should only ever visit the first range intersecting that given
for (Range<Long> intersecting : spanSet.subRangeSet(range).asRanges()) {
if (range.contains(intersecting.upperEndpoint())) {
return true;
}
}
return false;
}
return false;
}
@Override
public <T> boolean isCompletelyVisible(AddressRange range, Range<Long> lifespan, T object,
Occlusion<T> occlusion) {
for (Range<Long> rng : ordered) {
if (lifespan.contains(rng.upperEndpoint())) {
return true;
}
if (occlusion.occluded(object, range, rng)) {
return false;
synchronized (ordered) {
for (Range<Long> rng : ordered) {
if (lifespan.contains(rng.upperEndpoint())) {
return true;
}
if (occlusion.occluded(object, range, rng)) {
return false;
}
}
return false;
}
return false;
}
@Override
@@ -147,13 +141,15 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
return new AddressSet();
}
AddressSet remains = new AddressSet(set);
for (Range<Long> rng : ordered) {
if (lifespan.contains(rng.upperEndpoint())) {
return remains;
}
occlusion.remove(object, remains, rng);
if (remains.isEmpty()) {
return remains;
synchronized (ordered) {
for (Range<Long> rng : ordered) {
if (lifespan.contains(rng.upperEndpoint())) {
return remains;
}
occlusion.remove(object, remains, rng);
if (remains.isEmpty()) {
return remains;
}
}
}
// This condition should have been detected by !containsAnyUpper
@@ -161,14 +157,17 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
}
protected boolean isLower(long lower) {
Range<Long> range = spanSet.rangeContaining(lower);
if (range == null) {
return false;
synchronized (ordered) {
Range<Long> range = spanSet.rangeContaining(lower);
if (range == null) {
return false;
}
return range.lowerEndpoint().longValue() == lower;
}
return range.lowerEndpoint().longValue() == lower;
}
protected boolean addSnapRange(long lower, long upper) {
protected static boolean addSnapRange(long lower, long upper, RangeSet<Long> spanSet,
List<Range<Long>> ordered) {
if (spanSet.contains(lower)) {
return false;
}
@@ -178,7 +177,7 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
return true;
}
protected TraceSnapshot locateMostRecentFork(long from) {
protected static TraceSnapshot locateMostRecentFork(TraceTimeManager timeManager, long from) {
while (true) {
TraceSnapshot prev = timeManager.getMostRecentSnapshot(from);
if (prev == null) {
@@ -203,11 +202,23 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
}
}
protected void traverseAndAddForkRanges(long curSnap) {
/**
* Construct the ranges (set and ordered)
*
* <p>
* NOTE: I cannot hold the lock during this, because I also require the DB's read lock. There
* are other operations, e.g., addRegion, that will hold the DB's write lock, and then also
* require the viewport's lock to check if it is visible. That would cause the classic tango of
* death.
*
* @param curSnap the seed snap
*/
protected static void collectForkRanges(TraceTimeManager timeManager, long curSnap,
RangeSet<Long> spanSet, List<Range<Long>> ordered) {
while (true) {
TraceSnapshot fork = locateMostRecentFork(curSnap);
TraceSnapshot fork = locateMostRecentFork(timeManager, curSnap);
long prevSnap = fork == null ? Long.MIN_VALUE : fork.getKey();
if (!addSnapRange(prevSnap, curSnap)) {
if (!addSnapRange(prevSnap, curSnap, spanSet, ordered)) {
return;
}
if (fork == null) {
@@ -217,71 +228,124 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
}
}
protected void recomputeSnapRanges() {
spanSet.clear();
ordered.clear();
traverseAndAddForkRanges(snap);
protected void refreshSnapRanges() {
RangeSet<Long> spanSet = TreeRangeSet.create();
List<Range<Long>> ordered = new ArrayList<>();
collectForkRanges(timeManager, snap, spanSet, ordered);
synchronized (this.ordered) {
this.spanSet.clear();
this.ordered.clear();
this.spanSet.addAll(spanSet);
this.ordered.addAll(ordered);
}
assert !ordered.isEmpty();
changeListeners.fire.run();
}
public void setSnap(long snap) {
this.snap = snap;
recomputeSnapRanges();
refreshSnapRanges();
}
protected boolean checkSnapshotAddedNeedsRefresh(TraceSnapshot snapshot) {
synchronized (ordered) {
if (snapshot.getSchedule() == null) {
return false;
}
if (spanSet.contains(snapshot.getKey())) {
return true;
}
return false;
}
}
protected boolean checkSnapshotChangedNeedsRefresh(TraceSnapshot snapshot) {
synchronized (ordered) {
if (isLower(snapshot.getKey())) {
return true;
}
if (spanSet.contains(snapshot.getKey()) && snapshot.getSchedule() != null) {
return true;
}
return false;
}
}
protected boolean checkSnapshotDeletedNeedsRefresh(TraceSnapshot snapshot) {
synchronized (ordered) {
if (isLower(snapshot.getKey())) {
return true;
}
return false;
}
}
@Override
public boolean isForked() {
return ordered.size() > 1;
synchronized (ordered) {
return ordered.size() > 1;
}
}
@Override
public List<Long> getOrderedSnaps() {
return ordered
.stream()
.map(Range::upperEndpoint)
.collect(Collectors.toList());
synchronized (ordered) {
return ordered
.stream()
.map(Range::upperEndpoint)
.collect(Collectors.toList());
}
}
@Override
public List<Long> getReversedSnaps() {
return Lists.reverse(ordered)
.stream()
.map(Range::upperEndpoint)
.collect(Collectors.toList());
synchronized (ordered) {
return Lists.reverse(ordered)
.stream()
.map(Range::upperEndpoint)
.collect(Collectors.toList());
}
}
@Override
public <T> T getTop(Function<Long, T> func) {
for (Range<Long> rng : ordered) {
T t = func.apply(rng.upperEndpoint());
if (t != null) {
return t;
synchronized (ordered) {
for (Range<Long> rng : ordered) {
T t = func.apply(rng.upperEndpoint());
if (t != null) {
return t;
}
}
return null;
}
return null;
}
@Override
public <T> Iterator<T> mergedIterator(Function<Long, Iterator<T>> iterFunc,
Comparator<? super T> comparator) {
if (!isForked()) {
return iterFunc.apply(snap);
List<Iterator<T>> iters;
synchronized (ordered) {
if (!isForked()) {
return iterFunc.apply(snap);
}
iters = ordered.stream()
.map(rng -> iterFunc.apply(rng.upperEndpoint()))
.collect(Collectors.toList());
}
List<Iterator<T>> iters = ordered.stream()
.map(rng -> iterFunc.apply(rng.upperEndpoint()))
.collect(Collectors.toList());
return new UniqIterator<>(new MergeSortingIterator<>(iters, comparator));
}
@Override
public AddressSetView unionedAddresses(Function<Long, AddressSetView> viewFunc) {
if (!isForked()) {
return viewFunc.apply(snap);
List<AddressSetView> views;
synchronized (ordered) {
if (!isForked()) {
return viewFunc.apply(snap);
}
views = ordered.stream()
.map(rng -> viewFunc.apply(rng.upperEndpoint()))
.collect(Collectors.toList());
}
List<AddressSetView> views = ordered.stream()
.map(rng -> viewFunc.apply(rng.upperEndpoint()))
.collect(Collectors.toList());
return new UnionAddressSetView(views);
}
}