diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax/Mutability.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/syntax/Mutability.java | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Mutability.java b/src/main/java/com/google/devtools/build/lib/syntax/Mutability.java new file mode 100644 index 0000000000..5120f8d0d2 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/syntax/Mutability.java @@ -0,0 +1,136 @@ +// Copyright 2015 Google Inc. 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; + +/** + * A Mutability is a resource associated with an {@link Environment} during an evaluation, + * that gives those who possess it a revokable capability to mutate this Environment and + * the objects created within that {@link Environment}. At the end of the evaluation, + * the resource is irreversibly closed, at which point the capability is revoked, + * and it is not possible to mutate either this {@link Environment} or these objects. + * + * <p>Evaluation in an {@link Environment} may thus mutate objects created in the same + * {@link Environment}, but may not mutate {@link Freezable} objects (lists, sets, dicts) + * created in a previous, concurrent or future {@link Environment}, and conversely, + * the bindings and objects in this {@link Environment} will be protected from + * mutation by evaluation in a different {@link Environment}. + * + * <p>Only a single thread may use the {@link Environment} and objects created within it while the + * Mutability is still mutable as tested by {@link #isMutable}. Once the Mutability resource + * is closed, the {@link Environment} and its objects are immutable and can be simultaneously used + * by arbitrarily many threads. + * + * <p>The safe usage of a Mutability requires to always use try-with-resource style: + * <code>try(Mutability mutability = Mutability.create(fmt, ...)) { ... }</code> + * Thus, you can create a Mutability, build an {@link Environment}, mutate that {@link Environment} + * and create mutable objects as you evaluate in that {@link Environment}, and finally return the + * resulting {@link Environment}, at which point the resource is closed, and the {@link Environment} + * and the objects it contains all become immutable. + * (Unsafe usage is allowed only in test code that is not part of the Bazel jar.) + */ +// TODO(bazel-team): When we start using Java 8, this safe usage pattern can be enforced +// through the use of a higher-order function. +public final class Mutability implements AutoCloseable { + private boolean isMutable; + private final Object annotation; // For error reporting + + /** + * Creates a Mutability. + * @param annotation an Object used for error reporting, + * describing to the user the context in which this Mutability was active. + */ + private Mutability(Object annotation) { + this.isMutable = true; + this.annotation = Preconditions.checkNotNull(annotation); + } + + /** + * Creates a Mutability. + * @param pattern is a {@link Printer#format} pattern used to lazily produce a string + * for error reporting + * @param arguments are the optional {@link Printer#format} arguments to produce that string + */ + public static Mutability create(String pattern, Object... arguments) { + return new Mutability(Printer.formattable(pattern, arguments)); + } + + Object getAnnotation() { + return annotation; + } + + @Override + public String toString() { + return String.format(isMutable ? "[%s]" : "(%s)", annotation); + } + + boolean isMutable() { + return isMutable; + } + + /** + * Freezes this Mutability, marking as immutable all {@link Freezable} objects that use it. + */ + @Override + public void close() { + isMutable = false; + } + + /** + * A MutabilityException will be thrown when the user attempts to mutate an object he shouldn't. + */ + static class MutabilityException extends Exception { + MutabilityException(String message) { + super(message); + } + } + + /** + * Each {@link Freezable} object possesses a revokable Mutability attribute telling whether + * the object is still mutable. All {@link Freezable} objects created in the same + * {@link Environment} will share the same Mutability, inherited from this {@link Environment}. + * Only evaluation in the same {@link Environment} is allowed to mutate these objects, + * and only until the Mutability is irreversibly revoked. + */ + public interface Freezable { + /** + * Returns the {@link Mutability} capability associated with this Freezable object. + */ + Mutability mutability(); + } + + /** + * Checks that this Freezable object can be mutated from the given {@link Environment}. + * @param object a Freezable object that we check is still mutable. + * @param env the {@link Environment} attempting the mutation. + * @throws MutabilityException when the object was frozen already, or is from another context. + */ + public static void checkMutable(Freezable object, Environment env) + throws MutabilityException { + if (!object.mutability().isMutable()) { + throw new MutabilityException("trying to mutate a frozen object"); + } + // Consider an {@link Environment} e1, in which is created {@link UserDefinedFunction} f1, + // that closes over some variable v1 bound to list l1. If somehow, via the magic of callbacks, + // f1 or l1 is passed as argument to some function f2 evaluated in {@link environment} e2 + // while e1 is be mutable, e2, being a different {@link Environment}, should not be + // allowed to mutate objects from e1. It's a bug, that shouldn't happen in our current code + // base, so we throw an AssertionError. If in the future such situations are allowed to happen, + // then we should throw a MutabilityException instead. + if (!object.mutability().equals(env.mutability())) { + throw new AssertionError("trying to mutate an object from a different context"); + } + } +} |