diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java new file mode 100644 index 0000000000..7e6f4140a4 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java @@ -0,0 +1,253 @@ +// Copyright 2014 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.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.util.Fingerprint; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * The environment for Skylark. + */ +public class SkylarkEnvironment extends Environment { + + /** + * This set contains the variable names of all the successful lookups from the global + * environment. This is necessary because if in a function definition something + * reads a global variable after which a local variable with the same name is assigned an + * Exception needs to be thrown. + */ + private final Set<String> readGlobalVariables = new HashSet<>(); + + private ImmutableList<String> stackTrace; + + @Nullable private String fileContentHashCode; + + /** + * Creates a Skylark Environment for function calling, from the global Environment of the + * caller Environment (which must be a Skylark Environment). + */ + public static SkylarkEnvironment createEnvironmentForFunctionCalling( + Environment callerEnv, SkylarkEnvironment definitionEnv, + UserDefinedFunction function) throws EvalException { + if (callerEnv.getStackTrace().contains(function.getName())) { + throw new EvalException(function.getLocation(), "Recursion was detected when calling '" + + function.getName() + "' from '" + Iterables.getLast(callerEnv.getStackTrace()) + "'"); + } + ImmutableList<String> stackTrace = new ImmutableList.Builder<String>() + .addAll(callerEnv.getStackTrace()) + .add(function.getName()) + .build(); + SkylarkEnvironment childEnv = + // Always use the caller Environment's EventHandler. We cannot assume that the + // definition Environment's EventHandler is still working properly. + new SkylarkEnvironment(definitionEnv, stackTrace, callerEnv.eventHandler); + try { + for (String varname : callerEnv.propagatingVariables) { + childEnv.updateAndPropagate(varname, callerEnv.lookup(varname)); + } + } catch (NoSuchVariableException e) { + // This should never happen. + throw new IllegalStateException(e); + } + childEnv.disabledVariables = callerEnv.disabledVariables; + childEnv.disabledNameSpaces = callerEnv.disabledNameSpaces; + return childEnv; + } + + private SkylarkEnvironment(SkylarkEnvironment definitionEnv, ImmutableList<String> stackTrace, + EventHandler eventHandler) { + super(definitionEnv.getGlobalEnvironment()); + this.stackTrace = stackTrace; + this.eventHandler = Preconditions.checkNotNull(eventHandler, + "EventHandler cannot be null in an Environment which calls into Skylark"); + } + + /** + * Creates a global SkylarkEnvironment. + */ + public SkylarkEnvironment(EventHandler eventHandler, String astFileContentHashCode) { + super(); + stackTrace = ImmutableList.of(); + this.eventHandler = eventHandler; + this.fileContentHashCode = astFileContentHashCode; + } + + @VisibleForTesting + public SkylarkEnvironment(EventHandler eventHandler) { + this(eventHandler, null); + } + + public SkylarkEnvironment(SkylarkEnvironment globalEnv) { + super(globalEnv); + stackTrace = ImmutableList.of(); + this.eventHandler = globalEnv.eventHandler; + } + + @Override + public ImmutableList<String> getStackTrace() { + return stackTrace; + } + + /** + * Clones this Skylark global environment. + */ + public SkylarkEnvironment cloneEnv(EventHandler eventHandler) { + Preconditions.checkArgument(isGlobalEnvironment()); + SkylarkEnvironment newEnv = new SkylarkEnvironment(eventHandler, this.fileContentHashCode); + for (Entry<String, Object> entry : env.entrySet()) { + newEnv.env.put(entry.getKey(), entry.getValue()); + } + for (Map.Entry<Class<?>, Map<String, Function>> functionMap : functions.entrySet()) { + newEnv.functions.put(functionMap.getKey(), functionMap.getValue()); + } + return newEnv; + } + + /** + * Returns the global environment. Only works for Skylark environments. For the global Skylark + * environment this method returns this Environment. + */ + public SkylarkEnvironment getGlobalEnvironment() { + // If there's a parent that's the global environment, otherwise this is. + return parent != null ? (SkylarkEnvironment) parent : this; + } + + /** + * Returns true if this is a Skylark global environment. + */ + public boolean isGlobalEnvironment() { + return parent == null; + } + + /** + * Returns true if varname has been read as a global variable. + */ + public boolean hasBeenReadGlobalVariable(String varname) { + return readGlobalVariables.contains(varname); + } + + @Override + public boolean isSkylarkEnabled() { + return true; + } + + /** + * @return the value from the environment whose name is "varname". + * @throws NoSuchVariableException if the variable is not defined in the environment. + */ + @Override + public Object lookup(String varname) throws NoSuchVariableException { + if (disabledVariables.contains(varname)) { + throw new NoSuchVariableException(varname); + } + Object value = env.get(varname); + if (value == null) { + if (parent != null && parent.hasVariable(varname)) { + readGlobalVariables.add(varname); + return parent.lookup(varname); + } + throw new NoSuchVariableException(varname); + } + return value; + } + + /** + * Like <code>lookup(String)</code>, but instead of throwing an exception in + * the case where "varname" is not defined, "defaultValue" is returned instead. + */ + @Override + public Object lookup(String varname, Object defaultValue) { + throw new UnsupportedOperationException(); + } + + /** + * Updates the value of variable "varname" in the environment, corresponding + * to an AssignmentStatement. + */ + @Override + public void update(String varname, Object value) { + Preconditions.checkNotNull(value, "update(value == null)"); + env.put(varname, value); + } + + /** + * Returns the class of the variable or null if the variable does not exist. This function + * works only in the local Environment, it doesn't check the global Environment. + */ + public Class<?> getVariableType(String varname) { + Object variable = env.get(varname); + return variable != null ? EvalUtils.getSkylarkType(variable.getClass()) : null; + } + + /** + * Removes the functions and the modules (i.e. the symbol of the module from the top level + * Environment and the functions attached to it) from the Environment which should be present + * only during the loading phase. + */ + public void disableOnlyLoadingPhaseObjects() { + List<String> objectsToRemove = new ArrayList<>(); + List<Class<?>> modulesToRemove = new ArrayList<>(); + for (Map.Entry<String, Object> entry : env.entrySet()) { + Object object = entry.getValue(); + if (object instanceof SkylarkFunction) { + if (((SkylarkFunction) object).isOnlyLoadingPhase()) { + objectsToRemove.add(entry.getKey()); + } + } else if (object.getClass().isAnnotationPresent(SkylarkModule.class)) { + if (object.getClass().getAnnotation(SkylarkModule.class).onlyLoadingPhase()) { + objectsToRemove.add(entry.getKey()); + modulesToRemove.add(entry.getValue().getClass()); + } + } + } + for (String symbol : objectsToRemove) { + disabledVariables.add(symbol); + } + for (Class<?> moduleClass : modulesToRemove) { + disabledNameSpaces.add(moduleClass); + } + } + + public void handleEvent(Event event) { + eventHandler.handle(event); + } + + /** + * Returns a hash code calculated from the hash code of this Environment and the + * transitive closure of other Environments it loads. + */ + public String getTransitiveFileContentHashCode() { + Fingerprint fingerprint = new Fingerprint(); + fingerprint.addString(Preconditions.checkNotNull(fileContentHashCode)); + // Calculate a new hash from the hash of the loaded Environments. + for (SkylarkEnvironment env : importedExtensions.values()) { + fingerprint.addString(env.getTransitiveFileContentHashCode()); + } + return fingerprint.hexDigestAndReset(); + } +} |