Introduce flag --incompatible_static_name_resolution_in_build_files
Implements https://github.com/bazelbuild/bazel/issues/8022
When the flag is enabled, BUILD files are statically checked. This can find errors (undefined symbols) in code path that is not executed.
RELNOTES: Flag `--incompatible_static_name_resolution_in_build_files` is added. See https://github.com/bazelbuild/bazel/issues/8022
PiperOrigin-RevId: 243355609
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
index 7a1b53d..a590fdf 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
@@ -484,6 +484,20 @@
+ "will be available")
public boolean incompatibleRemoveNativeMavenJar;
+ @Option(
+ name = "incompatible_static_name_resolution_in_build_files",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
+ effectTags = {OptionEffectTag.BUILD_FILE_SEMANTICS},
+ metadataTags = {
+ OptionMetadataTag.INCOMPATIBLE_CHANGE,
+ OptionMetadataTag.TRIGGERED_BY_ALL_INCOMPATIBLE_CHANGES
+ },
+ help =
+ "If set to true, BUILD files use static name resolution (which can find errors in code "
+ + "that is not executed). See https://github.com/bazelbuild/bazel/issues/8022")
+ public boolean incompatibleStaticNameResolutionInBuildFiles;
+
/** Used in an integration test to confirm that flags are visible to the interpreter. */
@Option(
name = "internal_skylark_flag_test_canary",
@@ -546,6 +560,7 @@
.incompatibleNoTransitiveLoads(incompatibleNoTransitiveLoads)
.incompatibleRemapMainRepo(incompatibleRemapMainRepo)
.incompatibleRemoveNativeMavenJar(incompatibleRemoveNativeMavenJar)
+ .incompatibleStaticNameResolutionInBuildFiles(incompatibleStaticNameResolutionInBuildFiles)
.incompatibleStringJoinRequiresStrings(incompatibleStringJoinRequiresStrings)
.internalSkylarkFlagTestCanary(internalSkylarkFlagTestCanary)
.incompatibleDoNotSplitLinkingCmdline(incompatibleDoNotSplitLinkingCmdline)
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
index d4cb286..ce47ee6 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
@@ -176,6 +176,8 @@
public abstract boolean incompatibleStringJoinRequiresStrings();
+ public abstract boolean incompatibleStaticNameResolutionInBuildFiles();
+
public abstract boolean internalSkylarkFlagTestCanary();
@@ -227,6 +229,7 @@
.incompatibleNoTransitiveLoads(true)
.incompatibleRemapMainRepo(false)
.incompatibleRemoveNativeMavenJar(false)
+ .incompatibleStaticNameResolutionInBuildFiles(false)
.incompatibleStringJoinRequiresStrings(false)
.internalSkylarkFlagTestCanary(false)
.incompatibleDoNotSplitLinkingCmdline(false)
@@ -301,6 +304,7 @@
public abstract Builder incompatibleStringJoinRequiresStrings(boolean value);
+ public abstract Builder incompatibleStaticNameResolutionInBuildFiles(boolean value);
public abstract Builder internalSkylarkFlagTestCanary(boolean value);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java b/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
index e53fde0..590e9540 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
@@ -88,11 +88,14 @@
private final Environment env;
private Block block;
private int loopCount;
+ /** In BUILD files, we have a slightly different behavior for legacy reasons. */
+ private final boolean isBuildFile;
/** Create a ValidationEnvironment for a given global Environment (containing builtins). */
- ValidationEnvironment(Environment env) {
+ private ValidationEnvironment(Environment env, boolean isBuildFile) {
Preconditions.checkArgument(env.isGlobal());
this.env = env;
+ this.isBuildFile = isBuildFile;
block = new Block(Scope.Universe, null);
Set<String> builtinVariables = env.getVariableNames();
block.variables.addAll(builtinVariables);
@@ -173,12 +176,17 @@
// If this is the case, output a more helpful error message than 'not found'.
FlagGuardedValue result = env.getRestrictedBindings().get(node.getName());
if (result != null) {
- throw new ValidationException(result.getEvalExceptionFromAttemptingAccess(
- node.getLocation(), env.getSemantics(), node.getName()));
+ throw new ValidationException(
+ result.getEvalExceptionFromAttemptingAccess(
+ node.getLocation(), env.getSemantics(), node.getName()));
}
throw new ValidationException(node.createInvalidIdentifierException(getAllSymbols()));
}
- node.setScope(b.scope);
+ // TODO(laurentlb): In BUILD files, calling setScope will throw an exception. This happens
+ // because some AST nodes are shared across multipe ASTs (due to the prelude file).
+ if (!isBuildFile) {
+ node.setScope(b.scope);
+ }
}
@Override
@@ -271,7 +279,8 @@
/** Declare a variable and add it to the environment. */
private void declare(String varname, Location location) {
- if (block.scope == Scope.Module && block.variables.contains(varname)) {
+ // TODO(laurentlb): Forbid reassignment in BUILD files.
+ if (!isBuildFile && block.scope == Scope.Module && block.variables.contains(varname)) {
// Symbols defined in the module scope cannot be reassigned.
throw new ValidationException(
location,
@@ -333,7 +342,7 @@
/** Validates the AST and runs static checks. */
private void validateAst(List<Statement> statements) {
// Check that load() statements are on top.
- if (env.getSemantics().incompatibleBzlDisallowLoadAfterStatement()) {
+ if (!isBuildFile && env.getSemantics().incompatibleBzlDisallowLoadAfterStatement()) {
checkLoadAfterStatement(statements);
}
@@ -350,7 +359,7 @@
public static void validateAst(Environment env, List<Statement> statements) throws EvalException {
try {
- ValidationEnvironment venv = new ValidationEnvironment(env);
+ ValidationEnvironment venv = new ValidationEnvironment(env, false);
venv.validateAst(statements);
// Check that no closeBlock was forgotten.
Preconditions.checkState(venv.block.parent == null);
@@ -392,6 +401,17 @@
List<Statement> statements, final EventHandler eventHandler, Environment env) {
// Wrap the boolean inside an array so that the inner class can modify it.
final boolean[] success = new boolean[] {true};
+
+ if (env.getSemantics().incompatibleStaticNameResolutionInBuildFiles()) {
+ ValidationEnvironment venv = new ValidationEnvironment(env, true);
+ try {
+ venv.validateAst(statements);
+ } catch (ValidationException e) {
+ eventHandler.handle(Event.error(e.exception.getLocation(), e.exception.getMessage()));
+ return false;
+ }
+ }
+
// TODO(laurentlb): Merge with the visitor above when possible (i.e. when BUILD files use it).
SyntaxTreeVisitor checker =
new SyntaxTreeVisitor() {
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
index 2b533f9..08aad7f 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
@@ -155,6 +155,7 @@
"--incompatible_no_transitive_loads=" + rand.nextBoolean(),
"--incompatible_remap_main_repo=" + rand.nextBoolean(),
"--incompatible_remove_native_maven_jar=" + rand.nextBoolean(),
+ "--incompatible_static_name_resolution_in_build_files=" + rand.nextBoolean(),
"--incompatible_string_join_requires_strings=" + rand.nextBoolean(),
"--internal_skylark_flag_test_canary=" + rand.nextBoolean(),
"--incompatible_do_not_split_linking_cmdline=" + rand.nextBoolean());
@@ -200,6 +201,7 @@
.incompatibleNoTransitiveLoads(rand.nextBoolean())
.incompatibleRemapMainRepo(rand.nextBoolean())
.incompatibleRemoveNativeMavenJar(rand.nextBoolean())
+ .incompatibleStaticNameResolutionInBuildFiles(rand.nextBoolean())
.incompatibleStringJoinRequiresStrings(rand.nextBoolean())
.internalSkylarkFlagTestCanary(rand.nextBoolean())
.incompatibleDoNotSplitLinkingCmdline(rand.nextBoolean())
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
index 78700ea..2d25e1a 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
@@ -680,6 +680,16 @@
}
@Test
+ public void testStaticNameResolution() throws Exception {
+ newTest("--incompatible_static_name_resolution_in_build_files=true")
+ .testIfErrorContains("name 'foo' is not defined", "[foo for x in []]");
+
+ // legacy
+ new BuildTest("--incompatible_static_name_resolution_in_build_files=false")
+ .testStatement("str([foo for x in []])", "[]");
+ }
+
+ @Test
public void testDefInBuild() throws Exception {
new BuildTest()
.testIfErrorContains(