bazelbuild / bazel

a fast, scalable, multi-language and extensible build system
https://bazel.build
Apache License 2.0
23.15k stars 4.05k forks source link

Crash when incoming edge transition depends on a configurable attribute #23278

Open jacky8hyf opened 2 months ago

jacky8hyf commented 2 months ago

Description of the bug:

Bazel crashes if an incoming edge transition reads attr that is set to a select() expression.

Which category does this issue belong to?

No response

What's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.

# MODULE.bazel
# empty file
# BUILD.bazel
load(":a.bzl", "transitioned_files")

transitioned_files(
    name = "ok",
    target_platform = select({
        "//conditions:default": None, # LINE A
    })
)
# a.bzl
def _platform_transition_impl(_settings, attr):
    print(attr.target_platform)  # THIS CRASHES
    return None

platform_transition = transition(
    implementation = _platform_transition_impl,
    inputs = [],
    outputs = ["//command_line_option:platforms"],
)

def _transitioned_files_impl(_ctx):
    pass

transitioned_files = rule(
    implementation = _transitioned_files_impl,
    attrs = {
        "target_platform": attr.label(),
    },
    cfg = platform_transition,
)

Then run

bazel build //:ok

This crashes with the following trace:

$ bazel build :ok
Starting local Bazel server and connecting to it...
Analyzing: target //:ok (1 packages loaded, 0 targets configured)
    currently loading: @@bazel_tools//tools
[0 / 1] checking cached actions
FATAL: bazel crashed due to an internal error. Printing stack trace:
java.lang.RuntimeException: Unrecoverable error while evaluating node 'ConfiguredTargetKey{label=//:ok, config=BuildConfigurationKey[b357dd09923b37f48f631ab81341a90e6b4443fce0a2dddfde9d204ca537a54e]}' (requested by nodes 'BuildDriverKey of ActionLookupKey: ConfiguredTargetKey{label=//:ok, config=BuildConfigurationKey[b357dd09923b37f48f631ab81341a90e6b4443fce0a2dddfde9d204ca537a54e]}')
    at com.google.devtools.build.skyframe.AbstractParallelEvaluator$Evaluate.run(AbstractParallelEvaluator.java:550)
    at com.google.devtools.build.lib.concurrent.AbstractQueueVisitor$WrappedRunnable.run(AbstractQueueVisitor.java:414)
    at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinPool.scan(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source)
Caused by: net.starlark.java.eval.Starlark$UncheckedEvalException: NullPointerException thrown during Starlark evaluation (ANALYSIS)
    at <starlark>.print(<builtin>:0)
    at <starlark>._platform_transition_impl(/mnt/sdc/android/tmpdir2/a.bzl:4)
