Skip to content

Commit ebfb529

Browse files
committed
Add attribute validation to IncompatibleTargetChecker.
This causes errors with configurable `target_compatible_with` to be reported as errors, rather than causing a Bazel crash. Fixes bazelbuild#18021.
1 parent dd99cb1 commit ebfb529

File tree

6 files changed

+79
-9
lines changed

6 files changed

+79
-9
lines changed

src/main/java/com/google/devtools/build/lib/analysis/constraints/IncompatibleTargetChecker.java

+14-2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import com.google.devtools.build.lib.events.ExtendedEventHandler;
4646
import com.google.devtools.build.lib.packages.BuildType;
4747
import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper;
48+
import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper.ValidationException;
4849
import com.google.devtools.build.lib.packages.Package;
4950
import com.google.devtools.build.lib.packages.PackageSpecification;
5051
import com.google.devtools.build.lib.packages.PackageSpecification.PackageGroupContents;
@@ -56,6 +57,7 @@
5657
import com.google.devtools.build.lib.util.OrderedSetMultimap;
5758
import com.google.devtools.build.skyframe.SkyValue;
5859
import com.google.devtools.build.skyframe.state.StateMachine;
60+
import java.util.List;
5961
import java.util.Optional;
6062
import java.util.function.Consumer;
6163
import javax.annotation.Nullable;
@@ -115,6 +117,8 @@ public static class IncompatibleTargetProducer implements StateMachine, Consumer
115117
/** Sink for the output of this state machine. */
116118
public interface ResultSink {
117119
void accept(Optional<RuleConfiguredTargetValue> incompatibleTarget);
120+
121+
void acceptValidationException(ValidationException e);
118122
}
119123

