Throw an EvalException when a rule returns a non-exported provider
Fixes https://github.com/bazelbuild/bazel/issues/8541
RELNOTES: None.
PiperOrigin-RevId: 252715957
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
index e7e0586..842e3e0 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
@@ -321,7 +321,7 @@
InfoInterface info = (InfoInterface) target;
// Use the creation location of this struct as a better reference in error messages
loc = info.getCreationLoc();
- if (info.getProvider().getKey().equals(StructProvider.STRUCT.getKey())) {
+ if (getProviderKey(loc, info).equals(StructProvider.STRUCT.getKey())) {
if (context.getSkylarkSemantics().incompatibleDisallowStructProviderSyntax()) {
throw new EvalException(
@@ -345,7 +345,7 @@
InfoInterface.class,
loc,
"The value of 'providers' should be a sequence of declared providers");
- Provider.Key providerKey = declaredProvider.getProvider().getKey();
+ Provider.Key providerKey = getProviderKey(loc, declaredProvider);
if (declaredProviders.put(providerKey, declaredProvider) != null) {
context
.getRuleContext()
@@ -354,7 +354,7 @@
}
}
} else {
- Provider.Key providerKey = info.getProvider().getKey();
+ Provider.Key providerKey = getProviderKey(loc, info);
// Single declared provider
declaredProviders.put(providerKey, info);
}
@@ -368,7 +368,7 @@
loc,
"A return value of a rule implementation function should be "
+ "a sequence of declared providers");
- Provider.Key providerKey = declaredProvider.getProvider().getKey();
+ Provider.Key providerKey = getProviderKey(loc, declaredProvider);
if (declaredProviders.put(providerKey, declaredProvider) != null) {
context
.getRuleContext()
@@ -380,10 +380,7 @@
boolean defaultProviderProvidedExplicitly = false;
for (InfoInterface declaredProvider : declaredProviders.values()) {
- if (declaredProvider
- .getProvider()
- .getKey()
- .equals(DefaultInfo.PROVIDER.getKey())) {
+ if (getProviderKey(loc, declaredProvider).equals(DefaultInfo.PROVIDER.getKey())) {
parseDefaultProviderFields((DefaultInfo) declaredProvider, context, builder);
defaultProviderProvidedExplicitly = true;
} else {
@@ -421,6 +418,26 @@
}
}
+ /**
+ * Returns the provider key from an info object.
+ *
+ * @throws EvalException if the provider for this info object has not been exported, which can
+ * occur if the provider was declared in a non-global scope (for example a rule implementation
+ * function)
+ */
+ private static Provider.Key getProviderKey(Location loc, InfoInterface infoObject)
+ throws EvalException {
+ if (!infoObject.getProvider().isExported()) {
+ throw new EvalException(
+ loc,
+ "cannot return a non-exported provider instance from a "
+ + "rule implementation function. provider defined at "
+ + infoObject.getProvider().getLocation()
+ + " must be defined outside of a function scope.");
+ }
+ return infoObject.getProvider().getKey();
+ }
+
private static boolean isNativeDeclaredProviderWithLegacySkylarkName(Object value) {
if (!(value instanceof InfoInterface)) {
return false;
@@ -443,10 +460,7 @@
Location loc = provider.getCreationLoc();
- if (provider
- .getProvider()
- .getKey()
- .equals(DefaultInfo.PROVIDER.getKey())) {
+ if (getProviderKey(loc, provider).equals(DefaultInfo.PROVIDER.getKey())) {
DefaultInfo defaultInfo = (DefaultInfo) provider;
files = defaultInfo.getFiles();
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
index 940e224..78ab2ed 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
@@ -1803,6 +1803,28 @@
}
@Test
+ public void testReturnNonExportedProvider() throws Exception {
+ scratch.file(
+ "test/my_rule.bzl",
+ "def _rule_impl(ctx):",
+ " foo_provider = provider()",
+ " foo = foo_provider()",
+ " return [foo]",
+ "",
+ "my_rule = rule(",
+ " implementation = _rule_impl,",
+ ")");
+ scratch.file("test/BUILD", "load(':my_rule.bzl', 'my_rule')", "my_rule(name = 'my_rule')");
+
+ AssertionError expected =
+ assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:my_rule"));
+ assertThat(expected)
+ .hasMessageThat()
+ .contains(
+ "cannot return a non-exported provider instance from a rule implementation function.");
+ }
+
+ @Test
public void testFilesForFileConfiguredTarget() throws Exception {
Object result =
evalRuleContextCode(createRuleContext("//foo:bar"), "ruleContext.attr.srcs[0].files");