Refactor `getValues` in `skyframe` directory to only provide the necessary methods for Skyframe evaluation. It will help create less garbage.

PiperOrigin-RevId: 438056310
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
index 861b277..7c0f2cb 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -2513,6 +2513,7 @@
         ":package_value",
         ":test_expansion_value",
         ":tests_for_target_pattern_value",
+        "//src/main/java/com/google/devtools/build/lib/bugreport",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/build/skyframe",
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PathCasingLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PathCasingLookupFunction.java
index 46395bf..89424a9 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PathCasingLookupFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PathCasingLookupFunction.java
@@ -24,9 +24,8 @@
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionException;
 import com.google.devtools.build.skyframe.SkyKey;
-import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.SkyframeIterableResult;
 import java.io.IOException;
-import java.util.Map;
 
 /** SkyFunction for {@link PathCasingLookupValue}s. */
 public final class PathCasingLookupFunction implements SkyFunction {
@@ -51,17 +50,25 @@
     SkyKey parentCasingKey = PathCasingLookupValue.key(parent);
     SkyKey parentFileKey = FileValue.key(parent);
     SkyKey childFileKey = FileValue.key(arg.getPath());
-    Map<SkyKey, SkyValue> values =
-        env.getValues(ImmutableList.of(parentCasingKey, parentFileKey, childFileKey));
+    SkyframeIterableResult values =
+        env.getOrderedValuesAndExceptions(
+            ImmutableList.of(parentCasingKey, parentFileKey, childFileKey));
     if (env.valuesMissing()) {
       return null;
     }
-    if (!((PathCasingLookupValue) values.get(parentCasingKey)).isCorrect()) {
+    PathCasingLookupValue pathCasingLookupValue = (PathCasingLookupValue) values.next();
+    if (pathCasingLookupValue == null) {
+      return null;
+    }
+    if (!pathCasingLookupValue.isCorrect()) {
       // Parent's casing is bad, so this path's casing is also bad.
       return PathCasingLookupValue.BAD;
     }
 
-    FileValue parentFile = (FileValue) values.get(parentFileKey);
+    FileValue parentFile = (FileValue) values.next();
+    if (parentFile == null) {
+      return null;
+    }
     if (!parentFile.exists()) {
       // Parent's casing is good, because it's missing.
       // That means this path is also missing, so by definition its casing is good.
@@ -76,7 +83,10 @@
                   + ": its parent exists but is not a directory"));
     }
 
-    FileValue childFile = (FileValue) values.get(childFileKey);
+    FileValue childFile = (FileValue) values.next();
+    if (childFile == null) {
+      return null;
+    }
     if (!childFile.exists()) {
       // Parent's casing is good, but this file is missing.
       // That means this path is missing, so by definition its casing is good.
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareAnalysisPhaseFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareAnalysisPhaseFunction.java
index d0d0a07..f1ae109 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareAnalysisPhaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareAnalysisPhaseFunction.java
@@ -43,7 +43,8 @@
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionException;
 import com.google.devtools.build.skyframe.SkyKey;
-import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.SkyframeIterableResult;
+import com.google.devtools.build.skyframe.SkyframeLookupResult;
 import com.google.devtools.common.options.OptionsParsingException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -110,7 +111,7 @@
 
     ImmutableList<BuildConfigurationKey> targetConfigurationKeys =
         targetConfigurationKeysBuilder.build();
-    Map<SkyKey, SkyValue> configs = env.getValues(targetConfigurationKeys);
+    SkyframeLookupResult configs = env.getValuesAndExceptions(targetConfigurationKeys);
 
     // We only report invalid options for the target configurations, and abort if there's an error.
     ErrorSensingEventHandler<Void> nosyEventHandler =
@@ -277,7 +278,7 @@
       }
     }
 
-    Map<SkyKey, SkyValue> configsResult = env.getValues(configSkyKeys);
+    SkyframeIterableResult configsResult = env.getOrderedValuesAndExceptions(configSkyKeys);
     if (env.valuesMissing()) {
       return null;
     }
