[6.3.0] Fix absolute file paths showing up in lockfiles (#18993)

* Redact absolute root module file path in `MODULE.bazel.lock`

The absolute path of the root module file in Starlark `Location`s is replaced with a constant when writing to the lock file.

Work towards #18936

Closes #18949.

PiperOrigin-RevId: 549118781
Change-Id: Ie689f7b5edf92296772c605845d694d872074214

* Use a label as the location of the MODULE.bazel file of non-registry overrides

Fixes https://github.com/bazelbuild/bazel/issues/18936

PiperOrigin-RevId: 549310245
Change-Id: I852570fbc81c1592c2fc0b3848d4b58e1c9ffb7d
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileFunction.java
index 41cff39..1451f43 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileFunction.java
@@ -15,7 +15,6 @@
 
 package com.google.devtools.build.lib.bazel.bzlmod;
 
-import static com.google.devtools.build.lib.bazel.bzlmod.GsonTypeAdapterUtil.LOCKFILE_GSON;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.collect.ImmutableList;
@@ -93,7 +92,13 @@
     BazelLockFileValue bazelLockFileValue;
     try {
       String json = FileSystemUtils.readContent(lockfilePath.asPath(), UTF_8);
-      bazelLockFileValue = LOCKFILE_GSON.fromJson(json, BazelLockFileValue.class);
+      bazelLockFileValue =
+          GsonTypeAdapterUtil.createLockFileGson(
+                  lockfilePath
+                      .asPath()
+                      .getParentDirectory()
+                      .getRelative(LabelConstants.MODULE_DOT_BAZEL_FILE_NAME))
+              .fromJson(json, BazelLockFileValue.class);
     } catch (FileNotFoundException e) {
       bazelLockFileValue = EMPTY_LOCKFILE;
     }
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileModule.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileModule.java
index 5bd3b40..fd2bfdd 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileModule.java
@@ -14,7 +14,6 @@
 
 package com.google.devtools.build.lib.bazel.bzlmod;
 
-import static com.google.devtools.build.lib.bazel.bzlmod.GsonTypeAdapterUtil.LOCKFILE_GSON;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.collect.ImmutableMap;
@@ -136,7 +135,14 @@
   public static void updateLockfile(RootedPath lockfilePath, BazelLockFileValue updatedLockfile) {
     try {
       FileSystemUtils.writeContent(
-          lockfilePath.asPath(), UTF_8, LOCKFILE_GSON.toJson(updatedLockfile));
+          lockfilePath.asPath(),
+          UTF_8,
+          GsonTypeAdapterUtil.createLockFileGson(
+                  lockfilePath
+                      .asPath()
+                      .getParentDirectory()
+                      .getRelative(LabelConstants.MODULE_DOT_BAZEL_FILE_NAME))
+              .toJson(updatedLockfile));
     } catch (IOException e) {
       logger.atSevere().withCause(e).log(
           "Error while updating MODULE.bazel.lock file: %s", e.getMessage());
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java
index c558e80..313471c8 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java
@@ -20,11 +20,13 @@
 import static com.google.devtools.build.lib.bazel.bzlmod.DelegateTypeAdapterFactory.IMMUTABLE_MAP;
 import static com.google.devtools.build.lib.bazel.bzlmod.DelegateTypeAdapterFactory.IMMUTABLE_SET;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Splitter;
 import com.google.devtools.build.lib.bazel.bzlmod.Version.ParseException;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
+import com.google.devtools.build.lib.vfs.Path;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonParseException;
@@ -42,6 +44,7 @@
 import java.util.List;
 import java.util.Optional;
 import javax.annotation.Nullable;
+import net.starlark.java.syntax.Location;
 
 /**
  * Utility class to hold type adapters and helper methods to get gson registered with type adapters
@@ -188,24 +191,102 @@
     }
   }
 
-  public static final Gson LOCKFILE_GSON =
-      new GsonBuilder()
-          .setPrettyPrinting()
-          .disableHtmlEscaping()
-          .enableComplexMapKeySerialization()
-          .registerTypeAdapterFactory(GenerateTypeAdapter.FACTORY)
-          .registerTypeAdapterFactory(DICT)
-          .registerTypeAdapterFactory(IMMUTABLE_MAP)
-          .registerTypeAdapterFactory(IMMUTABLE_LIST)
-          .registerTypeAdapterFactory(IMMUTABLE_BIMAP)
-          .registerTypeAdapterFactory(IMMUTABLE_SET)
-          .registerTypeAdapterFactory(OPTIONAL)
-          .registerTypeAdapter(Version.class, VERSION_TYPE_ADAPTER)
-          .registerTypeAdapter(ModuleKey.class, MODULE_KEY_TYPE_ADAPTER)
-          .registerTypeAdapter(ModuleExtensionId.class, MODULE_EXTENSION_ID_TYPE_ADAPTER)
-          .registerTypeAdapter(AttributeValues.class, new AttributeValuesAdapter())
-          .registerTypeAdapter(byte[].class, BYTE_ARRAY_TYPE_ADAPTER)
-          .create();
+  /**
+   * A variant of {@link Location} that converts the absolute path to the root module file to a
+   * constant and back.
+   */
+  // protected only for @AutoValue
+  @GenerateTypeAdapter
+  @AutoValue
+  protected abstract static class RootModuleFileEscapingLocation {
+    // This marker string is neither a valid absolute path nor a valid URL and thus cannot conflict
+    // with any real module file location.
+    private static final String ROOT_MODULE_FILE_LABEL = "@@//:MODULE.bazel";
+
+    public abstract String file();
+
+    public abstract int line();
+
+    public abstract int column();
+
+    public Location toLocation(String moduleFilePath) {
+      String file;
+      if (file().equals(ROOT_MODULE_FILE_LABEL)) {
+        file = moduleFilePath;
+      } else {
+        file = file();
+      }
+      return Location.fromFileLineColumn(file, line(), column());
+    }
+
+    public static RootModuleFileEscapingLocation fromLocation(
+        Location location, String moduleFilePath) {
+      String file;
+      if (location.file().equals(moduleFilePath)) {
+        file = ROOT_MODULE_FILE_LABEL;
+      } else {
+        file = location.file();
+      }
+      return new AutoValue_GsonTypeAdapterUtil_RootModuleFileEscapingLocation(
+          file, location.line(), location.column());
+    }
+  }
+
+  private static final class LocationTypeAdapterFactory implements TypeAdapterFactory {
+
+    private final String moduleFilePath;
+
+    public LocationTypeAdapterFactory(Path moduleFilePath) {
+      this.moduleFilePath = moduleFilePath.getPathString();
+    }
+
+    @Nullable
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+      if (typeToken.getRawType() != Location.class) {
+        return null;
+      }
+      TypeAdapter<RootModuleFileEscapingLocation> relativizedLocationTypeAdapter =
+          gson.getAdapter(RootModuleFileEscapingLocation.class);
+      return (TypeAdapter<T>)
+          new TypeAdapter<Location>() {
+
+            @Override
+            public void write(JsonWriter jsonWriter, Location location) throws IOException {
+              relativizedLocationTypeAdapter.write(
+                  jsonWriter,
+                  RootModuleFileEscapingLocation.fromLocation(location, moduleFilePath));
+            }
+
+            @Override
+            public Location read(JsonReader jsonReader) throws IOException {
+              return relativizedLocationTypeAdapter.read(jsonReader).toLocation(moduleFilePath);
+            }
+          };
+    }
+  }
+
+  public static Gson createLockFileGson(Path moduleFilePath) {
+    return new GsonBuilder()
+        .setPrettyPrinting()
+        .disableHtmlEscaping()
+        .enableComplexMapKeySerialization()
+        .registerTypeAdapterFactory(GenerateTypeAdapter.FACTORY)
+        .registerTypeAdapterFactory(DICT)
+        .registerTypeAdapterFactory(IMMUTABLE_MAP)
+        .registerTypeAdapterFactory(IMMUTABLE_LIST)
+        .registerTypeAdapterFactory(IMMUTABLE_BIMAP)
+        .registerTypeAdapterFactory(IMMUTABLE_SET)
+        .registerTypeAdapterFactory(OPTIONAL)
+        .registerTypeAdapterFactory(new LocationTypeAdapterFactory(moduleFilePath))
+        .registerTypeAdapter(Version.class, VERSION_TYPE_ADAPTER)
+        .registerTypeAdapter(ModuleKey.class, MODULE_KEY_TYPE_ADAPTER)
+        .registerTypeAdapter(ModuleExtensionId.class, MODULE_EXTENSION_ID_TYPE_ADAPTER)
+        .registerTypeAdapter(AttributeValues.class, new AttributeValuesAdapter())
+        .registerTypeAdapter(byte[].class, BYTE_ARRAY_TYPE_ADAPTER)
+        .create();
+  }
 
   private GsonTypeAdapterUtil() {}
 }
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java
index 89792cb..3d62263 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java
@@ -24,7 +24,9 @@
 import com.google.devtools.build.lib.actions.FileValue;
 import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.NonRootModuleFileValue;
 import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileValue.RootModuleFileValue;
