blob: 12b9bce7a4bf0af83835fe41dbeccbcb9c3d8f3e [file]
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.packages;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.cmdline.BazelModuleContext;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.Collection;
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkThread;
/** Static utility methods pertaining to restricting Starlark method invocations */
// TODO(bazel-team): Maybe we can merge this utility class with some other existing allowlist
// helper? But it seems like a lot of existing allowlist machinery is geared toward allowlists on
// rule attributes rather than what .bzl you're in.
public final class BuiltinRestriction {
private static final String MAIN_REPO_NAME = RepositoryName.MAIN.getName();
/** An allowlist of packages that can access restricted APIs. */
public static final class Allowlist {
private static final Allowlist EMPTY = new Allowlist(ImmutableList.of());
private final ImmutableList<AllowlistEntry> entries;
private Allowlist(ImmutableList<AllowlistEntry> entries) {
this.entries = entries;
}
public static Allowlist create(Collection<AllowlistEntry> entries) {
return new Allowlist(ImmutableList.copyOf(entries));
}
public static Allowlist of(AllowlistEntry... entries) {
return new Allowlist(ImmutableList.copyOf(entries));
}
/**
* Returns an allowlist containing all entries in {@link #INTERNAL_STARLARK_API_ALLOWLIST} plus
* the given entries.
*/
public static Allowlist defaultPlus(AllowlistEntry... entries) {
return new Allowlist(
ImmutableList.<AllowlistEntry>builder()
.addAll(INTERNAL_STARLARK_API_ALLOWLIST.entries)
.add(entries)
.build());
}
private boolean allows(Label label, @Nullable String moduleRepoName) {
// Hot code path, avoid iterator garbage.
for (int i = 0; i < entries.size(); i++) {
if (entries.get(i).allows(label, moduleRepoName)) {
return true;
}
}
return false;
}
}
/**
* The "default" allowlist for restricted APIs added to aid the Java to Starlark migration.
*
* <p>Entries are roughly ordered by expected call frequency (most frequent first), since {@link
* Allowlist} checks them in order and stops at the first match.
*/
public static final Allowlist INTERNAL_STARLARK_API_ALLOWLIST =
Allowlist.of(
// Cc rules
mainRepoAllowlistEntry("third_party/bazel_rules/rules_cc"),
mainRepoAllowlistEntry("tools/build_defs/cc"),
externalRepoAllowlistEntry("rules_cc", ""),
// Java rules
mainRepoAllowlistEntry("third_party/bazel_rules/rules_java/java"),
externalRepoAllowlistEntry("rules_java", "java"),
// Proto rules
mainRepoAllowlistEntry("third_party/protobuf"),
externalRepoAllowlistEntry("protobuf", ""),
externalRepoAllowlistEntry("com_google_protobuf", ""),
// Rust rules
mainRepoAllowlistEntry("devtools/rust/toolchain/testing"),
mainRepoAllowlistEntry("third_party/bazel_rules/rules_rust/rust"),
mainRepoAllowlistEntry("third_party/crubit"),
externalRepoAllowlistEntry("rules_rust", "rust/private"),
// Go rules
mainRepoAllowlistEntry("tools/build_defs/go"),
// BuildInfo
mainRepoAllowlistEntry("tools/build_defs/build_info"),
externalRepoAllowlistEntry("bazel_tools", "tools/build_defs/build_info"),
// Android rules
mainRepoAllowlistEntry("bazel_internal/test_rules/cc"),
mainRepoAllowlistEntry("tools/build_defs/android"),
mainRepoAllowlistEntry("third_party/bazel_rules/rules_android"),
externalRepoAllowlistEntry("rules_android", ""),
externalRepoAllowlistEntry("build_bazel_rules_android", ""),
// Apple rules
mainRepoAllowlistEntry("third_party/apple_crosstool"),
mainRepoAllowlistEntry("third_party/cpptoolchains/portable_llvm/build_defs"),
mainRepoAllowlistEntry("third_party/bazel_rules/rules_apple"),
externalRepoAllowlistEntry("rules_apple", ""),
externalRepoAllowlistEntry("build_bazel_rules_apple", ""),
// CUDA rules
mainRepoAllowlistEntry("third_party/gpus/cuda"),
// Packaging rules
mainRepoAllowlistEntry("tools/build_defs/packaging"),
// Shell rules
externalRepoAllowlistEntry("rules_shell", ""),
// Testing
mainRepoAllowlistEntry("test"),
mainRepoAllowlistEntry("bazel_internal/test_rules"));
private BuiltinRestriction() {}
/**
* An entry in an {@link Allowlist}.
*
* @param repoName empty for main-repository-only entries; otherwise matches a module name for
* external repositories or the root module's {@code repo_name} for the main repository
*/
public record AllowlistEntry(String repoName, PathFragment packagePrefix) {
public AllowlistEntry {
checkNotNull(repoName);
checkNotNull(packagePrefix);
}
private boolean allows(Label label, @Nullable String moduleRepoName) {
return matchesRepository(label.getRepository(), moduleRepoName)
&& label.getPackageFragment().startsWith(packagePrefix);
}
private boolean matchesRepository(RepositoryName repository, @Nullable String moduleRepoName) {
if (repoName.isEmpty()) {
return repository.isMain();
}
if (repository.isMain()) {
// The root module may itself be an allowlisted ruleset. Its repo_name (which defaults to
// its module name) is the only non-empty apparent name that can refer to the main repo
// from the main repo's own mapping.
return repoName.equals(moduleRepoName);
}
if (repository.equals(RepositoryName.BAZEL_TOOLS)) {
return repoName.equals(RepositoryName.BAZEL_TOOLS.getName());
}
// Match any version of the module and repositories generated by its extensions.
return repository.getName().startsWith(repoName + "+");
}
}
/** Creates an {@link AllowlistEntry} in the main repository. */
public static AllowlistEntry mainRepoAllowlistEntry(String packagePrefix) {
return new AllowlistEntry(MAIN_REPO_NAME, PathFragment.create(packagePrefix));
}
/**
* Creates an {@link AllowlistEntry} matching a module name and a package prefix. When checking
* the main repository, the name instead matches the root module's {@code repo_name}, which
* defaults to its module name. The special name {@code bazel_tools} matches that repository.
*/
public static AllowlistEntry externalRepoAllowlistEntry(String repoName, String packagePrefix) {
checkArgument(!repoName.equals(MAIN_REPO_NAME));
return new AllowlistEntry(repoName, PathFragment.create(packagePrefix));
}
/**
* Throws {@code EvalException} if the innermost Starlark function in the given thread's call
* stack is not defined within the builtins repository.
*/
public static void failIfCalledOutsideBuiltins(StarlarkThread thread) throws EvalException {
failIfCalledOutsideAllowlist(thread, Allowlist.EMPTY);
}
/**
* Throws {@code EvalException} if the innermost Starlark function in the given thread's call
* stack is not defined within either 1) the builtins repository, or 2) a package or subpackage of
* an entry in the given allowlist.
*
* @throws NullPointerException if there is no currently executing Starlark function, or the
* innermost Starlark function's module is not a .bzl file
*/
public static void failIfCalledOutsideAllowlist(StarlarkThread thread, Allowlist allowlist)
throws EvalException {
failIfModuleOutsideAllowlist(BazelModuleContext.ofInnermostBzlOrThrow(thread), allowlist);
}
/**
* Throws {@code EvalException} if the call is made outside of the default allowlist or outside of
* builtins.
*
* @throws NullPointerException if there is no currently executing Starlark function, or the
* innermost Starlark function's module is not a .bzl file
*/
public static void failIfCalledOutsideDefaultAllowlist(StarlarkThread thread)
throws EvalException {
failIfCalledOutsideAllowlist(thread, INTERNAL_STARLARK_API_ALLOWLIST);
}
/**
* Throws {@code EvalException} if the given {@link BazelModuleContext} is not within either 1)
* the builtins repository, or 2) a package or subpackage of an entry in the given allowlist.
*/
public static void failIfModuleOutsideAllowlist(
BazelModuleContext moduleContext, Allowlist allowlist) throws EvalException {
failIfLabelOutsideAllowlist(moduleContext.label(), moduleContext.moduleRepoName(), allowlist);
}
/**
* Throws {@code EvalException} if the given {@link Label} is not within either 1) the builtins
* repository, or 2) a package or subpackage of an entry in the given allowlist.
*/
public static void failIfLabelOutsideAllowlist(
Label label, @Nullable String moduleRepoName, Allowlist allowlist) throws EvalException {
if (isNotAllowed(label, moduleRepoName, allowlist)) {
throw Starlark.errorf("file '%s' cannot use private API", label.getCanonicalForm());
}
}
/**
* Returns true if the given {@link Label} is not within either 1) the builtins repository, or 2)
* a package or subpackage of an entry in the given allowlist.
*/
public static boolean isNotAllowed(
Label label, @Nullable String moduleRepoName, Allowlist allowlist) {
if (label.getRepository().equals(RepositoryName.BUILTINS)) {
return false;
}
return !allowlist.allows(label, moduleRepoName);
}
}