From b9530269569d40cf8495f667c52c0746bf403762 Mon Sep 17 00:00:00 2001 From: Googler Date: Fri, 12 Jan 2024 07:35:43 -0800 Subject: [PATCH] Check symbolic macro instances for name conflicts with other objects in a package The maps of targets and macro instances are tracked separately in the Package.Builder, but we need to enforce that their keys are disjoint (since macro names will be used as the basis for deciding which macro to expand to obtain the target for a given label.) This CL accomplishes this by updating the name conflict utility methods to conveniently check both at the same time. Package.java: - createInputFile(): Rephrase to use checkNameConflict (now checkForExistingName()). This improves readability and avoids adding new complexity to this method, at the expense of an extra redundant lookup in the targets map. - beforeBuild(): Don't implicitly create an input file if it is the name of a macro. - checkForConflicts(): Rename checkRuleAndOutputs(). Construct a bespoke (and more streamlined) exception message, so we can eliminate the nameConflict() helper. - checkNameConflict(): Rename checkForExistingName(). Split into overloads for a Target and MacroInstance arg, both of which dispatch to unified logic for checking the targets/macros maps and constructing the appropriate error message. This absorbs the nameConflict() helper. OutputFileTest: - Update test case for new error message. PackageFactoryTest: - Test cases for conflicts between macros and each of: rule targets, generated outputs, environment groups, package groups, explicitly declared input files (exports_files), and other macros, all in both declaration orders (to exercise different code paths). - Check that macros *can* conflict with output prefixes, because that's a behavior we have for targets, whether or not it's a good one (I honestly don't understand it). Didn't bother doing both orders here. - Check that macros prevent implicit creation of input files by the same name. Work toward #19922. PiperOrigin-RevId: 597836620 Change-Id: Iee110b4db0dfcd09b72d43e6c83f435003a45e16 --- .../devtools/build/lib/packages/Package.java | 154 +++++++++++------- .../build/lib/packages/OutputFileTest.java | 11 +- .../lib/packages/PackageFactoryTest.java | 153 +++++++++++++++++ 3 files changed, 256 insertions(+), 62 deletions(-) diff --git a/src/main/java/com/google/devtools/build/lib/packages/Package.java b/src/main/java/com/google/devtools/build/lib/packages/Package.java index 2b0d617bb56c05..5b052c075aed87 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/Package.java +++ b/src/main/java/com/google/devtools/build/lib/packages/Package.java @@ -116,10 +116,12 @@ public class Package { private static final long PACKAGE_OVERHEAD_UNSET = -1; /** - * An exception used when a target's name conflicts with other targets in the package. + * An exception used when the name of a target or symbolic macro clashes with another entity + * defined in the package. * - *

This could be an exact match, or in the case of output files, a prefix clash (one output - * file is a prefix of another). + *