+import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.LabelConstants;
+import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.packages.StarlarkExportable;
@@ -37,6 +39,7 @@
 import com.google.devtools.build.lib.util.Fingerprint;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.Root;
 import com.google.devtools.build.lib.vfs.RootedPath;
 import com.google.devtools.build.skyframe.SkyFunction;
@@ -199,11 +202,11 @@
     if (env.getValue(FileValue.key(moduleFilePath)) == null) {
       return null;
     }
-    ModuleFile moduleFile = readModuleFile(moduleFilePath.asPath());
-    String moduleFileHash = new Fingerprint().addBytes(moduleFile.getContent()).hexDigestAndReset();
+    byte[] moduleFileContents = readModuleFile(moduleFilePath.asPath());
+    String moduleFileHash = new Fingerprint().addBytes(moduleFileContents).hexDigestAndReset();
     ModuleFileGlobals moduleFileGlobals =
         execModuleFile(
-            moduleFile,
+            ModuleFile.create(moduleFileContents, moduleFilePath.asPath().toString()),
             /* registry= */ null,
             ModuleKey.ROOT,
             /* ignoreDevDeps= */ Objects.requireNonNull(IGNORE_DEV_DEPS.get(env)),
@@ -335,8 +338,15 @@
       if (env.getValue(FileValue.key(moduleFilePath)) == null) {
         return null;
       }
+      Label moduleFileLabel =
+          Label.createUnvalidated(
+              PackageIdentifier.create(key.getCanonicalRepoName(), PathFragment.EMPTY_FRAGMENT),
+              LabelConstants.MODULE_DOT_BAZEL_FILE_NAME.getBaseName());
       GetModuleFileResult result = new GetModuleFileResult();
-      result.moduleFile = readModuleFile(moduleFilePath.asPath());
+      result.moduleFile =
+          ModuleFile.create(
+              readModuleFile(moduleFilePath.asPath()),
+              moduleFileLabel.getUnambiguousCanonicalForm());
       return result;
     }
 