@@ -292,20 +293,14 @@
       if (buildSettingPackages == null) {
         return null;
       }
-      Collection<BuildOptions> toOptions =
-          ConfigurationResolver.applyTransitionWithSkyframe(
-                  fromOptions, transition, env, env.getListener())
-              .values();
-      for (BuildOptions toOption : toOptions) {
-        SkyKey configKey =
-            BuildConfigurationKey.withPlatformMapping(platformMappingValue, toOption);
-        BuildConfigurationValue configValue =
-            (BuildConfigurationValue) configsResult.get(configKey);
+      while (configsResult.hasNext()) {
+        BuildConfigurationValue configValue = (BuildConfigurationValue) configsResult.next();
         // configValue will be null here if there was an exception thrown during configuration
         // creation. This will be reported elsewhere.
-        if (configValue != null) {
-          builder.put(key, configValue);
+        if (configValue == null) {
+          return null;
         }
+          builder.put(key, configValue);
       }
     }
     return builder;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternFunction.java
index 10527f6..55c693f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternFunction.java
@@ -339,8 +339,8 @@
 
       for (Root root : roots) {
         RootedPath rootedPath = RootedPath.toRootedPath(root, directoryPathFragment);
-        env.getValues(getDeps(repository, repositoryIgnoredSubdirectories, policy, rootedPath));
-        if (env.valuesMissing()) {
+        if (GraphTraversingHelper.declareDependenciesAndCheckIfValuesMissing(
+            env, getDeps(repository, repositoryIgnoredSubdirectories, policy, rootedPath))) {
           throw new MissingDepException();
         }
       }
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 b723829..df82357 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
@@ -18,9 +18,7 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Verify;
-import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Maps;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
 import com.google.devtools.build.lib.actions.FileArtifactValue;
@@ -60,10 +58,10 @@
 import com.google.devtools.build.skyframe.SkyFunctionException;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.SkyframeIterableResult;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -230,7 +228,7 @@
       }
 
       // We are free to traverse this directory.
-      Collection<RecursiveFilesystemTraversalValue> subdirTraversals =
+      ImmutableList<RecursiveFilesystemTraversalValue> subdirTraversals =
           traverseChildren(env, traversal);
       if (subdirTraversals == null) {
         return null;
@@ -595,7 +593,7 @@
   private static RecursiveFilesystemTraversalValue resultForDirectory(
       TraversalRequest traversal,
       FileInfo rootInfo,
-      Collection<RecursiveFilesystemTraversalValue> subdirTraversals) {
+      ImmutableList<RecursiveFilesystemTraversalValue> subdirTraversals) {
     // Collect transitive closure of files in subdirectories.
     NestedSetBuilder<ResolvedFile> paths = NestedSetBuilder.stableOrder();
     for (RecursiveFilesystemTraversalValue child : subdirTraversals) {
@@ -635,7 +633,7 @@
 
   /** Requests Skyframe to compute the dependent values and returns them. */
   @Nullable
-  private Collection<RecursiveFilesystemTraversalValue> traverseChildren(
+  private ImmutableList<RecursiveFilesystemTraversalValue> traverseChildren(
       Environment env, TraversalRequest traversal)
       throws InterruptedException, RecursiveFilesystemTraversalFunctionException, IOException {
     // Use the traversal's path, even if it's a symlink. The contents of the directory, as listed
@@ -677,21 +675,33 @@
     if (keys == null) {
       return null;
     }
-    Map<SkyKey, SkyValue> values = Maps.newHashMapWithExpectedSize(keys.size());
+    ImmutableList.Builder<RecursiveFilesystemTraversalValue> values =
+        ImmutableList.builderWithExpectedSize(keys.size());
     if (traversal.isRootGenerated) {
       // Don't create Skyframe nodes for a recursive traversal over the output tree.
       // Instead, inline the recursion in the top-level request.
       for (TraversalRequest traversalRequest : keys) {
-        values.put(traversalRequest, compute(traversalRequest, env));
+        SkyValue computeValue = compute(traversalRequest, env);
+        if (computeValue == null) {
+          continue;
+        }
+        values.add((RecursiveFilesystemTraversalValue) computeValue);
       }
     } else {
-      values = env.getValues(keys);
+      SkyframeIterableResult result = env.getOrderedValuesAndExceptions(keys);
+      while (result.hasNext()) {
+        var iterateValue = (RecursiveFilesystemTraversalValue) result.next();
+        if (iterateValue == null) {
+          break;
+        }
+        values.add(iterateValue);
+      }
     }
     if (env.valuesMissing()) {
       return null;
     }
 
-    return Collections2.transform(values.values(), RecursiveFilesystemTraversalValue.class::cast);
+    return values.build();
   }
 
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java
index 92de221..e17ae51 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternPhaseFunction.java
@@ -55,7 +55,6 @@
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunction.Environment;
 import com.google.devtools.build.skyframe.SkyKey;
-import com.google.devtools.build.skyframe.SkyValue;
 import com.google.devtools.build.skyframe.SkyframeIterableResult;
 import com.google.devtools.build.skyframe.SkyframeLookupResult;
 import java.util.ArrayList;
@@ -130,7 +129,7 @@
         }
       }
     }
-    Map<SkyKey, SkyValue> expandedTests = env.getValues(testExpansionKeys.values());
+    SkyframeLookupResult expandedTests = env.getValuesAndExceptions(testExpansionKeys.values());
     if (env.valuesMissing()) {
       return null;
     }
@@ -200,13 +199,16 @@
 
     ResolvedTargets.Builder<Label> expandedLabelsBuilder = ResolvedTargets.builder();
     ImmutableMap.Builder<Label, ImmutableSet<Label>> testSuiteExpansions =
-        ImmutableMap.builderWithExpectedSize(expandedTests.size());
+        ImmutableMap.builderWithExpectedSize(testExpansionKeys.size());
     for (Target target : targets.getTargets()) {
       Label label = target.getLabel();
       if (TargetUtils.isTestSuiteRule(target) && options.isExpandTestSuites()) {
         SkyKey expansionKey = Preconditions.checkNotNull(testExpansionKeys.get(label));
-        ResolvedTargets<Label> testExpansion =
-            ((TestsForTargetPatternValue) expandedTests.get(expansionKey)).getLabels();
+        var value = (TestsForTargetPatternValue) expandedTests.get(expansionKey);
+        if (value == null) {
+          return null;
+        }
+        ResolvedTargets<Label> testExpansion = value.getLabels();
         expandedLabelsBuilder.merge(testExpansion);
         testSuiteExpansions.put(label, testExpansion.getTargets());
       } else {
@@ -443,7 +445,7 @@
       }
       expandedSuiteKeys.add(TestsForTargetPatternValue.key(value.getTargets().getTargets()));
     }
-    Map<SkyKey, SkyValue> expandedSuites = env.getValues(expandedSuiteKeys);
+    SkyframeIterableResult expandedSuites = env.getOrderedValuesAndExceptions(expandedSuiteKeys);
     if (env.valuesMissing()) {
       return null;
     }
@@ -466,8 +468,13 @@
       }
 
       TestsForTargetPatternValue expandedSuitesValue =
-          (TestsForTargetPatternValue)
-              expandedSuites.get(TestsForTargetPatternValue.key(value.getTargets().getTargets()));
+          (TestsForTargetPatternValue) expandedSuites.next();
+      if (expandedSuitesValue == null) {
+        BugReport.sendBugReport(
+            new IllegalStateException(
+                "expandedSuitesValue " + pattern + " was missing, this should never happen"));
+        return null;
+      }
       if (pattern.isNegative()) {
         ResolvedTargets<Target> negativeTargets =
             TestsForTargetPatternFunction.labelsToTargets(
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TestCompletionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TestCompletionFunction.java
index b98e2e4..f2357ef 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TestCompletionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TestCompletionFunction.java
@@ -20,6 +20,7 @@
 import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
 import com.google.devtools.build.lib.analysis.test.TestProvider;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.skyframe.GraphTraversingHelper;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
@@ -55,11 +56,11 @@
         }
       }
     } else {
-      env.getValues(
+      if (GraphTraversingHelper.declareDependenciesAndCheckIfValuesMissingMaybeWithExceptions(
+          env,
           Iterables.transform(
               TestProvider.getTestStatusArtifacts(ct),
-              Artifact.DerivedArtifact::getGeneratingActionKey));
-      if (env.valuesMissing()) {
+              Artifact.DerivedArtifact::getGeneratingActionKey))) {
         return null;
       }
     }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TestsForTargetPatternFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TestsForTargetPatternFunction.java
