Fail `Label` call referencing invalid repository with strict visibility
With Bzlmod's enforcement of strict visibility of repositories based on repository mapping, a `Label` created from a repo-absolute (but not canonical) label string such as `@repo//foo:bar` will be in an error state if there is no repository visible from the calling .bzl module under the apparent name `repo`.
Before this commit, such instances of `Label` could be freely constructed and used from Starlark, even though some of their fields return nonsensical values (for example, `workspace_root` will be `"external"` for `Label("@//foo:bar")` called from a repo without visibility into the main repository).
This commit ensures that instead the `Label` call (and also analogous calls to `relative`) fail with an error such as:
```
Invalid label string '@//foo:bar': no repository visible as '@' from repository '@current_repo'
```
Fixes #16528
Closes #16530.
PiperOrigin-RevId: 483373288
Change-Id: I4831e179a2d0363aa2f27ca4a25d79a634103f8f
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
index 0c11cec..b43e9d7 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
@@ -1004,7 +1004,15 @@
BazelModuleContext moduleContext =
BazelModuleContext.of(Module.ofInnermostEnclosingStarlarkFunction(thread));
try {
- return Label.parseWithRepoContext(labelString, moduleContext.packageContext());
+ Label label = Label.parseWithRepoContext(labelString, moduleContext.packageContext());
+ if (!label.getRepository().isVisible()) {
+ throw Starlark.errorf(
+ "Invalid label string '%s': no repository visible as '@%s' from %s",
+ labelString,
+ label.getRepository().getName(),
+ label.getRepository().getOwnerRepoDisplayString());
+ }
+ return label;
} catch (LabelSyntaxException e) {
throw Starlark.errorf("Illegal absolute label syntax: %s", e.getMessage());
}
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
index 42507f2..7105afc 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
@@ -37,8 +37,10 @@
import net.starlark.java.annot.Param;
import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.annot.StarlarkMethod;
+import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Module;
import net.starlark.java.eval.Printer;
+import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.eval.StarlarkThread;
import net.starlark.java.eval.StarlarkValue;
@@ -502,10 +504,21 @@
@Param(name = "relName", doc = "The label that will be resolved relative to this one.")
},
useStarlarkThread = true)
- public Label getRelative(String relName, StarlarkThread thread) throws LabelSyntaxException {
- return getRelativeWithRemapping(
- relName,
- BazelModuleContext.of(Module.ofInnermostEnclosingStarlarkFunction(thread)).repoMapping());
+ public Label getRelative(String relName, StarlarkThread thread)
+ throws LabelSyntaxException, EvalException {
+ Label label =
+ getRelativeWithRemapping(
+ relName,
+ BazelModuleContext.of(Module.ofInnermostEnclosingStarlarkFunction(thread))
+ .repoMapping());
+ if (!label.getRepository().isVisible()) {
+ throw Starlark.errorf(
+ "Invalid label string '%s': no repository visible as '@%s' from %s",
+ relName,
+ label.getRepository().getName(),
+ label.getRepository().getOwnerRepoDisplayString());
+ }
+ return label;
}
/**
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java
index 6bc7542..6077c23 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java
@@ -121,6 +121,9 @@
@Before
public void setup() throws Exception {
workspaceRoot = scratch.dir("/ws");
+ String bazelToolsPath = "/ws/embedded_tools";
+ scratch.file(bazelToolsPath + "/MODULE.bazel", "module(name = 'bazel_tools')");
+ scratch.file(bazelToolsPath + "/WORKSPACE");
modulesRoot = scratch.dir("/modules");
differencer = new SequencedRecordingDifferencer();
evaluationContext =
@@ -178,7 +181,11 @@
externalFilesHelper))
.put(
SkyFunctions.MODULE_FILE,
- new ModuleFileFunction(registryFactory, workspaceRoot, ImmutableMap.of()))
+ new ModuleFileFunction(
+ registryFactory,
+ workspaceRoot,
+ // Required to load @_builtins.
+ ImmutableMap.of("bazel_tools", LocalPathOverride.create(bazelToolsPath))))
.put(SkyFunctions.PRECOMPUTED, new PrecomputedFunction())
.put(SkyFunctions.BZL_COMPILE, new BzlCompileFunction(packageFactory, hashFunction))
.put(
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
index 7bf6ac1..ce0d6ff 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
@@ -28,11 +28,16 @@
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
import com.google.devtools.build.lib.analysis.starlark.StarlarkAttrModule;
+import com.google.devtools.build.lib.analysis.starlark.StarlarkModules;
import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleClassFunctions.StarlarkRuleFunction;
import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleContext;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.analysis.util.TestAspects;
+import com.google.devtools.build.lib.cmdline.BazelModuleContext;
import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.PackageIdentifier;
+import com.google.devtools.build.lib.cmdline.RepositoryMapping;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.collect.nestedset.Depset;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventKind;
@@ -64,6 +69,7 @@
import com.google.devtools.build.lib.testutil.MoreAsserts;
import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
import com.google.devtools.build.lib.util.FileTypeSet;
+import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
@@ -75,8 +81,10 @@
import net.starlark.java.eval.StarlarkCallable;
import net.starlark.java.eval.StarlarkInt;
import net.starlark.java.eval.StarlarkList;
+import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.eval.Structure;
import net.starlark.java.eval.Tuple;
+import net.starlark.java.syntax.FileOptions;
import net.starlark.java.syntax.ParserInput;
import net.starlark.java.syntax.Program;
import net.starlark.java.syntax.StarlarkFile;
@@ -2828,4 +2836,56 @@
ev.assertContainsError(
"Error in analysis_test: 'name' cannot be set or overridden in 'attr_values'");
}
+
+ private Object eval(Module module, String... lines) throws Exception {
+ ParserInput input = ParserInput.fromLines(lines);
+ return Starlark.eval(input, FileOptions.DEFAULT, module, ev.getStarlarkThread());
+ }
+
+ @Test
+ public void testLabelWithStrictVisibility() throws Exception {
+ ImmutableMap.Builder<String, Object> predeclared = ImmutableMap.builder();
+ StarlarkModules.addPredeclared(predeclared);
+ RepositoryName currentRepo = RepositoryName.createUnvalidated("module~1.2.3");
+ RepositoryName otherRepo = RepositoryName.createUnvalidated("dep~4.5");
+ Label bzlLabel =
+ Label.create(
+ PackageIdentifier.create(currentRepo, PathFragment.create("lib")), "label.bzl");
+ Object clientData =
+ BazelModuleContext.create(
+ bzlLabel,
+ RepositoryMapping.create(
+ ImmutableMap.of("my_module", currentRepo, "dep", otherRepo), currentRepo),
+ "lib/label.bzl",
+ /*loads=*/ ImmutableMap.of(),
+ /*bzlTransitiveDigest=*/ new byte[0]);
+ Module module =
+ Module.withPredeclaredAndData(
+ StarlarkSemantics.DEFAULT, predeclared.buildOrThrow(), clientData);
+
+ assertThat(eval(module, "Label('//foo:bar').workspace_root"))
+ .isEqualTo("external/module~1.2.3");
+ assertThat(eval(module, "Label('@my_module//foo:bar').workspace_root"))
+ .isEqualTo("external/module~1.2.3");
+ assertThat(eval(module, "Label('@@module~1.2.3//foo:bar').workspace_root"))
+ .isEqualTo("external/module~1.2.3");
+ assertThat(eval(module, "Label('@dep//foo:bar').workspace_root")).isEqualTo("external/dep~4.5");
+ assertThat(eval(module, "Label('@@dep~4.5//foo:bar').workspace_root"))
+ .isEqualTo("external/dep~4.5");
+ assertThat(eval(module, "Label('@@//foo:bar').workspace_root")).isEqualTo("");
+
+ assertThat(assertThrows(EvalException.class, () -> eval(module, "Label('@//foo:bar')")))
+ .hasMessageThat()
+ .contains(
+ "Invalid label string '@//foo:bar': no repository visible as '@' from repository "
+ + "'@module~1.2.3'");
+ assertThat(
+ assertThrows(
+ EvalException.class,
+ () -> eval(module, "Label('@@//foo:bar').relative('@not_dep//foo:bar')")))
+ .hasMessageThat()
+ .contains(
+ "Invalid label string '@not_dep//foo:bar': no repository visible as '@not_dep' "
+ + "from repository '@module~1.2.3'");
+ }
}