aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/Mutability.java
blob: c21428cc1d09f57e72ee03baae97e2f85fdaa14d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 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.Preconditions;

import java.io.Serializable;

/**
 * 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, Serializable {
  private boolean isMutable;
  private final String 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(String 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) {
    // For efficiency, we could be lazy and use formattable instead of format,
    // but the result is going to be serialized, anyway.
    return new Mutability(Printer.format(pattern, arguments));
  }

  String 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;
  }

  /**
   * Freezes this Mutability
   * @return it in fluent style.
   */
  public Mutability freeze() {
    close();
    return this;
  }

  /**
   * 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");
    }
  }

  public static final Mutability IMMUTABLE = create("IMMUTABLE").freeze();
}