Common examples of conflicts include two targets or symbolic macros sharing the same name, + * and one output file being a prefix of another. See {@link #checkForExistingName} and {@link + * #checkRuleAndOutputs} for more details. */ public static final class NameConflictException extends Exception { private NameConflictException(String message) { @@ -922,19 +924,19 @@ default boolean precomputeTransitiveLoads() { private enum NameConflictCheckingPolicy { UNKNOWN, - DISABLED, + NOT_GUARANTEED, ENABLED; } /** - * Whether to do validation checks for name clashes between targets and between output file + * Whether to do all validation checks for name clashes among targets, macros, and output file * prefixes. * - *

This should only be disabled for data that has already been validated, e.g. in package - * deserialization. + *

The {@code NOT_GUARANTEED} value should only be used when the package data has already + * been validated, e.g. in package deserialization. * - *

Even disabling this does not turn off *all* checking, just some of the more expensive - * ones. Do not rely on being able to violate these checks. + *

Setting it to {@code NOT_GUARANTEED} does not necessarily turn off *all* checking, just + * some of the more expensive ones. Do not rely on being able to violate these checks. */ private NameConflictCheckingPolicy nameConflictCheckingPolicy = NameConflictCheckingPolicy.UNKNOWN; @@ -953,7 +955,7 @@ private enum NameConflictCheckingPolicy { * file having that prefix. Used for error reporting. * *

This field is null if name conflict checking is disabled. It is also null after the - * package is built. The content of the map is manipulated only in {@link #checkForConflicts}. + * package is built. The content of the map is manipulated only in {@link #checkRuleAndOutputs}. */ @Nullable private Map outputFilePrefixes = new HashMap<>(); @@ -1425,8 +1427,8 @@ private Iterable getRules() { * @param targetName name of the input file. This must be a valid target name as defined by * {@link com.google.devtools.build.lib.cmdline.LabelValidator#validateTargetName}. * @return the newly-created {@code InputFile}, or the old one if it already existed. - * @throws NameConflictException if the name was already taken by another target that is not an - * input file + * @throws NameConflictException if the name was already taken by another target/macro that is + * not an input file * @throws IllegalArgumentException if the name is not a valid label */ InputFile createInputFile(String targetName, Location location) throws NameConflictException { @@ -1444,12 +1446,9 @@ InputFile createInputFile(String targetName, Location location) throws NameConfl "FileTarget in package " + pkg.getName() + " has illegal name: " + targetName, e); } - if (existing == null) { - addInputFile(inputFile); - return inputFile; - } else { - throw nameConflict(inputFile, existing); - } + checkForExistingName(inputFile); + addInputFile(inputFile); + return inputFile; } /** @@ -1509,7 +1508,7 @@ void addPackageGroup( repoRootMeansCurrentRepo, eventHandler, location); - checkNameConflict(group); + checkForExistingName(group); targets.put(group.getName(), group); if (group.containsErrors()) { @@ -1559,7 +1558,7 @@ void addEnvironmentGroup( EnvironmentGroup group = new EnvironmentGroup(createLabel(name), pkg, environments, defaults, location); - checkNameConflict(group); + checkForExistingName(group); targets.put(group.getName(), group); // Invariant: once group is inserted into targets, it must also: @@ -1594,8 +1593,9 @@ void addEnvironmentGroup( } /** - * Turns off conflict checking for name clashes between targets, or between output file - * prefixes. + * Turns off (some) conflict checking for name clashes between targets, macros, and output file + * prefixes. (It is not guaranteed to disable all checks, since it is intended as an + * optimization and not for semantic effect.) * *

This should only be done for data that has already been validated, e.g. during package * deserialization. Do not call this unless you know what you're doing. @@ -1606,14 +1606,15 @@ void addEnvironmentGroup( @CanIgnoreReturnValue Builder disableNameConflictChecking() { Preconditions.checkState(nameConflictCheckingPolicy == NameConflictCheckingPolicy.UNKNOWN); - this.nameConflictCheckingPolicy = NameConflictCheckingPolicy.DISABLED; + this.nameConflictCheckingPolicy = NameConflictCheckingPolicy.NOT_GUARANTEED; this.ruleLabels = null; this.outputFilePrefixes = null; return this; } private void ensureNameConflictChecking() { - Preconditions.checkState(nameConflictCheckingPolicy != NameConflictCheckingPolicy.DISABLED); + Preconditions.checkState( + nameConflictCheckingPolicy != NameConflictCheckingPolicy.NOT_GUARANTEED); this.nameConflictCheckingPolicy = NameConflictCheckingPolicy.ENABLED; } @@ -1637,7 +1638,8 @@ private void addRuleInternal(Rule rule) { * #disableNameConflictChecking} was already called. */ void addRuleUnchecked(Rule rule) { - Preconditions.checkState(nameConflictCheckingPolicy == NameConflictCheckingPolicy.DISABLED); + Preconditions.checkState( + nameConflictCheckingPolicy == NameConflictCheckingPolicy.NOT_GUARANTEED); addRuleInternal(rule); } @@ -1649,13 +1651,14 @@ void addRule(Rule rule) throws NameConflictException { ensureNameConflictChecking(); List