blob: 7aedca2c82962823ff24d4893d70766de0cf9c96 [file] [log] [blame]
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.syntax;
16
17import com.google.common.base.Preconditions;
Laurent Le Brun352b9da2015-04-16 14:59:59 +000018import com.google.common.collect.ImmutableSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010019import com.google.devtools.build.lib.events.Location;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010020
21import java.util.HashMap;
22import java.util.HashSet;
Laurent Le Brun68743162015-05-13 13:18:09 +000023import java.util.List;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024import java.util.Map;
25import java.util.Set;
26import java.util.Stack;
27
28/**
29 * An Environment for the semantic checking of Skylark files.
30 *
31 * @see Statement#validate
32 * @see Expression#validate
33 */
34public class ValidationEnvironment {
35
36 private final ValidationEnvironment parent;
37
Laurent Le Brun352b9da2015-04-16 14:59:59 +000038 private Set<String> variables = new HashSet<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010039
40 private Map<String, Location> variableLocations = new HashMap<>();
41
42 private Set<String> readOnlyVariables = new HashSet<>();
43
44 // A stack of variable-sets which are read only but can be assigned in different
45 // branches of if-else statements.
46 private Stack<Set<String>> futureReadOnlyVariables = new Stack<>();
47
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010048 // Whether this validation environment is not modified therefore clonable or not.
49 private boolean clonable;
Florian Weikert917ceaa2015-06-10 13:54:26 +000050
51 /**
52 * Tracks the number of nested for loops that contain the statement that is currently being
53 * validated
54 */
55 private int loopCount = 0;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010056
Laurent Le Brun352b9da2015-04-16 14:59:59 +000057 public ValidationEnvironment(Set<String> builtinVariables) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010058 parent = null;
Laurent Le Brun352b9da2015-04-16 14:59:59 +000059 variables.addAll(builtinVariables);
60 readOnlyVariables.addAll(builtinVariables);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010061 clonable = true;
62 }
63
Laurent Le Brun352b9da2015-04-16 14:59:59 +000064 private ValidationEnvironment(Set<String> builtinVariables, Set<String> readOnlyVariables) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010065 parent = null;
Laurent Le Brun352b9da2015-04-16 14:59:59 +000066 this.variables = new HashSet<>(builtinVariables);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010067 this.readOnlyVariables = new HashSet<>(readOnlyVariables);
68 clonable = false;
69 }
70
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +000071 // ValidationEnvironment for a new Environment()
Laurent Le Brun352b9da2015-04-16 14:59:59 +000072 private static ImmutableSet<String> globalTypes = ImmutableSet.of("False", "True", "None");
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +000073
74 public ValidationEnvironment() {
75 this(globalTypes);
76 }
77
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010078 @Override
79 public ValidationEnvironment clone() {
80 Preconditions.checkState(clonable);
Laurent Le Brun352b9da2015-04-16 14:59:59 +000081 return new ValidationEnvironment(variables, readOnlyVariables);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010082 }
83
84 /**
85 * Creates a local ValidationEnvironment to validate user defined function bodies.
86 */
Laurent Le Brun8d966f72015-04-15 18:47:34 +000087 public ValidationEnvironment(ValidationEnvironment parent) {
Laurent Le Brun8fc603c2015-03-24 10:04:50 +000088 // Don't copy readOnlyVariables: Variables may shadow global values.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010089 this.parent = parent;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010090 this.clonable = false;
91 }
92
93 /**
94 * Returns true if this ValidationEnvironment is top level i.e. has no parent.
95 */
96 public boolean isTopLevel() {
97 return parent == null;
98 }
99
100 /**
Laurent Le Brun964d8d52015-04-13 12:15:04 +0000101 * Declare a variable and add it to the environment.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100102 */
Laurent Le Brun964d8d52015-04-13 12:15:04 +0000103 public void declare(String varname, Location location)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100104 throws EvalException {
105 checkReadonly(varname, location);
106 if (parent == null) { // top-level values are immutable
107 readOnlyVariables.add(varname);
108 if (!futureReadOnlyVariables.isEmpty()) {
109 // Currently validating an if-else statement
110 futureReadOnlyVariables.peek().add(varname);
111 }
112 }
Laurent Le Brun352b9da2015-04-16 14:59:59 +0000113 variables.add(varname);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100114 variableLocations.put(varname, location);
115 clonable = false;
116 }
117
118 private void checkReadonly(String varname, Location location) throws EvalException {
119 if (readOnlyVariables.contains(varname)) {
120 throw new EvalException(location, String.format("Variable %s is read only", varname));
121 }
122 }
123
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100124 /**
125 * Returns true if the symbol exists in the validation environment.
126 */
127 public boolean hasSymbolInEnvironment(String varname) {
Laurent Le Brun352b9da2015-04-16 14:59:59 +0000128 return variables.contains(varname) || topLevel().variables.contains(varname);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100129 }
130
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100131 private ValidationEnvironment topLevel() {
132 return Preconditions.checkNotNull(parent == null ? this : parent);
133 }
134
135 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100136 * Starts a session with temporarily disabled readonly checking for variables between branches.
137 * This is useful to validate control flows like if-else when we know that certain parts of the
138 * code cannot both be executed.
139 */
140 public void startTemporarilyDisableReadonlyCheckSession() {
141 futureReadOnlyVariables.add(new HashSet<String>());
142 clonable = false;
143 }
144
145 /**
146 * Finishes the session with temporarily disabled readonly checking.
147 */
148 public void finishTemporarilyDisableReadonlyCheckSession() {
149 Set<String> variables = futureReadOnlyVariables.pop();
150 readOnlyVariables.addAll(variables);
151 if (!futureReadOnlyVariables.isEmpty()) {
152 futureReadOnlyVariables.peek().addAll(variables);
153 }
154 clonable = false;
155 }
156
157 /**
158 * Finishes a branch of temporarily disabled readonly checking.
159 */
160 public void finishTemporarilyDisableReadonlyCheckBranch() {
161 readOnlyVariables.removeAll(futureReadOnlyVariables.peek());
162 clonable = false;
163 }
Laurent Le Brun68743162015-05-13 13:18:09 +0000164
165 /**
166 * Validates the AST and runs static checks.
167 */
168 public void validateAst(List<Statement> statements) throws EvalException {
169 // Add every function in the environment before validating. This is
170 // necessary because functions may call other functions defined
171 // later in the file.
172 for (Statement statement : statements) {
173 if (statement instanceof FunctionDefStatement) {
174 FunctionDefStatement fct = (FunctionDefStatement) statement;
175 declare(fct.getIdent().getName(), fct.getLocation());
176 }
177 }
178
179 for (Statement statement : statements) {
180 statement.validate(this);
181 }
182 }
Florian Weikert917ceaa2015-06-10 13:54:26 +0000183
184 /**
185 * Returns whether the current statement is inside a for loop (either in this environment or one
186 * of its parents)
187 *
188 * @return True if the current statement is inside a for loop
189 */
190 public boolean isInsideLoop() {
191 return (loopCount > 0);
192 }
193
194 /**
195 * Signals that the block of a for loop was entered
196 */
197 public void enterLoop() {
198 ++loopCount;
199 }
200
201 /**
202 * Signals that the block of a for loop was left
203 *
204 * @param location The current location
205 * @throws EvalException If there was no corresponding call to
206 * {@code ValidationEnvironment#enterLoop}
207 */
208 public void exitLoop(Location location) throws EvalException {
209 Preconditions.checkState(loopCount > 0);
210 --loopCount;
211 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100212}