Stop blaze crash due to a symlink cycle or unbounded expansion encountered
while traversing filesets.

RELNOTES: None
PiperOrigin-RevId: 166913262
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java
index e122307..50ae176 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunction.java
@@ -31,6 +31,7 @@
 import com.google.devtools.build.skyframe.SkyFunctionException;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -97,6 +98,13 @@
     }
   }
 
+  /** Thrown when we encounter errors from underlying File operations */
+  public static final class FileOperationException extends RecursiveFilesystemTraversalException {
+    public FileOperationException(String message) {
+      super(message);
+    }
+  }
+
   /** Exception type thrown by {@link RecursiveFilesystemTraversalFunction#compute}. */
   private static final class RecursiveFilesystemTraversalFunctionException extends
       SkyFunctionException {
@@ -166,6 +174,9 @@
       // We are free to traverse this directory.
       Collection<SkyKey> dependentKeys = createRecursiveTraversalKeys(env, traversal);
       return resultForDirectory(traversal, rootInfo, traverseChildren(env, dependentKeys));
+    } catch (FileSymlinkException | InconsistentFilesystemException | IOException e) {
+      throw new RecursiveFilesystemTraversalFunctionException(
+          new FileOperationException("Error while traversing fileset: " + e.getMessage()));
     } catch (MissingDepException e) {
       return null;
     }
@@ -202,9 +213,20 @@
   }
 
   private static FileInfo lookUpFileInfo(Environment env, TraversalRequest traversal)
-      throws MissingDepException, InterruptedException {
+      throws MissingDepException, FileSymlinkException, InconsistentFilesystemException,
+          IOException, InterruptedException {
     // Stat the file.
-    FileValue fileValue = (FileValue) getDependentSkyValue(env, FileValue.key(traversal.path));
+    FileValue fileValue =
+        (FileValue)
+            env.getValueOrThrow(
+                FileValue.key(traversal.path),
+                FileSymlinkException.class,
+                InconsistentFilesystemException.class,
+                IOException.class);
+
+    if (env.valuesMissing()) {
+      throw new MissingDepException();
+    }
     if (fileValue.exists()) {
       // If it exists, it may either be a symlink or a file/directory.
       PathFragment unresolvedLinkTarget = null;
@@ -279,7 +301,8 @@
    */
   private static PkgLookupResult checkIfPackage(
       Environment env, TraversalRequest traversal, FileInfo rootInfo)
-      throws MissingDepException, InterruptedException {
+      throws MissingDepException, FileSymlinkException, InconsistentFilesystemException,
+          IOException, InterruptedException {
     Preconditions.checkArgument(rootInfo.type.exists() && !rootInfo.type.isFile(),
         "{%s} {%s}", traversal, rootInfo);
     PackageLookupValue pkgLookup = (PackageLookupValue) getDependentSkyValue(env,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java
index e42ac6e..b11543e 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/RecursiveFilesystemTraversalFunctionTest.java
@@ -38,6 +38,7 @@
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
 import com.google.devtools.build.lib.skyframe.PackageLookupFunction.CrossRepositoryLabelViolationStrategy;
 import com.google.devtools.build.lib.skyframe.PackageLookupValue.BuildFileName;
+import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalFunction.FileOperationException;
 import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.ResolvedFile;
 import com.google.devtools.build.lib.skyframe.RecursiveFilesystemTraversalValue.TraversalRequest;
 import com.google.devtools.build.lib.testutil.FoundationTestCase;
@@ -125,6 +126,11 @@
             directories));
     skyFunctions.put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction());
     skyFunctions.put(SkyFunctions.LOCAL_REPOSITORY_LOOKUP, new LocalRepositoryLookupFunction());
+    skyFunctions.put(
+        SkyFunctions.FILE_SYMLINK_INFINITE_EXPANSION_UNIQUENESS,
+        new FileSymlinkInfiniteExpansionUniquenessFunction());
+    skyFunctions.put(
+        SkyFunctions.FILE_SYMLINK_CYCLE_UNIQUENESS, new FileSymlinkCycleUniquenessFunction());
 
     progressReceiver = new RecordingEvaluationProgressReceiver();
     differencer = new RecordingDifferencer();
@@ -816,4 +822,35 @@
         .hasMessageThat()
         .contains("Generated directory a/b/c conflicts with package under the same path.");
   }
+
+  @Test
+  public void unboundedSymlinkExpansionError() throws Exception {
+    Artifact bazLink = sourceArtifact("foo/baz.sym");
+    Path parentDir = scratch.dir("foo");
+    bazLink.getPath().createSymbolicLink(parentDir);
+    SkyKey key = rftvSkyKey(pkgRoot(parentOf(rootedPath(bazLink)), DONT_CROSS));
+    EvaluationResult<SkyValue> result = eval(key);
+    assertThat(result.hasError()).isTrue();
+    ErrorInfo error = result.getError(key);
+    assertThat(error.getException()).isInstanceOf(FileOperationException.class);
+    assertThat(error.getException()).hasMessageThat().contains("Infinite symlink expansion");
+  }
+
+  @Test
+  public void symlinkChainError() throws Exception {
+    scratch.dir("a");
+    Artifact fooLink = sourceArtifact("a/foo.sym");
+    Artifact barLink = sourceArtifact("a/bar.sym");
+    Artifact bazLink = sourceArtifact("a/baz.sym");
+    fooLink.getPath().createSymbolicLink(barLink.getPath());
+    barLink.getPath().createSymbolicLink(bazLink.getPath());
+    bazLink.getPath().createSymbolicLink(fooLink.getPath());
+
+    SkyKey key = rftvSkyKey(pkgRoot(parentOf(rootedPath(bazLink)), DONT_CROSS));
+    EvaluationResult<SkyValue> result = eval(key);
+    assertThat(result.hasError()).isTrue();
+    ErrorInfo error = result.getError(key);
+    assertThat(error.getException()).isInstanceOf(FileOperationException.class);
+    assertThat(error.getException()).hasMessageThat().contains("Symlink cycle");
+  }
 }