120124
public IncompatibleTargetProducer(
@@ -148,8 +152,16 @@ public StateMachine step(Tasks tasks, ExtendedEventHandler listener) {
148152
return DONE;
149153
}
150154

151-
// Resolves the constraint labels.
152-
for (Label label : attrs.get("target_compatible_with", BuildType.LABEL_LIST)) {
155+
// Resolves the constraint labels, checking for invalid configured attributes.
156+
List<Label> targetCompatibleWith;
157+
try {
158+
targetCompatibleWith = attrs.getAndValidate("target_compatible_with", BuildType.LABEL_LIST);
159+
} catch (ValidationException e) {
160+
sink.accept(Optional.empty());
161+
sink.acceptValidationException(e);
162+
return DONE;
163+
}
164+
for (Label label : targetCompatibleWith) {
153165
tasks.lookUp(
154166
ConfiguredTargetKey.builder().setLabel(label).setConfiguration(configuration).build(),
155167
this);

src/main/java/com/google/devtools/build/lib/analysis/constraints/TopLevelConstraintSemantics.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ private static String reportOnIncompatibility(ConfiguredTarget target) {
303303
String message = "\nDependency chain:";
304304
IncompatiblePlatformProvider provider = null;
305305

306-
// TODO(austinschuh): While the first eror is helpful, reporting all the errors at once would
306+
// TODO(austinschuh): While the first error is helpful, reporting all the errors at once would
307307
// save the user bazel round trips.
308308
while (target != null) {
309309
message +=

src/main/java/com/google/devtools/build/lib/packages/ConfiguredAttributeMapper.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ private ValidationException(String message) {
125125
* Variation of {@link #get} that throws an informative exception if the attribute can't be
126126
* resolved due to intrinsic contradictions in the configuration.
127127
*/
128-
private <T> T getAndValidate(String attributeName, Type<T> type) throws ValidationException {
128+
public <T> T getAndValidate(String attributeName, Type<T> type) throws ValidationException {
129129
SelectorList<T> selectorList = getSelectorList(attributeName, type);
130130
if (selectorList == null) {
131131
// This is a normal attribute.
@@ -292,8 +292,8 @@ public <T> T get(String attributeName, Type<T> type) {
292292
return getAndValidate(attributeName, type);
293293
} catch (ValidationException e) {
294294
// Callers that reach this branch should explicitly validate the attribute through an
295-
// appropriate call and handle the exception directly. This method assumes
296-
// pre-validated attributes.
295+
// appropriate call (either {@link #validateAttributes} or {@link #getAndValidate}) and handle
296+
// the exception directly. This method assumes pre-validated attributes.
297297
throw new IllegalStateException(
298298
"lookup failed on attribute " + attributeName + ": " + e.getMessage());
299299
}

src/main/java/com/google/devtools/build/lib/skyframe/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ java_library(
313313
"//src/main/java/com/google/devtools/build/lib/io:inconsistent_filesystem_exception",
314314
"//src/main/java/com/google/devtools/build/lib/io:process_package_directory_exception",
315315
"//src/main/java/com/google/devtools/build/lib/packages",
316+
"//src/main/java/com/google/devtools/build/lib/packages:configured_attribute_mapper",
316317
"//src/main/java/com/google/devtools/build/lib/packages:exec_group",
317318
"//src/main/java/com/google/devtools/build/lib/packages:globber",
318319
"//src/main/java/com/google/devtools/build/lib/packages:globber_utils",

src/main/java/com/google/devtools/build/lib/skyframe/PrerequisiteProducer.java

+33-3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import com.google.devtools.build.lib.events.StoredEventHandler;
6060
import com.google.devtools.build.lib.packages.Aspect;
6161
import com.google.devtools.build.lib.packages.BuildType;
62+
import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper.ValidationException;
6263
import com.google.devtools.build.lib.packages.ExecGroup;
6364
import com.google.devtools.build.lib.packages.NoSuchTargetException;
6465
import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
@@ -92,6 +93,7 @@
9293
import java.util.Set;
9394
import java.util.function.Predicate;
9495
import javax.annotation.Nullable;
96+
import net.starlark.java.syntax.Location;
9597

9698
/**
9799
* Helper logic for {@link ConfiguredTargetFunction}: performs the analysis phase through
@@ -124,13 +126,16 @@ static class State implements SkyKeyComputeState, IncompatibleTargetProducer.Res
124126
* <p>Non-null only while the computation is in-flight.
125127
*/
126128
@Nullable private Driver incompatibleTargetProducer;
129+
127130
/**
128131
* If a value is present, it means the target was directly incompatible.
129132
*
130133
* <p>Non-null after the {@link #incompatibleTargetProducer} completes.
131134
*/
132135
private Optional<RuleConfiguredTargetValue> incompatibleTarget;
133136

137+
private ValidationException validationException;
138+
134139
/** Null if not yet computed or if {@link #resolveConfigurationsResult} is non-null. */
135140
@Nullable private OrderedSetMultimap<DependencyKind, DependencyKey> dependentNodeMapResult;
136141

@@ -171,6 +176,11 @@ static class State implements SkyKeyComputeState, IncompatibleTargetProducer.Res
171176
public void accept(Optional<RuleConfiguredTargetValue> incompatibleTarget) {
172177
this.incompatibleTarget = incompatibleTarget;
173178
}
179+
180+
@Override
181+
public void acceptValidationException(ValidationException e) {
182+
this.validationException = e;
183+
}
174184
}
175185

176186
/**
@@ -475,14 +485,15 @@ private static boolean checkForIncompatibleTarget(
475485
@Nullable ConfigConditions configConditions,
476486
@Nullable PlatformInfo targetPlatformInfo,
477487
@Nullable NestedSetBuilder<Package> transitivePackages)
478-
throws InterruptedException, IncompatibleTargetException {
488+
throws InterruptedException, IncompatibleTargetException, DependencyEvaluationException {
479489
if (state.incompatibleTarget == null) {
490+
BuildConfigurationValue configuration = targetAndConfiguration.getConfiguration();
480491
if (state.incompatibleTargetProducer == null) {
481492
state.incompatibleTargetProducer =
482493
new Driver(
483494
new IncompatibleTargetProducer(
484495
targetAndConfiguration.getTarget(),
485-
targetAndConfiguration.getConfiguration(),
496+
configuration,
486497
configConditions,
487498
targetPlatformInfo,
488499
transitivePackages,
@@ -492,7 +503,26 @@ private static boolean checkForIncompatibleTarget(
492503
return false;
493504
}
494505
state.incompatibleTargetProducer = null;
495-
if (state.incompatibleTarget.isPresent()) {
506+
if (state.validationException != null) {
507+
Label label = targetAndConfiguration.getLabel();
508+
Location location = targetAndConfiguration.getTarget().getLocation();
509+
env.getListener()
510+
.post(
511+
new AnalysisRootCauseEvent(
512+
configuration, label, state.validationException.getMessage()));
513+
throw new DependencyEvaluationException(
514+
new ConfiguredValueCreationException(
515+
location,
516+
state.validationException.getMessage(),
517+
label,
518+
configuration.getEventId(),
519+
null,
520+
null),
521+
// These errors occur within DependencyResolver, which is attached to the current
522+
// target. i.e. no dependent ConfiguredTargetFunction call happens to report its own
523+
// error.
524+
/* depReportedOwnError= */ false);
525+
} else if (state.incompatibleTarget.isPresent()) {
496526
throw new IncompatibleTargetException(state.incompatibleTarget.get());
497527
}
498528
}

src/test/shell/integration/target_compatible_with_test.sh

+27
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,33 @@ EOF
892892
expect_log 'ERROR: Build did NOT complete successfully'
893893
}
894894

895+
# Regression test for b/277371822.
896+
function test_missing_default() {
897+
cat >> target_skipping/BUILD <<'EOF'
898+
sh_test(
899+
name = "pass_on_foo1_or_foo2_but_not_on_foo3",
900+
srcs = [":pass.sh"],
901+
target_compatible_with = select({
902+
":foo1": [],
903+
# No default branch.
904+
}),
905+
)
906+
EOF
907+
908+
cd target_skipping || fail "couldn't cd into workspace"
909+
910+
bazel test \
911+
--show_result=10 \
912+
--host_platform=@//target_skipping:foo3_platform \
913+
--platforms=@//target_skipping:foo3_platform \
914+
//target_skipping:pass_on_foo1_or_foo2_but_not_on_foo3 &> "${TEST_log}" \
915+
&& fail "Bazel passed unexpectedly."
916+
917+
expect_log 'ERROR:.*configurable attribute "target_compatible_with" in //target_skipping:pass_on_foo1_or_foo2_but_not_on_foo3'
918+
expect_log 'ERROR: Build did NOT complete successfully'
919+
expect_not_log 'FATAL: bazel crashed'
920+
}
921+
895922
# Validates that we can express targets being compatible with everything _but_
896923
# A and B.
897924
function test_inverse_logic() {

0 commit comments

Comments
 (0)