Elegantly handle unbounded file symlink resolutions, e.g. 'a' -> 'b' -> 'a/nope'.
--
MOS_MIGRATED_REVID=99337668
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FileFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/FileFunction.java
index 1ca8588..ecccc07 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FileFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FileFunction.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.lib.skyframe;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.util.Pair;
@@ -28,7 +29,8 @@
import com.google.devtools.build.skyframe.SkyValue;
import java.io.IOException;
-import java.util.LinkedHashSet;
+import java.util.ArrayList;
+import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
@@ -41,7 +43,6 @@
* if the destination of the symlink is invalidated. Directory symlinks are also covered.
*/
public class FileFunction implements SkyFunction {
-
private final AtomicReference<PathPackageLocator> pkgLocator;
private final TimestampGranularityMonitor tsgm;
private final ExternalFilesHelper externalFilesHelper;
@@ -84,19 +85,11 @@
realFileStateValue = fileStateValue;
}
- LinkedHashSet<RootedPath> seenPaths = Sets.newLinkedHashSet();
+ ArrayList<RootedPath> symlinkChain = new ArrayList<>();
+ TreeSet<Path> orderedSeenPaths = Sets.newTreeSet();
while (realFileStateValue.getType().equals(FileStateValue.Type.SYMLINK)) {
- if (!seenPaths.add(realRootedPath)) {
- FileSymlinkCycleException fileSymlinkCycleException =
- makeFileSymlinkCycleException(realRootedPath, seenPaths);
- if (env.getValue(FileSymlinkCycleUniquenessValue.key(fileSymlinkCycleException.getCycle()))
- == null) {
- // Note that this dependency is merely to ensure that each unique cycle gets reported
- // exactly once.
- return null;
- }
- throw new FileFunctionException(fileSymlinkCycleException);
- }
+ symlinkChain.add(realRootedPath);
+ orderedSeenPaths.add(realRootedPath.asPath());
if (externalFilesHelper.shouldAssumeImmutable(realRootedPath)) {
// If the file is assumed to be immutable, we want to resolve the symlink chain without
// adding dependencies since we don't care about incremental correctness.
@@ -112,7 +105,7 @@
}
} else {
Pair<RootedPath, FileStateValue> resolvedState = getSymlinkTargetRootedPath(realRootedPath,
- realFileStateValue.getSymlinkTarget(), env);
+ realFileStateValue.getSymlinkTarget(), orderedSeenPaths, symlinkChain, env);
if (resolvedState == null) {
return null;
}
@@ -171,48 +164,94 @@
*/
@Nullable
private Pair<RootedPath, FileStateValue> getSymlinkTargetRootedPath(RootedPath rootedPath,
- PathFragment symlinkTarget, Environment env) throws FileFunctionException {
+ PathFragment symlinkTarget, TreeSet<Path> orderedSeenPaths,
+ Iterable<RootedPath> symlinkChain, Environment env) throws FileFunctionException {
+ RootedPath symlinkTargetRootedPath;
if (symlinkTarget.isAbsolute()) {
Path path = rootedPath.asPath().getFileSystem().getRootDirectory().getRelative(
symlinkTarget);
- return resolveFromAncestors(
- RootedPath.toRootedPathMaybeUnderRoot(path, pkgLocator.get().getPathEntries()), env);
+ symlinkTargetRootedPath =
+ RootedPath.toRootedPathMaybeUnderRoot(path, pkgLocator.get().getPathEntries());
+ } else {
+ Path path = rootedPath.asPath();
+ Path symlinkTargetPath;
+ if (path.getParentDirectory() != null) {
+ RootedPath parentRootedPath = RootedPath.toRootedPathMaybeUnderRoot(
+ path.getParentDirectory(), pkgLocator.get().getPathEntries());
+ FileValue parentFileValue = (FileValue) env.getValue(FileValue.key(parentRootedPath));
+ if (parentFileValue == null) {
+ return null;
+ }
+ symlinkTargetPath = parentFileValue.realRootedPath().asPath().getRelative(symlinkTarget);
+ } else {
+ // This means '/' is a symlink to 'symlinkTarget'.
+ symlinkTargetPath = path.getRelative(symlinkTarget);
+ }
+ symlinkTargetRootedPath = RootedPath.toRootedPathMaybeUnderRoot(symlinkTargetPath,
+ pkgLocator.get().getPathEntries());
}
- Path path = rootedPath.asPath();
- Path symlinkTargetPath;
- if (path.getParentDirectory() != null) {
- RootedPath parentRootedPath = RootedPath.toRootedPathMaybeUnderRoot(
- path.getParentDirectory(), pkgLocator.get().getPathEntries());
- FileValue parentFileValue = (FileValue) env.getValue(FileValue.key(parentRootedPath));
- if (parentFileValue == null) {
+ Path symlinkTargetPath = symlinkTargetRootedPath.asPath();
+ Path existingFloorPath = orderedSeenPaths.floor(symlinkTargetPath);
+ // Here is a brief argument that the following logic is correct.
+ //
+ // Any path 'p' in the symlink chain that is no larger than 'symlinkTargetPath' is one of:
+ // (i) 'symlinkTargetPath'
+ // (ii) a smaller sibling 's' of 'symlinkTargetPath' or a sibling of an ancestor of
+ // 'symlinkTargetPath'
+ // (iii) an ancestor 'a' of 'symlinkTargetPath'
+ // (iv) something else (e.g. a smaller sibling of an ancestor of 'symlinkTargetPath')
+ // If the largest 'p' is 'symlinkTarget' itself then 'existingFloorPath' will be that and we
+ // have found cycle. Otherwise, if there is such a 's' then 'existingFloorPath' will be the
+ // largest one. But the presence of any such 's' in the symlink chain implies an infinite
+ // expansion, which we would have already noticed. On the other hand, if there is such an 'a'
+ // then 'existingFloorPath' will be the largest one that and we definitely have found an
+ // infinite symlink expansion. Otherwise, if there is no such 'a', then the presence of
+ // 'symlinkTargetPath' doesn't create an infinite symlink expansion.
+ if (existingFloorPath != null && symlinkTargetPath.startsWith(existingFloorPath)) {
+ SkyKey uniquenessKey;
+ FileSymlinkException fse;
+ if (symlinkTargetPath.equals(existingFloorPath)) {
+ Pair<ImmutableList<RootedPath>, ImmutableList<RootedPath>> pathAndChain =
+ splitIntoPathAndChain(symlinkTargetRootedPath.asPath(), symlinkChain);
+ FileSymlinkCycleException fsce =
+ new FileSymlinkCycleException(pathAndChain.getFirst(), pathAndChain.getSecond());
+ uniquenessKey = FileSymlinkCycleUniquenessValue.key(fsce.getCycle());
+ fse = fsce;
+ } else {
+ Pair<ImmutableList<RootedPath>, ImmutableList<RootedPath>> pathAndChain =
+ splitIntoPathAndChain(existingFloorPath,
+ ImmutableList.copyOf(Iterables.concat(symlinkChain,
+ ImmutableList.of(symlinkTargetRootedPath))));
+ uniquenessKey = FileSymlinkInfiniteExpansionUniquenessValue.key(pathAndChain.getSecond());
+ fse = new FileSymlinkInfiniteExpansionException(
+ pathAndChain.getFirst(), pathAndChain.getSecond());
+ }
+ if (env.getValue(uniquenessKey) == null) {
+ // Note that this dependency is merely to ensure that each unique symlink error gets
+ // reported exactly once.
return null;
}
- symlinkTargetPath = parentFileValue.realRootedPath().asPath().getRelative(symlinkTarget);
- } else {
- // This means '/' is a symlink to 'symlinkTarget'.
- symlinkTargetPath = path.getRelative(symlinkTarget);
+ throw new FileFunctionException(fse);
}
- RootedPath symlinkTargetRootedPath = RootedPath.toRootedPathMaybeUnderRoot(symlinkTargetPath,
- pkgLocator.get().getPathEntries());
return resolveFromAncestors(symlinkTargetRootedPath, env);
}
- private FileSymlinkCycleException makeFileSymlinkCycleException(RootedPath startOfCycle,
- Iterable<RootedPath> symlinkPaths) {
+ private Pair<ImmutableList<RootedPath>, ImmutableList<RootedPath>> splitIntoPathAndChain(
+ Path startOfCycle, Iterable<RootedPath> symlinkRootedPaths) {
boolean inPathToCycle = true;
ImmutableList.Builder<RootedPath> pathToCycleBuilder = ImmutableList.builder();
ImmutableList.Builder<RootedPath> cycleBuilder = ImmutableList.builder();
- for (RootedPath path : symlinkPaths) {
- if (path.equals(startOfCycle)) {
+ for (RootedPath rootedPath : symlinkRootedPaths) {
+ if (rootedPath.asPath().equals(startOfCycle)) {
inPathToCycle = false;
}
if (inPathToCycle) {
- pathToCycleBuilder.add(path);
+ pathToCycleBuilder.add(rootedPath);
} else {
- cycleBuilder.add(path);
+ cycleBuilder.add(rootedPath);
}
}
- return new FileSymlinkCycleException(pathToCycleBuilder.build(), cycleBuilder.build());
+ return Pair.of(pathToCycleBuilder.build(), cycleBuilder.build());
}
@Nullable
@@ -231,7 +270,7 @@
super(e, transience);
}
- public FileFunctionException(FileSymlinkCycleException e) {
+ public FileFunctionException(FileSymlinkException e) {
super(e, Transience.PERSISTENT);
}