Fix non-deterministic Blaze crash with "--aspects -k".
The crash is:
java.lang.ClassCastException: class com.google.devtools.build.lib.skyframe.AspectValue$AspectKey cannot be cast to class com.google.devtools.build.lib.skyframe.ConfiguredTargetKey (com.google.devtools.build.lib.skyframe.AspectValue$AspectKey and com.google.devtools.build.lib.skyframe.ConfiguredTargetKey are in unnamed module of loader 'app')
at com.google.devtools.build.lib.skyframe.ConfiguredTargetCycleReporter.asTransitiveTargetKey(ConfiguredTargetCycleReporter.java:94)
...
This only works with the following magical combination of features:
1) Building with a top-level aspect (--aspects).
2) Building -keep_going (-k).
3) The rule you're building or one of its transitive deps is a genquery.
4) The genquery hits a loading phase cycle.
5) Actually building whatever the genquery visits doesn't hit a cycle because a select() makes the cycle go away in the actual build.
While this sounds incomprehensible, the basic story is this crash only happens in a pure loading phase cycle based from TransitiveTargetValue. genquery is the only rule that uses TransitiveTargetValue. Its "scope = " attribute normally guarantees whatever rule has the cycle will first be analyzed and hit the cycle during configured target evaluation, which skips this code path (you'll see the nice non-crashy message "This cycle occurred because of a configuration option" in those cases). But select()able deps short-circuit this: The select() means the cycle *doesn't* appear in analysis because the build's flags happen not to trigger whatever dependency path would trigger the cycle. So "scope = " finishes just fine. Then genquery gets into its rule-specific analysis and calls TransitiveTargetValue, which doesn't know which build flags are set so chooses *all* deps, including the bad ones. And we get a "loading-phase only" error, which propagates up to the ConfiguredTargetCycleReporter.
Even *this* is normally fine: ConfiguredTargetCycleReporter already has logic to handle loading phase errors. But that logic assumes the top-level analyzing unit was a configured target, not an aspect. Bringing in --aspects breaks that assumption.
Code details:
This causes both a ConfiguredTarget and Aspect to be requested as top-level nodes at:
https://github.com/bazelbuild/bazel/blob/b2e1fe367269103e127718131163cd702cefcdff/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java#L403-L404
-keep_going guarantees both the aspect and configured target are evaluated in Skyframe. Both will return null on a cycle
error. Both have their error recorded at
https://github.com/bazelbuild/bazel/blob/f998f4e8b5c172286074f4de58eb3fb966ed8243/src/main/java/com/google/devtools/build/skyframe/EvaluationResult.java#L171
which stores them in a HashMap<SkyKey, ErrorInfo>:
https://github.com/bazelbuild/bazel/blob/f998f4e8b5c172286074f4de58eb3fb966ed8243/src/main/java/com/google/devtools/build/skyframe/EvaluationResult.java#L157
which is then non-deterministically iterated over for cycle detection in:
https://github.com/bazelbuild/bazel/blob/3740768a2f625e7b3310f69bcec47b5dd0898cf1/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java#L527.
If the top-level build request includes a configured target and aspect, that means we're randomly going to get one of [ConfiguredTargetKey, AspectKey] first.
If AspectKey comes first, then https://github.com/bazelbuild/bazel/blob/209847415ac251d0b73e98e705bcb1a411490a2e/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetCycleReporter.java#L94 crashes.
If it comes second, that code path is skipped because of
https://github.com/bazelbuild/bazel/blob/6505f7c5db239a823e8503dcc6798702b2d54519/src/main/java/com/google/devtools/build/lib/skyframe/AbstractLabelCycleReporter.java#L85.
I have no idea how to test this, given how deeply this depends on Skyframe's evaluation model.
========================================================================
Minimal repro:
$ mkdir testapp
$ cat >testapp/defs.bzl <<EOF
def _rule_impl(ctx):
pass
custom_rule = rule(
implementation = _rule_impl,
attrs = {
"deps": attr.label_list(),
},
)
AspectProvider = provider()
def _aspect_impl(target, ctx):
pass
my_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = ["deps"],
)
EOF
$ cat >testapp/BUILD <<EOF
load("//testapp:defs.bzl", "custom_rule")
config_setting(
name = "not_invoked",
define_values = {"dont_set_me": "1"},
)
custom_rule(
name = "my_rule",
deps = [":maybe_cycle_dep"],
)
custom_rule(
name = "maybe_cycle_dep",
deps = select({
"not_invoked": [":my_rule"],
"//conditions:default": [],
}),
)
genquery(
name = "my_query",
expression = "(//testapp:my_rule)",
scope = ["//testapp:my_rule"],
)
EOF
$ blaze build //testapp:my_query --aspects=//testapp:defs.bzl%my_aspect -k
Because of the non-determinism, this may or may not crash Blaze.
PiperOrigin-RevId: 277807980
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetCycleReporter.java b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetCycleReporter.java
index fe73b63..24b01e8 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetCycleReporter.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetCycleReporter.java
@@ -20,7 +20,7 @@
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
-import com.google.devtools.build.lib.actions.ActionLookupValue;
+import com.google.devtools.build.lib.actions.ActionLookupValue.ActionLookupKey;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.pkgcache.PackageProvider;
@@ -89,9 +89,13 @@
}
private SkyKey asTransitiveTargetKey(SkyKey key) {
- return IS_TRANSITIVE_TARGET_SKY_KEY.apply(key)
- ? key
- : TransitiveTargetKey.of(((ConfiguredTargetKey) key.argument()).getLabel());
+ if (IS_TRANSITIVE_TARGET_SKY_KEY.apply(key)) {
+ return key;
+ } else if (key.argument() instanceof ActionLookupKey) {
+ return TransitiveTargetKey.of(((ActionLookupKey) key.argument()).getLabel());
+ } else {
+ throw new IllegalArgumentException("Unknown type: " + key);
+ }
}
@Override
@@ -109,9 +113,8 @@
@Override
public Label getLabel(SkyKey key) {
- if (key instanceof ActionLookupValue.ActionLookupKey) {
- return Preconditions.checkNotNull(
- ((ActionLookupValue.ActionLookupKey) key.argument()).getLabel(), key);
+ if (key instanceof ActionLookupKey) {
+ return Preconditions.checkNotNull(((ActionLookupKey) key.argument()).getLabel(), key);
} else if (SkyFunctions.isSkyFunction(TRANSITIVE_TARGET).apply(key)) {
return ((TransitiveTargetKey) key).getLabel();
} else {
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetCycleReporterTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetCycleReporterTest.java
new file mode 100644
index 0000000..e455350
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetCycleReporterTest.java
@@ -0,0 +1,86 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.skyframe;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.skyframe.AspectValue.AspectKey;
+import com.google.devtools.build.skyframe.CycleInfo;
+import com.google.devtools.build.skyframe.SkyKey;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link ConfiguredTargetCycleReporter}. */
+@RunWith(JUnit4.class)
+public class ConfiguredTargetCycleReporterTest extends BuildViewTestCase {
+
+ /**
+ * Regression test for b/142966884 : Blaze crashes when building with --aspects and --keep_going
+ * on a target where the transitive deps have a genquery which finds a cycle over //foo:c that
+ * doesn't happen when actually building //foo:c because of a select() on its deps that skips the
+ * path that happens to make the cycle.
+ *
+ * <p>That results in top-level keys that aren't {@link ConfiguredTargetKey} in {@link
+ * ConfiguredTargetCycleReporter#getAdditionalMessageAboutCycle}.
+ */
+ @Test
+ public void loadingPhaseCycleWithDifferentTopLevelKeyTypes() throws Exception {
+ scratch.file(
+ "foo/BUILD",
+ "genrule(name = 'a', srcs = [], outs = ['a.o'], cmd = 'echo uh > $@')",
+ "genrule(name = 'b', srcs = [], outs = ['b.o'], cmd = 'echo hi > $@', visibility = [':c'])",
+ "genrule(name = 'c', srcs = [], outs = ['c.o'], cmd = 'echo hi > $@')");
+ ConfiguredTargetCycleReporter cycleReporter =
+ new ConfiguredTargetCycleReporter(getPackageManager());
+ CycleInfo cycle =
+ new CycleInfo(
+ ImmutableList.of(
+ TransitiveTargetKey.of(makeLabel("//foo:b")),
+ TransitiveTargetKey.of(makeLabel("//foo:c"))));
+
+ ConfiguredTargetKey ctKey = ConfiguredTargetKey.of(makeLabel("//foo:a"), targetConfig);
+ assertThat(cycleReporter.getAdditionalMessageAboutCycle(reporter, ctKey, cycle))
+ .contains(
+ "The cycle is caused by a visibility edge from //foo:b to the non-package-group "
+ + "target //foo:c");
+
+ SkyKey aspectKey =
+ AspectKey.createAspectKey(
+ makeLabel("//foo:a"),
+ ctKey,
+ ImmutableList.of(),
+ null,
+ BuildConfigurationValue.key(targetConfig),
+ /*aspectConfigurationIsHost=*/ false);
+ assertThat(cycleReporter.getAdditionalMessageAboutCycle(reporter, aspectKey, cycle))
+ .contains(
+ "The cycle is caused by a visibility edge from //foo:b to the non-package-group "
+ + "target //foo:c");
+
+ SkyKey starlarkAspectKey =
+ AspectValue.createSkylarkAspectKey(
+ makeLabel("//foo:a"),
+ targetConfig,
+ targetConfig,
+ makeLabel("//foo:b"),
+ "my Starlark key");
+ assertThat(cycleReporter.getAdditionalMessageAboutCycle(reporter, starlarkAspectKey, cycle))
+ .contains(
+ "The cycle is caused by a visibility edge from //foo:b to the non-package-group "
+ + "target //foo:c");
+ }
+}