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");