@@ -392,10 +402,9 @@
     throw errorf(Code.MODULE_NOT_FOUND, "module not found in registries: %s", key);
   }
 
-  private static ModuleFile readModuleFile(Path path) throws ModuleFileFunctionException {
+  private static byte[] readModuleFile(Path path) throws ModuleFileFunctionException {
     try {
-      return ModuleFile.create(
-          FileSystemUtils.readWithKnownFileSize(path, path.getFileSize()), path.getPathString());
+      return FileSystemUtils.readWithKnownFileSize(path, path.getFileSize());
     } catch (IOException e) {
       throw errorf(Code.MODULE_NOT_FOUND, "MODULE.bazel expected but not found at %s", path);
     }
diff --git a/src/test/py/bazel/bzlmod/bazel_lockfile_test.py b/src/test/py/bazel/bzlmod/bazel_lockfile_test.py
index 03381c1..1bbed92 100644
--- a/src/test/py/bazel/bzlmod/bazel_lockfile_test.py
+++ b/src/test/py/bazel/bzlmod/bazel_lockfile_test.py
@@ -505,6 +505,70 @@
       lockfile = json.loads(f.read().strip())
       self.assertEqual(len(lockfile['moduleExtensions']), 0)
 
+  def testNoAbsoluteRootModuleFilePath(self):
+    self.ScratchFile(
+        'MODULE.bazel',
+        [
+            'ext = use_extension("extension.bzl", "ext")',
+            'ext.dep(generate = True)',
+            'use_repo(ext, ext_hello = "hello")',
+            'other_ext = use_extension("extension.bzl", "other_ext")',
+            'other_ext.dep(generate = False)',
+            'use_repo(other_ext, other_ext_hello = "hello")',
+        ],
+    )
+    self.ScratchFile('BUILD.bazel')
+    self.ScratchFile(
+        'extension.bzl',
+        [
+            'def _repo_rule_impl(ctx):',
+            '    ctx.file("WORKSPACE")',
+            '    ctx.file("BUILD", "filegroup(name=\'lala\')")',
+            '',
+            'repo_rule = repository_rule(implementation=_repo_rule_impl)',
+            '',
+            'def _module_ext_impl(ctx):',
+            '    for mod in ctx.modules:',
+            '        for dep in mod.tags.dep:',
+            '            if dep.generate:',
+            '                repo_rule(name="hello")',
+            '',
+            '_dep = tag_class(attrs={"generate": attr.bool()})',
+            'ext = module_extension(',
+            '    implementation=_module_ext_impl,',
+            '    tag_classes={"dep": _dep},',
+            ')',
+            'other_ext = module_extension(',
+            '    implementation=_module_ext_impl,',
+            '    tag_classes={"dep": _dep},',
+            ')',
+        ],
+    )
+
+    # Paths to module files in error message always use forward slashes as
+    # separators, even on Windows.
+    module_file_path = self.Path('MODULE.bazel').replace('\\', '/')
+
+    self.RunBazel(['build', '--nobuild', '@ext_hello//:all'])
+    with open(self.Path('MODULE.bazel.lock'), 'r') as f:
+      self.assertNotIn(module_file_path, f.read())
+
+    self.RunBazel(['shutdown'])
+    exit_code, _, stderr = self.RunBazel(
+        ['build', '--nobuild', '@other_ext_hello//:all'], allow_failure=True
+    )
+    self.AssertNotExitCode(exit_code, 0, stderr)
+    self.assertIn(
+        (
+            'ERROR: module extension "other_ext" from "//:extension.bzl" does '
+            'not generate repository "hello", yet it is imported as '
+            '"other_ext_hello" in the usage at '
+            + module_file_path
+            + ':4:26'
+        ),
+        stderr,
+    )
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/src/test/py/bazel/bzlmod/bazel_module_test.py b/src/test/py/bazel/bzlmod/bazel_module_test.py
index 682e58a..97d1455 100644
--- a/src/test/py/bazel/bzlmod/bazel_module_test.py
+++ b/src/test/py/bazel/bzlmod/bazel_module_test.py
@@ -484,6 +484,258 @@
     _, stdout, _ = self.RunBazel(['run', '//:main'], allow_failure=False)
     self.assertIn('main function => aaa@1.2', stdout)
 
+  def testNativeModuleNameAndVersion(self):
+    self.main_registry.setModuleBasePath('projects')
+    projects_dir = self.main_registry.projects
+
+    self.ScratchFile(
+        'MODULE.bazel',
+        [
+            'module(name="root",version="0.1")',
+            'bazel_dep(name="foo",version="1.0")',
+            'report_ext = use_extension("@foo//:ext.bzl", "report_ext")',
+            'use_repo(report_ext, "report_repo")',
+            'bazel_dep(name="bar")',
+            'local_path_override(module_name="bar",path="bar")',
+        ],
+    )
+    self.ScratchFile('WORKSPACE')
+    self.ScratchFile(
+        'WORKSPACE.bzlmod', ['local_repository(name="quux",path="quux")']
+    )
+    self.ScratchFile(
+        'BUILD',
+        [
+            'load("@foo//:report.bzl", "report")',
+            'report()',
+        ],
+    )
+    # foo: a repo defined by a normal Bazel module. Also hosts the extension
+    #      `report_ext` which generates a repo `report_repo`.
+    self.main_registry.createLocalPathModule('foo', '1.0', 'foo')
+    projects_dir.joinpath('foo').mkdir(exist_ok=True)
+    scratchFile(projects_dir.joinpath('foo', 'WORKSPACE'))
+    scratchFile(
+        projects_dir.joinpath('foo', 'BUILD'),
+        [
+            'load(":report.bzl", "report")',
+            'report()',
+        ],
+    )
+    scratchFile(
+        projects_dir.joinpath('foo', 'report.bzl'),
+        [
+            'def report():',
+            '  repo = native.repository_name()',
+            '  name = str(native.module_name())',
+            '  version = str(native.module_version())',
+            '  print("@" + repo + " reporting in: " + name + "@" + version)',
+            '  native.filegroup(name="a")',
+        ],
+    )
+    scratchFile(
+        projects_dir.joinpath('foo', 'ext.bzl'),
+        [
+            'def _report_repo(rctx):',
+            '  rctx.file("BUILD",',
+            '    "load(\\"@foo//:report.bzl\\", \\"report\\")\\n" +',
+            '    "report()")',
+            'report_repo = repository_rule(_report_repo)',
+            'report_ext = module_extension(',
+            '  lambda mctx: report_repo(name="report_repo"))',
+        ],
+    )
+    # bar: a repo defined by a Bazel module with a non-registry override
+    self.ScratchFile('bar/WORKSPACE')
+    self.ScratchFile(
+        'bar/MODULE.bazel',
+        [
+            'module(name="bar", version="2.0")',
+            'bazel_dep(name="foo",version="1.0")',
+        ],
+    )
+    self.ScratchFile(
+        'bar/BUILD',
+        [
+            'load("@foo//:report.bzl", "report")',
+            'report()',
+        ],
+    )
+    # quux: a repo defined by WORKSPACE
+    self.ScratchFile('quux/WORKSPACE')
+    self.ScratchFile(
+        'quux/BUILD',
+        [
+            'load("@foo//:report.bzl", "report")',
+            'report()',
+        ],
+    )
+
+    _, _, stderr = self.RunBazel(
+        [
+            'build',
+            ':a',
+            '@foo//:a',
+            '@report_repo//:a',
+            '@bar//:a',
+            '@quux//:a',
+        ],
+    )
+    stderr = '\n'.join(stderr)
+    self.assertIn('@@ reporting in: root@0.1', stderr)
+    self.assertIn('@@foo~1.0 reporting in: foo@1.0', stderr)
+    self.assertIn(
+        '@@foo~1.0~report_ext~report_repo reporting in: foo@1.0', stderr
+    )
+    self.assertIn('@@bar~override reporting in: bar@2.0', stderr)
+    self.assertIn('@@quux reporting in: None@None', stderr)
+
+  def testWorkspaceToolchainRegistrationWithPlatformsConstraint(self):
+    """Regression test for https://github.com/bazelbuild/bazel/issues/17289."""
+    self.ScratchFile('MODULE.bazel')
+    self.ScratchFile(
+        'WORKSPACE', ['register_toolchains("//:my_toolchain_toolchain")']
+    )
+    os.remove(self.Path('WORKSPACE.bzlmod'))
+
+    self.ScratchFile(
+        'BUILD.bazel',
+        [
+            'load(":defs.bzl", "get_host_os", "my_consumer", "my_toolchain")',
+            'toolchain_type(name = "my_toolchain_type")',
+            'my_toolchain(',
+            '    name = "my_toolchain",',
+            '    my_value = "Hello, Bzlmod!",',
+            ')',
+            'toolchain(',
+            '    name = "my_toolchain_toolchain",',
+            '    toolchain = ":my_toolchain",',
+            '    toolchain_type = ":my_toolchain_type",',
+            '    target_compatible_with = [',
+            '        "@platforms//os:" + get_host_os(),',
+            '    ],',
+            ')',
+            'my_consumer(',
+            '    name = "my_consumer",',
+            ')',
+        ],
+    )
+
+    self.ScratchFile(
+        'defs.bzl',
+        [
+            (
+                'load("@local_config_platform//:constraints.bzl",'
+                ' "HOST_CONSTRAINTS")'
+            ),
+            'def _my_toolchain_impl(ctx):',
+            '    return [',
+            '        platform_common.ToolchainInfo(',
+            '            my_value = ctx.attr.my_value,',
+            '        ),',
+            '    ]',
+            'my_toolchain = rule(',
+            '    implementation = _my_toolchain_impl,',
+            '    attrs = {',
+            '        "my_value": attr.string(),',
+            '    },',
+            ')',
+            'def _my_consumer(ctx):',
+            '    my_toolchain_info = ctx.toolchains["//:my_toolchain_type"]',
+            '    out = ctx.actions.declare_file(ctx.attr.name)',
+            (
+                '    ctx.actions.write(out, "my_value ='
+                ' {}".format(my_toolchain_info.my_value))'
+            ),
+            '    return [DefaultInfo(files = depset([out]))]',
+            'my_consumer = rule(',
+            '    implementation = _my_consumer,',
+            '    attrs = {},',
+            '    toolchains = ["//:my_toolchain_type"],',
+            ')',
+            'def get_host_os():',
+            '    for constraint in HOST_CONSTRAINTS:',
+            '        if constraint.startswith("@platforms//os:"):',
+            '            return constraint.removeprefix("@platforms//os:")',
+        ],
+    )
+
+    self.RunBazel([
+        'build',
+        '//:my_consumer',
+        '--toolchain_resolution_debug=//:my_toolchain_type',
+    ])
+    with open(self.Path('bazel-bin/my_consumer'), 'r') as f:
+      self.assertEqual(f.read().strip(), 'my_value = Hello, Bzlmod!')
+
+  def testModuleExtensionWithRuleError(self):
+    self.ScratchFile(
+        'MODULE.bazel',
+        [
+            'ext = use_extension("extensions.bzl", "ext")',
+            'use_repo(ext, "ext")',
+        ],
+    )
+    self.ScratchFile('BUILD')
+    self.ScratchFile(
+        'extensions.bzl',
+        [
+            'def _rule_impl(ctx):',
+            '  print("RULE CALLED")',
+            'init_rule = rule(_rule_impl)',
+            'def ext_impl(module_ctx):',
+            '  init_rule()',
+            'ext = module_extension(implementation = ext_impl,)',
+        ],
+    )
+    exit_code, _, stderr = self.RunBazel(
+        ['build', '--nobuild', '@ext//:all'],
+        allow_failure=True,
+    )
+    self.AssertExitCode(exit_code, 48, stderr)
+    self.assertIn(
+        'Error in init_rule: A rule can only be instantiated in a BUILD file, '
+        'or a macro invoked from a BUILD file',
+        stderr,
+    )
+
+  def testLocationRoot(self):
+    """Tests that the reported location of the MODULE.bazel file of the root module is as expected."""
+    self.ScratchFile('MODULE.bazel', ['wat'])
+    _, _, stderr = self.RunBazel(['build', '@what'], allow_failure=True)
+    self.assertIn(
+        'ERROR: ' + self.Path('MODULE.bazel').replace('\\', '/'),
+        '\n'.join(stderr).replace('\\', '/'),
+    )
+
+  def testLocationRegistry(self):
+    """Tests that the reported location of the MODULE.bazel file of a module from a registry is as expected."""
+    self.ScratchFile('MODULE.bazel', ['bazel_dep(name="hello",version="1.0")'])
+    self.main_registry.createCcModule(
+        'hello', '1.0', extra_module_file_contents=['wat']
+    )
+    _, _, stderr = self.RunBazel(['build', '@what'], allow_failure=True)
+    self.assertIn(
+        'ERROR: '
+        + self.main_registry.getURL()
+        + '/modules/hello/1.0/MODULE.bazel',
+        '\n'.join(stderr),
+    )
+
+  def testLocationNonRegistry(self):
+    """Tests that the reported location of the MODULE.bazel file of a module with a non-registry override is as expected."""
+    self.ScratchFile(
+        'MODULE.bazel',
+        [
+            'bazel_dep(name="hello")',
+            'local_path_override(module_name="hello",path="hello")',
+        ],
+    )
+    self.ScratchFile('hello/MODULE.bazel', ['wat'])
+    self.ScratchFile('hello/WORKSPACE.bazel')
+    _, _, stderr = self.RunBazel(['build', '@what'], allow_failure=True)
+    self.assertIn('ERROR: @@hello~override//:MODULE.bazel', '\n'.join(stderr))
+
 
 if __name__ == '__main__':
   unittest.main()