aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/packages/AttributeContainer.java
blob: 3ec0a5c5027cefbbb26eb5fb7cc0d4bfa4c7cef1 (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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// Copyright 2014 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.packages;

import com.google.common.annotations.VisibleForTesting;
import com.google.devtools.build.lib.events.Location;
import java.util.Arrays;
import javax.annotation.Nullable;

/**
 * Provides attribute setting and retrieval for a Rule. Encapsulating attribute access
 * here means it can be passed around independently of the Rule itself. In particular,
 * it can be consumed by independent {@link AttributeMap} instances that can apply
 * varying kinds of logic for determining the "value" of an attribute. For example,
 * a configurable attribute's "value" may be a { config --> value } dictionary
 * or a configuration-bound lookup on that dictionary, depending on the context in
 * which it's requested.
 *
 * <p>This class provides the lowest-level access to attribute information. It is *not*
 * intended to be a robust public interface, but rather just an input to {@link AttributeMap}
 * instances. Use those instances for all domain-level attribute access.
 */
public class AttributeContainer {

  private final RuleClass ruleClass;

  // Attribute values, keyed by attribute index:
  private final Object[] attributeValues;

  // Holds two lists of attribute indices.
  // The first byte gives the length of the first list.
  // The first list records which attributes were set explicitly in the BUILD file.
  // The second list ends at the end of the array.
  // The second list records which attributes have Locations, in reverse order
  // from the attributeLocations array.
  // Between the lists there may be unused zero bytes (zeros are forbidden within each list).
  private byte[] state;

  // Attribute locations, packed:
  private Location[] attributeLocations;


  /**
   * Create a container for a rule of the given rule class.
   */
  public AttributeContainer(RuleClass ruleClass) {
   this(ruleClass, EMPTY_LOCATIONS);
  }

  AttributeContainer(RuleClass ruleClass, Location[] locations) {
    int n = ruleClass.getAttributeCount();
    if (n > 254) {
      // We reserve the zero byte as a hole/sentinel inside state[].
      // If you hit this limit, replace byte with char and remove the masking with 0xff.
      throw new AssertionError("can't pack " + n + " rule indices into bytes");
    }
    this.ruleClass = ruleClass;
    this.attributeValues = new Object[n];
    this.state = EMPTY_STATE;
    this.attributeLocations = locations;
  }

  private static final byte[] EMPTY_STATE = {0};
  private static final Location[] EMPTY_LOCATIONS = {};

  /**
   * Returns an attribute value by name, or null on no match.
   */
  @Nullable
  public Object getAttr(String attrName) {
    Integer idx = ruleClass.getAttributeIndex(attrName);
    return idx != null ? attributeValues[idx] : null;
  }

  /**
   * {@see #isAttributeValueExplicitlySpecified(String)}
   */
  public boolean isAttributeValueExplicitlySpecified(Attribute attribute) {
    return isAttributeValueExplicitlySpecified(attribute.getName());
  }

  /**
   * Returns true iff the value of the specified attribute is explicitly set in the BUILD file.
   * This returns true also if the value explicity specified in the BUILD file is the same as the
   * attribute's default value. In addition, this method return false if the rule has no attribute
   * with the given name.
   */
  public boolean isAttributeValueExplicitlySpecified(String attributeName) {
    Integer idx = ruleClass.getAttributeIndex(attributeName);
    return idx != null && getExplicit(idx);
  }

 /**
  * Returns the number of elements of state[] currently used to store
  * indices of "explicitly set" attributes.
  */
  private int explicitCount() {
    return 0xff & state[0];
  }

  private boolean getExplicit(int index) {
    int n = explicitCount();
    for (int i = 1; i <= n; ++i) {
      if ((0xff & state[i]) == index + 1) {
        return true;
      }
    }
    return false;
  }

  private int getLocationIndex(int index) {
    int n = explicitCount();
    for (int i = state.length - 1; i > n; --i) {
      if ((0xff & state[i]) == index + 1) {
        return state.length - 1 - i;
      }
    }
    return -1;
  }

  private void setExplicit(int index) {
    if (getExplicit(index)) {
      return;
    }
    ensureSpace();
    int n = explicitCount() + 1;
    state[0] = (byte) n;
    state[n] = (byte) (index + 1);
  }

  private int addLocationIndex(int index) {
    ensureSpace();
    for (int i = state.length - 1; ; --i) {
      if (i <= explicitCount()) {
        throw new AssertionError("ensureSpace() did not insert a zero");
      }
      if (state[i] == 0) {
        state[i] = (byte) (index + 1);
        return state.length - 1 - i;
      }
    }
  }

  /**
   * Ensures that the state[n] byte is equal to the sentinel value 0, so there is room for another
   * attribute's explicit bit to be set, or for an attribute's location to be set.
   */
  private void ensureSpace() {
    int n = explicitCount() + 1;
    if (n < state.length && state[n] == 0) {
      return;
    }
    // Grow up to the next multiple of eight bytes, as the object will be
    // aligned to eight bytes anyway.  Insert zeros between the two lists.
    byte[] newState = new byte[(state.length | 7) + 1];
    // Copy stored explicit attributes to the beginning of the array.
    System.arraycopy(state, 0, newState, 0, n);
    // Copy stored attribute locations to the *end* of the array.
    int oldLocations = state.length - n;
    System.arraycopy(state, n, newState, newState.length - oldLocations, oldLocations);
    state = newState;
  }

  /**
   * Returns the location of the attribute definition for this rule, or null if not found.
   */
  public Location getAttributeLocation(String attrName) {
    Integer idx = ruleClass.getAttributeIndex(attrName);
    int locationIndex = idx != null ? getLocationIndex(idx) : -1;
    return locationIndex >= 0 ? attributeLocations[locationIndex] : null;
  }

  Object getAttributeValue(int index) {
    return attributeValues[index];
  }

  void setAttributeValue(Attribute attribute, Object value, boolean explicit) {
    String name = attribute.getName();
    Integer index = ruleClass.getAttributeIndex(name);
    if (!explicit && getExplicit(index)) {
      throw new IllegalArgumentException("attribute " + name + " already explicitly set");
    }
    attributeValues[index] = value;
    if (explicit) {
      setExplicit(index);
    }
  }

  // This sets the attribute "explicitly" as if it came from the BUILD file.
  // At present, the sole use of this is for the test_suite.$implicit_tests
  // attribute, which is synthesized during package loading.  We do want to
  // consider that "explicitly set" so that it appears in query output.
  void setAttributeValueByName(String attrName, Object value) {
    setAttributeValue(ruleClass.getAttributeByName(attrName), value, true);
  }

  void setAttributeLocation(int attrIndex, Location location) {
    int locationIndex = getLocationIndex(attrIndex);
    if (locationIndex >= 0) {
      throw new IllegalArgumentException("already have a location for attribute "
          + ruleClass.getAttribute(attrIndex).getName() + ": " + attributeLocations[locationIndex]);
    }
    locationIndex = addLocationIndex(attrIndex);
    if (locationIndex >= attributeLocations.length) {
      // Grow by two references, as the object will be aligned to eight bytes anyway.
      attributeLocations = Arrays.copyOf(attributeLocations, attributeLocations.length + 2);
    }
    attributeLocations[locationIndex] = location;
  }

  @VisibleForTesting
  void setAttributeLocation(Attribute attribute, Location location) {
    Integer index = ruleClass.getAttributeIndex(attribute.getName());
    setAttributeLocation(index, location);
  }
}