| // 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.syntax; | |
| import com.google.common.base.Joiner; | |
| import java.util.IdentityHashMap; | |
| /** | |
| * An object that manages the capability to mutate Starlark objects and their {@link | |
| * StarlarkThread}s. Collectively, the managed objects are called {@link Freezable}s. | |
| * | |
| * <p>Each {@code StarlarkThread}, and each of the mutable Starlark values (i.e., {@link | |
| * StarlarkMutable}s) that are created in that {@code StarlarkThread}, holds a pointer to the same | |
| * {@code Mutability} instance. Once the {@code StarlarkThread} is done evaluating, its {@code | |
| * Mutability} is irreversibly closed ("frozen"). At that point, it is no longer possible to change | |
| * either the bindings in that {@code StarlarkThread} or the state of its objects. This protects | |
| * each {@code StarlarkThread} from unintentional and unsafe modification. | |
| * | |
| * <p>{@code Mutability}s enforce isolation between {@code StarlarkThread}s; it is illegal for an | |
| * evaluation in one {@code StarlarkThread} to affect the bindings or values of another. In | |
| * particular, the {@code StarlarkThread} for any Starlark module is frozen before its symbols can | |
| * be imported for use by another module. Each individual {@code StarlarkThread}'s evaluation is | |
| * single-threaded, so this isolation also translates to thread safety. Any number of threads may | |
| * simultaneously access frozen data. (The {@code Mutability} itself is also thread-safe if and only | |
| * if it is frozen.} | |
| * | |
| * <p>Although the mutability pointer of a {@code Freezable} contains some debugging information | |
| * about its context, this should not affect the {@code Freezable}'s semantics. From a behavioral | |
| * point of view, the only thing that matters is whether the {@code Mutability} is frozen, not what | |
| * particular {@code Mutability} object is pointed to. | |
| * | |
| * <p>When a Starlark program iterates over a mutable sequence value in a for-loop or comprehension, | |
| * the sequence value becomes temporarily immutable for the duration of the loop. Conceptually, the | |
| * value maintains a counter of active iterations, and the interpreter notifies the {@code | |
| * Freezable} value before and after the loop so that it can alter its counter by calling its {@code | |
| * updateIteratorCount} method. While the counter value is nonzero, the value should cause all | |
| * attempts to mutate it to fail. The temporary immutability applies only to the sequence itself, | |
| * not to its elements. Once a mutable sequence becomes frozen, there is no need to count active | |
| * iterators (and doing so would be racy as frozen objects may be published to other Starlark | |
| * threads). The default implementation of {@code updateIteratorCount} uses a set of counters in the | |
| * Mutability, but a Freezable object may define a more efficient intrusive counter implementation. | |
| * | |
| * <p>We follow two disciplines to ensure safety. First, all mutation methods of a Freezable value | |
| * must confirm that the value's Mutability is not yet frozen, nor is the value temporarily | |
| * immutable due to active iterators. | |
| * | |
| * <p>Second, {@code Mutability}s are created using the try-with-resource style: | |
| * | |
| * <pre>{@code | |
| * try (Mutability mutability = Mutability.create(name, ...)) { ... } | |
| * }</pre> | |
| * | |
| * The general pattern is to create a {@code Mutability}, build an {@code StarlarkThread}, mutate | |
| * that {@code StarlarkThread} and its objects, and possibly return the result from within the | |
| * {@code try} block, relying on the try-with-resource construct to ensure that everything gets | |
| * frozen before the result is used. The only code that should create a {@code Mutability} without | |
| * using try-with-resource is test code that is not part of the Bazel jar. | |
| * | |
| * <p>We keep some (unchecked) invariants regarding where {@code Mutability} objects may appear | |
| * within a compound value. | |
| * | |
| * <ol> | |
| * <li>A compound value can never contain an unfrozen {@code Mutability} for any {@code | |
| * StarlarkThread} except the one currently being evaluated. | |
| * <li>If a value has the special {@link #IMMUTABLE} {@code Mutability}, all of its contents are | |
| * themselves deeply immutable too (i.e. have frozen {@code Mutability}s). | |
| * </ol> | |
| * | |
| * It follows that, if these invariants hold, an unfrozen value cannot appear as the child of a | |
| * value whose {@code Mutability} is already frozen. This knowledge is used by {@link | |
| * StarlarkMutable#isImmutable} to prune traversals of a compound value. | |
| * | |
| * <p>There is a special API for freezing individual values rather than whole {@code | |
| * StarlarkThread}s. Because this API makes it easier to violate the above invariants, you should | |
| * avoid using it if at all possible; at the moment it is only used for serialization. Under this | |
| * API, you may call {@link Freezable#unsafeShallowFreeze} to reset a value's {@code Mutability} | |
| * pointer to be {@link #IMMUTABLE}. This operation has no effect on the {@code Mutability} itself. | |
| * It is up to the caller to preserve or restore the above invariants by ensuring that any deeply | |
| * contained values are also frozen. For safety and explicitness, this operation is disallowed | |
| * unless the {@code Mutability}'s {@link #allowsUnsafeShallowFreeze} method returns true. | |
| */ | |
| public final class Mutability implements AutoCloseable { | |
| // Maps each temporarily frozen Freezable value to the (positive) count of active iterators over | |
| // the value. This field is set to null when the Mutability becomes permanently frozen, at which | |
| // point there is no need to track iterators. This map does not contain Freezable values that | |
| // define their own implementation of updateIteratorCount. | |
| private IdentityHashMap<Freezable, Integer> iteratorCount = | |
| new IdentityHashMap<>(10); // 10 nested for-loops seems plenty | |
| // An optional list of values that are formatted with toString and joined with spaces to yield the | |
| // "annotation", an internal name describing the purpose of this Mutability. | |
| private final Object[] annotation; | |
| /** Controls access to {@link Freezable#unsafeShallowFreeze}. */ | |
| private final boolean allowsUnsafeShallowFreeze; | |
| private Mutability(Object[] annotation, boolean allowsUnsafeShallowFreeze) { | |
| this.annotation = annotation; | |
| this.allowsUnsafeShallowFreeze = allowsUnsafeShallowFreeze; | |
| } | |
| /** | |
| * Creates a {@code Mutability}. | |
| * | |
| * @param annotation a list of objects whose toString representations are joined with spaces to | |
| * yield the annotation, an internal name describing the purpose of this Mutability. | |
| */ | |
| public static Mutability create(Object... annotation) { | |
| return new Mutability(annotation, /*allowsUnsafeShallowFreeze=*/ false); | |
| } | |
| /** | |
| * Creates a {@code Mutability} whose objects can be individually frozen; see docstrings for | |
| * {@link Mutability} and {@link Freezable#unsafeShallowFreeze}. | |
| */ | |
| public static Mutability createAllowingShallowFreeze(Object... annotation) { | |
| return new Mutability(annotation, /*allowsUnsafeShallowFreeze=*/ true); | |
| } | |
| /** Returns the Mutability's "annotation", an internal name describing its purpose. */ | |
| public String getAnnotation() { | |
| // The annotation string is computed when needed, typically never, | |
| // to avoid the performance penalty of materializing it eagerly. | |
| return Joiner.on(" ").join(annotation); | |
| } | |
| @Override | |
| public String toString() { | |
| return (isFrozen() ? "(" : "[") + getAnnotation() + (isFrozen() ? ")" : "]"); | |
| } | |
| public boolean isFrozen() { | |
| return this.iteratorCount == null; | |
| } | |
| // Defines the default behavior of mutable Freezable sequence values, | |
| // which become temporarily immutable while there are active iterators. | |
| private boolean updateIteratorCount(Freezable x, int delta) { | |
| if (isFrozen()) { | |
| return false; | |
| } | |
| int i = this.iteratorCount.getOrDefault(x, 0); | |
| if (delta > 0) { | |
| i++; | |
| this.iteratorCount.put(x, i); | |
| } else if (delta < 0) { | |
| i--; | |
| if (i == 0) { | |
| this.iteratorCount.remove(x); | |
| } else if (i > 0) { | |
| this.iteratorCount.put(x, i); | |
| } else { | |
| throw new IllegalStateException("zero value in this.iteratorCount"); | |
| } | |
| } | |
| return i > 0; | |
| } | |
| /** | |
| * Freezes this {@code Mutability}, rendering all {@link Freezable} objects that refer to it | |
| * immutable. | |
| * | |
| * Note that freezing does not directly touch all the {@code Freezables}, so this operation is | |
| * constant-time. | |
| * | |
| * @return this object, in the fluent style | |
| */ | |
| public Mutability freeze() { | |
| this.iteratorCount = null; | |
| return this; | |
| } | |
| @Override | |
| public void close() { | |
| freeze(); | |
| } | |
| /** | |
| * Returns whether {@link Freezable}s having this {@code Mutability} allow the {@link | |
| * #unsafeShallowFreeze} operation. | |
| */ | |
| public boolean allowsUnsafeShallowFreeze() { | |
| return allowsUnsafeShallowFreeze; | |
| } | |
| /** | |
| * An object that refers to a {@link Mutability} to decide whether to allow mutation. All {@link | |
| * Freezable} Starlark objects created within a given {@link StarlarkThread} will share the same | |
| * {@code Mutability} as that {@code StarlarkThread}. | |
| */ | |
| public interface Freezable { | |
| /** | |
| * Returns the {@link Mutability} associated with this {@code Freezable}. This should not change | |
| * over the lifetime of the object, except by calling {@link #shallowFreeze} if applicable. | |
| */ | |
| Mutability mutability(); | |
| /** | |
| * Registers a change to this Freezable's iterator count and reports whether it is temporarily | |
| * immutable. | |
| * | |
| * <p>If the value is permanently frozen ({@code mutability().isFrozen()), this function is a | |
| * no-op that returns false. | |
| * | |
| * <p>Otherwise, if delta is positive, this increments the count of active iterators over the | |
| * value, causing it to appear temporarily frozen (if it wasn't already). If delta is negative, | |
| * the counter is decremented, and if delta is zero the counter is unchanged. It is illegal to | |
| * decrement the counter if it was already zero. The return value is true if the count is | |
| * positive after the change, and false otherwise. | |
| * | |
| * <p>The default implementation stores the counter of iterators in a hash table in the | |
| * Mutability, but a subclass of Freezable may define a more efficient implementation such as an | |
| * integer field in the freezable value itself. | |
| * | |
| * <p>Call this function with a positive value when starting an iteration and with a negative | |
| * value when ending it. | |
| */ | |
| default boolean updateIteratorCount(int delta) { | |
| return mutability().updateIteratorCount(this, delta); | |
| } | |
| /** | |
| * Freezes this object (and not its contents). Use with care. | |
| * | |
| * <p>This method is optional (i.e. may throw {@link NotImplementedException}). | |
| * | |
| * <p>If this object's {@link Mutability} is 1) not frozen, and 2) has {@link | |
| * #allowUnsafeShallowFreeze} return true, then the object's {@code Mutability} reference is | |
| * updated to point to {@link #IMMUTABLE}. Otherwise, this method throws {@link | |
| * IllegalArgumentException}. | |
| * | |
| * <p>It is up to the caller to ensure that any contents of this {@code Freezable} are also | |
| * frozen in order to preserve/restore the invariant that an immutable value cannot contain a | |
| * mutable one. Note that {@link StarlarkMutable#isImmutable} correctness and thread-safety are | |
| * not guaranteed otherwise. | |
| */ | |
| default void unsafeShallowFreeze() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| /** | |
| * Throws {@link IllegalArgumentException} if the precondition for {@link #unsafeShallowFreeze} | |
| * is violated. To be used by implementors of {@link #unsafeShallowFreeze}. | |
| */ | |
| static void checkUnsafeShallowFreezePrecondition( | |
| Freezable freezable) { | |
| Mutability mutability = freezable.mutability(); | |
| if (mutability.isFrozen()) { | |
| // It's not safe to rewrite the Mutability pointer if this is already frozen, because we | |
| // could be accessed by multiple threads. | |
| throw new IllegalArgumentException( | |
| "cannot call unsafeShallowFreeze() on an object whose Mutability is already frozen"); | |
| } | |
| if (!mutability.allowsUnsafeShallowFreeze()) { | |
| throw new IllegalArgumentException( | |
| "cannot call unsafeShallowFreeze() on a mutable object whose Mutability's " | |
| + "allowsUnsafeShallowFreeze() == false"); | |
| } | |
| } | |
| } | |
| /** | |
| * A {@code Mutability} indicating that a value is deeply immutable. | |
| * | |
| * <p>It is not associated with any particular {@link StarlarkThread}. | |
| */ | |
| public static final Mutability IMMUTABLE = create("IMMUTABLE").freeze(); | |
| } |