blob: b0e766d6bfd8819ffc249d1f763908a9036df2bd [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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
Laurent Le Brun8e965b82016-08-03 11:50:24 +000017import com.google.devtools.build.lib.events.Event;
18import com.google.devtools.build.lib.events.EventHandler;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010019import com.google.devtools.build.lib.events.Location;
Mark Schaller6df81792015-12-10 18:47:47 +000020import com.google.devtools.build.lib.util.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import 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 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000034public final class ValidationEnvironment {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035
36 private final ValidationEnvironment parent;
37
Laurent Le Brune51a4d22016-10-11 18:04:16 +000038 private final Set<String> variables = new HashSet<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010039
Laurent Le Brune51a4d22016-10-11 18:04:16 +000040 private final Map<String, Location> variableLocations = new HashMap<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041
Laurent Le Brune51a4d22016-10-11 18:04:16 +000042 private final Set<String> readOnlyVariables = new HashSet<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010043
44 // A stack of variable-sets which are read only but can be assigned in different
45 // branches of if-else statements.
Laurent Le Brune51a4d22016-10-11 18:04:16 +000046 private final Stack<Set<String>> futureReadOnlyVariables = new Stack<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010047
Florian Weikert917ceaa2015-06-10 13:54:26 +000048 /**
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000049 * Create a ValidationEnvironment for a given global Environment.
Francois-Rene Rideau6e7160d2015-08-26 17:22:35 +000050 */
51 public ValidationEnvironment(Environment env) {
Francois-Rene Rideau6e7160d2015-08-26 17:22:35 +000052 Preconditions.checkArgument(env.isGlobal());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010053 parent = null;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000054 Set<String> builtinVariables = env.getVariableNames();
Laurent Le Brun352b9da2015-04-16 14:59:59 +000055 variables.addAll(builtinVariables);
56 readOnlyVariables.addAll(builtinVariables);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010057 }
58
59 /**
60 * Creates a local ValidationEnvironment to validate user defined function bodies.
61 */
Laurent Le Brun8d966f72015-04-15 18:47:34 +000062 public ValidationEnvironment(ValidationEnvironment parent) {
Laurent Le Brun8fc603c2015-03-24 10:04:50 +000063 // Don't copy readOnlyVariables: Variables may shadow global values.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010064 this.parent = parent;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010065 }
66
67 /**
68 * Returns true if this ValidationEnvironment is top level i.e. has no parent.
69 */
70 public boolean isTopLevel() {
71 return parent == null;
72 }
73
74 /**
Laurent Le Brun964d8d52015-04-13 12:15:04 +000075 * Declare a variable and add it to the environment.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010076 */
Laurent Le Brun964d8d52015-04-13 12:15:04 +000077 public void declare(String varname, Location location)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010078 throws EvalException {
79 checkReadonly(varname, location);
80 if (parent == null) { // top-level values are immutable
81 readOnlyVariables.add(varname);
82 if (!futureReadOnlyVariables.isEmpty()) {
83 // Currently validating an if-else statement
84 futureReadOnlyVariables.peek().add(varname);
85 }
86 }
Laurent Le Brun352b9da2015-04-16 14:59:59 +000087 variables.add(varname);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010088 variableLocations.put(varname, location);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010089 }
90
91 private void checkReadonly(String varname, Location location) throws EvalException {
92 if (readOnlyVariables.contains(varname)) {
Laurent Le Brunfa407e52016-11-04 15:53:08 +000093 throw new EvalException(
94 location,
95 String.format("Variable %s is read only", varname),
96 "https://bazel.build/versions/master/docs/skylark/errors/read-only-variable.html");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010097 }
98 }
99
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100100 /**
101 * Returns true if the symbol exists in the validation environment.
102 */
103 public boolean hasSymbolInEnvironment(String varname) {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000104 return variables.contains(varname)
105 || (parent != null && topLevel().variables.contains(varname));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100106 }
107
Laurent Le Brune102a2d2017-01-02 12:06:18 +0000108 /** Returns the set of all accessible symbols (both local and global) */
109 public Set<String> getAllSymbols() {
110 Set<String> all = new HashSet<>();
111 all.addAll(variables);
112 if (parent != null) {
113 all.addAll(parent.getAllSymbols());
114 }
115 return all;
116 }
117
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100118 private ValidationEnvironment topLevel() {
119 return Preconditions.checkNotNull(parent == null ? this : parent);
120 }
121
122 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100123 * Starts a session with temporarily disabled readonly checking for variables between branches.
124 * This is useful to validate control flows like if-else when we know that certain parts of the
Francois-Rene Rideau6e7160d2015-08-26 17:22:35 +0000125 * code cannot both be executed.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100126 */
127 public void startTemporarilyDisableReadonlyCheckSession() {
128 futureReadOnlyVariables.add(new HashSet<String>());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100129 }
130
131 /**
132 * Finishes the session with temporarily disabled readonly checking.
133 */
134 public void finishTemporarilyDisableReadonlyCheckSession() {
135 Set<String> variables = futureReadOnlyVariables.pop();
136 readOnlyVariables.addAll(variables);
137 if (!futureReadOnlyVariables.isEmpty()) {
138 futureReadOnlyVariables.peek().addAll(variables);
139 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100140 }
141
142 /**
143 * Finishes a branch of temporarily disabled readonly checking.
144 */
145 public void finishTemporarilyDisableReadonlyCheckBranch() {
146 readOnlyVariables.removeAll(futureReadOnlyVariables.peek());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100147 }
Laurent Le Brun68743162015-05-13 13:18:09 +0000148
149 /**
150 * Validates the AST and runs static checks.
151 */
152 public void validateAst(List<Statement> statements) throws EvalException {
153 // Add every function in the environment before validating. This is
154 // necessary because functions may call other functions defined
155 // later in the file.
156 for (Statement statement : statements) {
157 if (statement instanceof FunctionDefStatement) {
158 FunctionDefStatement fct = (FunctionDefStatement) statement;
159 declare(fct.getIdent().getName(), fct.getLocation());
160 }
161 }
162
163 for (Statement statement : statements) {
164 statement.validate(this);
165 }
166 }
Florian Weikert917ceaa2015-06-10 13:54:26 +0000167
Laurent Le Brun8e965b82016-08-03 11:50:24 +0000168 public boolean validateAst(List<Statement> statements, EventHandler eventHandler) {
169 try {
170 validateAst(statements);
171 return true;
172 } catch (EvalException e) {
173 if (!e.isDueToIncompleteAST()) {
174 eventHandler.handle(Event.error(e.getLocation(), e.getMessage()));
175 }
176 return false;
177 }
178 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100179}