index ec13a52..33ee1ba 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TestsForTargetPatternFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TestsForTargetPatternFunction.java
@@ -14,6 +14,7 @@
 package com.google.devtools.build.lib.skyframe;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.bugreport.BugReport;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.cmdline.ResolvedTargets;
@@ -25,6 +26,7 @@
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.SkyframeIterableResult;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
@@ -48,7 +50,7 @@
         testsInSuitesKeys.add(TestExpansionValue.key(target, true));
       }
     }
-    Map<SkyKey, SkyValue> testsInSuites = env.getValues(testsInSuitesKeys);
+    SkyframeIterableResult testsInSuites = env.getOrderedValuesAndExceptions(testsInSuitesKeys);
     if (env.valuesMissing()) {
       return null;
     }
@@ -59,19 +61,16 @@
       if (TargetUtils.isTestRule(target)) {
         result.add(target.getLabel());
       } else if (TargetUtils.isTestSuiteRule(target)) {
-        TestExpansionValue value =
-            (TestExpansionValue) testsInSuites.get(TestExpansionValue.key(target, true));
-        if (value != null) {
-          result.addAll(value.getLabels().getTargets());
-          hasError |= value.getLabels().hasError();
+        TestExpansionValue value = (TestExpansionValue) testsInSuites.next();
+        if (value == null) {
+          return null;
         }
+        result.addAll(value.getLabels().getTargets());
+        hasError |= value.getLabels().hasError();
       } else {
         result.add(target.getLabel());
       }
     }