Caused by: java.lang.NullPointerException: null value in entry: //conditions:default=null
    at com.google.common.collect.CollectPreconditions.checkEntryNotNull(CollectPreconditions.java:33)
    at com.google.common.collect.SingletonImmutableBiMap.<init>(SingletonImmutableBiMap.java:43)
    at com.google.common.collect.ImmutableBiMap.of(ImmutableBiMap.java:83)
    at com.google.common.collect.ImmutableMap.of(ImmutableMap.java:130)
    at com.google.common.collect.ImmutableMap.copyOf(ImmutableMap.java:709)
    at com.google.common.collect.ImmutableMap.copyOf(ImmutableMap.java:688)
    at com.google.devtools.build.lib.packages.SelectorValue.<init>(SelectorValue.java:56)
    at com.google.devtools.build.lib.packages.BuildType$SelectorList.repr(BuildType.java:637)
    at net.starlark.java.eval.StarlarkValue.str(StarlarkValue.java:45)
    at net.starlark.java.eval.StarlarkValue.debugPrint(StarlarkValue.java:61)
    at net.starlark.java.eval.Printer.debugPrint(Printer.java:119)
    at net.starlark.java.eval.MethodLibrary.print(MethodLibrary.java:885)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
    at java.base/java.lang.reflect.Method.invoke(Unknown Source)
    at net.starlark.java.eval.MethodDescriptor.call(MethodDescriptor.java:178)
    at net.starlark.java.eval.BuiltinFunction.fastcall(BuiltinFunction.java:78)
    at net.starlark.java.eval.Starlark.fastcall(Starlark.java:806)
    at net.starlark.java.eval.Eval.evalCall(Eval.java:682)
    at net.starlark.java.eval.Eval.eval(Eval.java:497)
    at net.starlark.java.eval.Eval.exec(Eval.java:271)
    at net.starlark.java.eval.Eval.execStatements(Eval.java:82)
    at net.starlark.java.eval.Eval.execFunctionBody(Eval.java:66)
    at net.starlark.java.eval.StarlarkFunction.fastcall(StarlarkFunction.java:179)
    at net.starlark.java.eval.Starlark.fastcall(Starlark.java:806)
    at com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition$RegularTransition.evaluate(StarlarkDefinedConfigTransition.java:563)
    at com.google.devtools.build.lib.analysis.starlark.FunctionTransitionUtil.applyAndValidate(FunctionTransitionUtil.java:99)
    at com.google.devtools.build.lib.analysis.starlark.StarlarkRuleTransitionProvider$FunctionPatchTransition.patch(StarlarkRuleTransitionProvider.java:201)
    at com.google.devtools.build.lib.analysis.config.transitions.PatchTransition.apply(PatchTransition.java:75)
    at com.google.devtools.build.lib.analysis.config.transitions.ComposingTransition.apply(ComposingTransition.java:69)
    at com.google.devtools.build.lib.analysis.config.StarlarkTransitionCache.computeIfAbsent(StarlarkTransitionCache.java:79)
    at com.google.devtools.build.lib.analysis.producers.TransitionApplier.applyStarlarkTransition(TransitionApplier.java:126)
    at com.google.devtools.build.lib.analysis.producers.TransitionApplier.step(TransitionApplier.java:96)
    at com.google.devtools.build.skyframe.state.TaskTreeNode.run(TaskTreeNode.java:94)
    at com.google.devtools.build.skyframe.state.Driver.drive(Driver.java:87)
    at com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.computeTargetAndConfiguration(ConfiguredTargetFunction.java:479)
    at com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.compute(ConfiguredTargetFunction.java:249)
    at com.google.devtools.build.skyframe.AbstractParallelEvaluator$Evaluate.run(AbstractParallelEvaluator.java:461)
    at com.google.devtools.build.lib.concurrent.AbstractQueueVisitor$WrappedRunnable.run(AbstractQueueVisitor.java:414)
    at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinPool.scan(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(Unknown Source)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source)

I expect it to not crash and print None.

If I change BUILD.bazel, LINE A to the following:

        "//conditions:default": "@platforms//host",

Then it will print a select expression, instead of the evaluated result:

$ bazel build :ok
Starting local Bazel server and connecting to it...
DEBUG: /mnt/sdc/android/tmpdir2/a.bzl:4:10: select({Label("//conditions:default"): Label("@@platforms//host:host")})
DEBUG: /mnt/sdc/android/tmpdir2/a.bzl:4:10: select({Label("//conditions:default"): Label("@@platforms//host:host")})
DEBUG: /mnt/sdc/android/tmpdir2/a.bzl:4:10: select({Label("//conditions:default"): Label("@@platforms//host:host")})
INFO: Analyzed target //:ok (6 packages loaded, 9 targets configured).
INFO: Found 1 target...
Target //:ok up-to-date (nothing to build)
INFO: Elapsed time: 2.173s, Critical Path: 0.02s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action

I expect it to print a simple Label("@@platforms//host:host") instead of the un-evaluated expression.

If it were an outgoing-edge transition, then it properly prints None and Label("@@platforms//host:host") respectively. This bug only happens on the incoming edge transition.

transitioned_files = rule(
    implementation = _transitioned_files_impl,
    attrs = {
        "src": attr.label(
            cfg = platform_transition,
        ),
        "target_platform": attr.label(),
    },
)
$ bazel build :ok
DEBUG: /mnt/sdc/android/tmpdir2/a.bzl:4:10: @@platforms//host:host

Which operating system are you running Bazel on?

Linux

What is the output of bazel info release?

release 7.3.0

If bazel info release returns development version or (@non-git), tell us how you built Bazel.

No response

What's the output of git remote get-url origin; git rev-parse HEAD ?

No response

If this is a regression, please try to identify the Bazel commit where the bug was introduced with bazelisk --bisect.

No response

Have you found anything relevant by searching the web?

No response

Any other information, logs, or outputs that you want to share?

This is probably a WAI, but I don't see any documentation describing the behavior in https://bazel.build/extending/config. Perhaps a documentation update would be sufficient?

Also, I wouldn't expect a crash, but a proper error message that this is not a supported use case.

jacky8hyf commented 2 months ago

I forgot to put in the "category"; it should be "configurability".

fmeum commented 2 months ago

CC @aranguyen