| // Copyright 2019 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.syntax; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.devtools.build.lib.syntax.Mutability.MutabilityException; | |
| import java.util.Collections; | |
| import java.util.HashSet; | |
| import java.util.LinkedHashMap; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import javax.annotation.Nullable; | |
| /** | |
| * A {@link Module} represents a Starlark module, a container of global variables populated by | |
| * executing a Starlark file. Each top-level assignment updates a global variable in the module. | |
| * | |
| * <p>Each module references its "predeclared" environment, which is often shared among many | |
| * modules. These are the names that are defined even at the start of execution. For example, in | |
| * Bazel, the predeclared environment of the module for a BUILD or .bzl file defines name values | |
| * such as cc_binary and glob. | |
| * | |
| * <p>The predeclared environment currently must include the "universal" names present in every | |
| * Starlark thread in every dialect, such as None, len, and str. | |
| * | |
| * <p>Global bindings in a Module may shadow bindings inherited from the predeclared or universe | |
| * block. | |
| * | |
| * <p>A module may carry an arbitrary piece of metadata called its "label". In Bazel, for example, | |
| * the label is a build label such as "//dir:file.bzl", for use by the Label function. This is a | |
| * hack. | |
| * | |
| * <p>A {@link Module} may be constructed in a two-phase process. To do this, call the nullary | |
| * constructor to create an uninitialized {@link Module}, then call {@link #initialize}. It is | |
| * illegal to use any other method in-between these two calls, or to call {@link #initialize} on an | |
| * already initialized {@link Module}. | |
| */ | |
| // TODO(adonovan): | |
| // - make fields private where possible. | |
| // - remove references to this from StarlarkThread. | |
| // - separate the universal predeclared environment and make it implicit. | |
| // - eliminate initialize(). The only constructor we need is: | |
| // (String name, Mutability mu, Map<String, Object> predeclared, Object label). | |
| public final class Module implements ValidationEnvironment.Module { | |
| /** | |
| * Final, except that it may be initialized after instantiation. Null mutability indicates that | |
| * this Frame is uninitialized. | |
| */ | |
| @Nullable private Mutability mutability; | |
| /** Final, except that it may be initialized after instantiation. */ | |
| @Nullable Module universe; | |
| // The label (an optional piece of metadata) associated with the file. | |
| @Nullable Object label; | |
| /** Bindings are maintained in order of creation. */ | |
| private final LinkedHashMap<String, Object> bindings; | |
| /** | |
| * A list of bindings which *would* exist in this global frame under certain semantic flags, but | |
| * do not exist using the semantic flags used in this frame's creation. This map should not be | |
| * used for lookups; it should only be used to throw descriptive error messages when a lookup of a | |
| * restricted object is attempted. | |
| */ | |
| final LinkedHashMap<String, FlagGuardedValue> restrictedBindings; | |
| /** Set of bindings that are exported (can be loaded from other modules). */ | |
| final HashSet<String> exportedBindings; | |
| /** Constructs an uninitialized instance; caller must call {@link #initialize} before use. */ | |
| public Module() { | |
| this.mutability = null; | |
| this.universe = null; | |
| this.label = null; | |
| this.bindings = new LinkedHashMap<>(); | |
| this.restrictedBindings = new LinkedHashMap<>(); | |
| this.exportedBindings = new HashSet<>(); | |
| } | |
| /** | |
| * Returns the module (file) of the innermost enclosing Starlark function on the call stack, or | |
| * null if none of the active calls are functions defined in Starlark. | |
| * | |
| * <p>The name of this function is intentionally horrible to make you feel bad for using it. | |
| */ | |
| @Nullable | |
| public static Module ofInnermostEnclosingStarlarkFunction(StarlarkThread thread) { | |
| for (Debug.Frame fr : thread.getDebugCallStack().reverse()) { | |
| if (fr.getFunction() instanceof StarlarkFunction) { | |
| return ((StarlarkFunction) fr.getFunction()).getModule(); | |
| } | |
| } | |
| return null; | |
| } | |
| Module( | |
| Mutability mutability, | |
| @Nullable Module universe, | |
| @Nullable Object label, | |
| @Nullable Map<String, Object> bindings, | |
| @Nullable Map<String, FlagGuardedValue> restrictedBindings) { | |
| Preconditions.checkState(universe == null || universe.universe == null); | |
| this.mutability = Preconditions.checkNotNull(mutability); | |
| this.universe = universe; | |
| if (label != null) { | |
| this.label = label; | |
| } else if (universe != null) { | |
| this.label = universe.label; | |
| } else { | |
| this.label = null; | |
| } | |
| this.bindings = new LinkedHashMap<>(); | |
| if (bindings != null) { | |
| this.bindings.putAll(bindings); | |
| } | |
| this.restrictedBindings = new LinkedHashMap<>(); | |
| if (restrictedBindings != null) { | |
| this.restrictedBindings.putAll(restrictedBindings); | |
| } | |
| if (universe != null) { | |
| this.restrictedBindings.putAll(universe.restrictedBindings); | |
| } | |
| this.exportedBindings = new HashSet<>(); | |
| } | |
| public Module(Mutability mutability) { | |
| this(mutability, null, null, null, null); | |
| } | |
| public Module(Mutability mutability, @Nullable Module universe) { | |
| this(mutability, universe, null, null, null); | |
| } | |
| public Module(Mutability mutability, @Nullable Module universe, @Nullable Object label) { | |
| this(mutability, universe, label, null, null); | |
| } | |
| /** Constructs a global frame for the given builtin bindings. */ | |
| public static Module createForBuiltins(Map<String, Object> bindings) { | |
| Mutability mutability = Mutability.create("<builtins>").freeze(); | |
| return new Module(mutability, null, null, bindings, null); | |
| } | |
| /** | |
| * Constructs a global frame based on the given parent frame, filtering out flag-restricted global | |
| * objects. | |
| */ | |
| static Module filterOutRestrictedBindings( | |
| Mutability mutability, Module parent, StarlarkSemantics semantics) { | |
| if (parent == null) { | |
| return new Module(mutability); | |
| } | |
| Preconditions.checkArgument(parent.mutability().isFrozen(), "parent frame must be frozen"); | |
| Preconditions.checkArgument(parent.universe == null); | |
| Map<String, Object> filteredBindings = new LinkedHashMap<>(); | |
| Map<String, FlagGuardedValue> restrictedBindings = new LinkedHashMap<>(); | |
| for (Map.Entry<String, Object> binding : parent.bindings.entrySet()) { | |
| if (binding.getValue() instanceof FlagGuardedValue) { | |
| FlagGuardedValue val = (FlagGuardedValue) binding.getValue(); | |
| if (val.isObjectAccessibleUsingSemantics(semantics)) { | |
| filteredBindings.put(binding.getKey(), val.getObject()); | |
| } else { | |
| restrictedBindings.put(binding.getKey(), val); | |
| } | |
| } else { | |
| filteredBindings.put(binding.getKey(), binding.getValue()); | |
| } | |
| } | |
| restrictedBindings.putAll(parent.restrictedBindings); | |
| return new Module( | |
| mutability, /*universe=*/ null, parent.label, filteredBindings, restrictedBindings); | |
| } | |
| private void checkInitialized() { | |
| Preconditions.checkNotNull(mutability, "Attempted to use Frame before initializing it"); | |
| } | |
| public void initialize( | |
| Mutability mutability, | |
| @Nullable Module universe, | |
| @Nullable Object label, | |
| Map<String, Object> bindings) { | |
| Preconditions.checkState( | |
| universe == null || universe.universe == null); // no more than 1 universe | |
| Preconditions.checkState( | |
| this.mutability == null, "Attempted to initialize an already initialized Frame"); | |
| this.mutability = Preconditions.checkNotNull(mutability); | |
| this.universe = universe; | |
| if (label != null) { | |
| this.label = label; | |
| } else if (universe != null) { | |
| this.label = universe.label; | |
| } else { | |
| this.label = null; | |
| } | |
| this.bindings.putAll(bindings); | |
| } | |
| /** | |
| * Returns a new {@link Module} with the same fields, except that {@link #label} is set to the | |
| * given value. The label associated with each function (frame) on the stack is accessible using | |
| * {@link #getLabel}, and is included in the result of {@code str(fn)} where {@code fn} is a | |
| * StarlarkFunction. | |
| */ | |
| public Module withLabel(Object label) { | |
| checkInitialized(); | |
| return new Module( | |
| mutability, /*universe=*/ null, label, bindings, /*restrictedBindings=*/ null); | |
| } | |
| /** Returns the {@link Mutability} of this {@link Module}. */ | |
| public Mutability mutability() { | |
| checkInitialized(); | |
| return mutability; | |
| } | |
| /** | |
| * Returns the parent {@link Module}, if it exists. | |
| * | |
| * <p>TODO(laurentlb): Should be called getUniverse. | |
| */ | |
| @Nullable | |
| public Module getParent() { | |
| checkInitialized(); | |
| return universe; | |
| } | |
| /** | |
| * Returns the label (an optional piece of metadata) associated with this {@code Module}. (For | |
| * Bazel LOADING threads, this is the build label of its BUILD or .bzl file.) | |
| */ | |
| @Nullable | |
| public Object getLabel() { | |
| checkInitialized(); | |
| return label; | |
| } | |
| /** | |
| * Returns a map of direct bindings of this {@link Module}, ignoring universe. | |
| * | |
| * <p>The bindings are returned in a deterministic order (for a given sequence of initial values | |
| * and updates). | |
| * | |
| * <p>For efficiency an unmodifiable view is returned. Callers should assume that the view is | |
| * invalidated by any subsequent modification to the {@link Module}'s bindings. | |
| */ | |
| public Map<String, Object> getBindings() { | |
| checkInitialized(); | |
| return Collections.unmodifiableMap(bindings); | |
| } | |
| /** | |
| * Returns a map of bindings that are exported (i.e. symbols declared using `=` and `def`, but not | |
| * `load`). | |
| */ | |
| // TODO(adonovan): whether bindings are exported should be decided by the resolver; | |
| // non-exported bindings should never be added to the module. | |
| public Map<String, Object> getExportedBindings() { | |
| checkInitialized(); | |
| ImmutableMap.Builder<String, Object> result = new ImmutableMap.Builder<>(); | |
| for (Map.Entry<String, Object> entry : bindings.entrySet()) { | |
| if (exportedBindings.contains(entry.getKey())) { | |
| result.put(entry); | |
| } | |
| } | |
| return result.build(); | |
| } | |
| @Override | |
| public Set<String> getNames() { | |
| return getTransitiveBindings().keySet(); | |
| } | |
| @Override | |
| public String getUndeclaredNameError(String name) { | |
| FlagGuardedValue v = restrictedBindings.get(name); | |
| return v == null ? null : v.getErrorFromAttemptingAccess(name); | |
| } | |
| /** Returns an environment containing both module and predeclared bindings. */ | |
| // TODO(adonovan): eliminate in favor of explicit module vs. predeclared operations. | |
| public Map<String, Object> getTransitiveBindings() { | |
| checkInitialized(); | |
| // Can't use ImmutableMap.Builder because it doesn't allow duplicates. | |
| LinkedHashMap<String, Object> collectedBindings = new LinkedHashMap<>(); | |
| if (universe != null) { | |
| collectedBindings.putAll(universe.getTransitiveBindings()); | |
| } | |
| collectedBindings.putAll(getBindings()); | |
| return collectedBindings; | |
| } | |
| /** | |
| * Returns the value of the specified module variable, or null if not bound. Does not look in the | |
| * predeclared environment. | |
| */ | |
| public Object lookup(String varname) { | |
| checkInitialized(); | |
| return bindings.get(varname); | |
| } | |
| /** | |
| * Returns the value of the named variable in the module environment, or if not bound there, in | |
| * the predeclared environment, or if not bound there, null. | |
| */ | |
| public Object get(String varname) { | |
| // TODO(adonovan): delete this whole function, and getTransitiveBindings. | |
| // With proper resolution, the interpreter will know whether | |
| // to look in the module or the predeclared/universal environment. | |
| checkInitialized(); | |
| Object val = bindings.get(varname); | |
| if (val != null) { | |
| return val; | |
| } | |
| if (universe != null) { | |
| return universe.get(varname); | |
| } | |
| return null; | |
| } | |
| /** Updates a binding in the module environment. */ | |
| public void put(String varname, Object value) throws MutabilityException { | |
| Preconditions.checkNotNull(value, "Module.put(%s, null)", varname); | |
| checkInitialized(); | |
| if (mutability.isFrozen()) { | |
| throw new MutabilityException("trying to mutate a frozen module"); | |
| } | |
| bindings.put(varname, value); | |
| } | |
| @Override | |
| public String toString() { | |
| // TODO(adonovan): use the file name of the module (not visible to Starlark programs). | |
| if (mutability == null) { | |
| return "<Uninitialized Module>"; | |
| } else { | |
| return String.format("<Module%s>", mutability()); | |
| } | |
| } | |
| } |