-    if (env.valuesMissing()) {
-      return null;
-    }
     // We use ResolvedTargets in order to associate an error flag; the result should never contain
     // any filtered targets.
     return new TestsForTargetPatternValue(new ResolvedTargets<>(result, hasError));
@@ -83,8 +82,8 @@
     for (Label label : labels) {
       pkgIdentifiers.add(label.getPackageIdentifier());
     }
-    // Don't bother to check for exceptions - the incoming list should only contain valid targets.
-    Map<SkyKey, SkyValue> packages = env.getValues(PackageValue.keys(pkgIdentifiers));
+    List<SkyKey> packagesKeys = PackageValue.keys(pkgIdentifiers);
+    SkyframeIterableResult packages = env.getOrderedValuesAndExceptions(packagesKeys);
     if (env.valuesMissing()) {
       return null;
     }
@@ -92,10 +91,16 @@
     ResolvedTargets.Builder<Target> builder = ResolvedTargets.builder();
     builder.mergeError(hasError);
     Map<PackageIdentifier, Package> packageMap = new HashMap<>();
-    for (Map.Entry<SkyKey, SkyValue> entry : packages.entrySet()) {
-      packageMap.put(
-          (PackageIdentifier) entry.getKey().argument(),
-          ((PackageValue) entry.getValue()).getPackage());
+    for (SkyKey packagesKey : packagesKeys) {
+      // Don't bother to check for exceptions - the incoming list should only contain valid targets.
+      PackageValue packagesValue = (PackageValue) packages.next();
+      if (packagesValue == null) {
+        BugReport.sendBugReport(
+            new IllegalStateException(
+                "PackageValue " + packagesKey + " was missing, this should never happen"));
+        return null;
+      }
+      packageMap.put((PackageIdentifier) packagesKey.argument(), packagesValue.getPackage());
     }
 
     for (Label label : labels) {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TopLevelActionLookupConflictFindingFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TopLevelActionLookupConflictFindingFunction.java
index e567650..a4066fc 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TopLevelActionLookupConflictFindingFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TopLevelActionLookupConflictFindingFunction.java
@@ -13,6 +13,8 @@
 // limitations under the License.
 package com.google.devtools.build.lib.skyframe;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.actions.ActionLookupKey;
@@ -22,12 +24,12 @@
 import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.skyframe.GraphTraversingHelper;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionException;
 import com.google.devtools.build.skyframe.SkyFunctionName;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
-import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 
 /**
@@ -46,12 +48,13 @@
     if (env.valuesMissing()) {
       return null;
     }
-
-    env.getValues(
-        ActionLookupConflictFindingFunction.convertArtifacts(
-                valueAndArtifactsToBuild.second.getAllArtifacts())
-            .collect(Collectors.toList()));
-    return env.valuesMissing() ? null : ActionLookupConflictFindingValue.INSTANCE;
+    return GraphTraversingHelper.declareDependenciesAndCheckIfValuesMissingMaybeWithExceptions(
+            env,
+            ActionLookupConflictFindingFunction.convertArtifacts(
+                    valueAndArtifactsToBuild.second.getAllArtifacts())
+                .collect(toImmutableList()))
+        ? null
+        : ActionLookupConflictFindingValue.INSTANCE;
   }
 
   @Nullable
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ToplevelStarlarkAspectFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ToplevelStarlarkAspectFunction.java
index 12a608f..1b356d4 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ToplevelStarlarkAspectFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ToplevelStarlarkAspectFunction.java
@@ -25,6 +25,7 @@
 import com.google.devtools.build.skyframe.SkyFunctionException;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.SkyframeIterableResult;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
@@ -63,12 +64,20 @@
             topLevelAspectsDetails.getAspectsDetails(),
             topLevelAspectsKey.getBaseConfiguredTargetKey());
 
-    Map<SkyKey, SkyValue> result = env.getValues(aspectsKeys);
+    SkyframeIterableResult result = env.getOrderedValuesAndExceptions(aspectsKeys);
     if (env.valuesMissing()) {
       return null; // some aspects keys are not evaluated
     }
-
-    return new TopLevelAspectsValue(result.values());
+    ImmutableList.Builder<SkyValue> values =
+        ImmutableList.builderWithExpectedSize(aspectsKeys.size());
+    while (result.hasNext()) {
+      SkyValue value = result.next();
+      if (value == null) {
+        return null;
+      }
+      values.add(value);
+    }
+    return new TopLevelAspectsValue(values.build());
   }
 
   private static Collection<AspectKey> getTopLevelAspectsKeys(
diff --git a/src/main/java/com/google/devtools/build/skyframe/GraphTraversingHelper.java b/src/main/java/com/google/devtools/build/skyframe/GraphTraversingHelper.java
index 6e4d9cd..604140f 100644
--- a/src/main/java/com/google/devtools/build/skyframe/GraphTraversingHelper.java
+++ b/src/main/java/com/google/devtools/build/skyframe/GraphTraversingHelper.java
@@ -102,5 +102,29 @@
     return false;
   }
 
+  /**
+   * Returns false iff for each key in {@code skyKeys}, the corresponding node is done with values
+   * in the Skyframe graph, and every node evaluated successfully without an exception.
+   *
+   * <p>Prefer {@link #declareDependenciesAndCheckIfValuesMissing} when possible. This method is for
+   * {@link SkyFunction} callers that don't handle child exceptions themselves, and just want to
+   * propagate child exceptions upwards via Skyframe.
+   */
+  public static <E extends Exception>
+      boolean declareDependenciesAndCheckIfValuesMissingMaybeWithExceptions(
+          SkyFunction.Environment env, Iterable<? extends SkyKey> skyKeys)
+          throws InterruptedException {
+    SkyframeIterableResult result = env.getOrderedValuesAndExceptions(skyKeys);
+    if (env.valuesMissing()) {
+      return true;
+    }
+    while (result.hasNext()) {
+      if (result.next() == null) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private GraphTraversingHelper() {}
 }