/* * 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.errorprone.dataflow; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Sets.intersection; import static javax.lang.model.element.ElementKind.EXCEPTION_PARAMETER; import static javax.lang.model.element.ElementKind.LOCAL_VARIABLE; import static javax.lang.model.element.ElementKind.PARAMETER; import static javax.lang.model.element.ElementKind.RESOURCE_VARIABLE; import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; import javax.lang.model.element.Element; import org.checkerframework.dataflow.analysis.AbstractValue; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.Store; import org.checkerframework.dataflow.cfg.node.LocalVariableNode; /** * Immutable map from each local variable to its {@link AbstractValue}. Note that, while the * interface is written in terms of <b>nodes</b>, the stored data is indexed by variable * <b>declaration</b>, so values persist across nodes. * * <p>To derive a new instance, {@linkplain #toBuilder() create a builder} from an old instance. To * start from scratch, call {@link #empty()}. * * @author deminguyen@google.com (Demi Nguyen) */ public final class LocalStore<V extends AbstractValue<V>> implements Store<LocalStore<V>>, LocalVariableValues<V> { @SuppressWarnings({"unchecked", "rawtypes"}) // fully variant private static final LocalStore<?> EMPTY = new LocalStore(ImmutableMap.of()); @SuppressWarnings("unchecked") // fully variant public static <V extends AbstractValue<V>> LocalStore<V> empty() { return (LocalStore<V>) EMPTY; } private final ImmutableMap<Element, V> contents; private LocalStore(Map<Element, V> contents) { this.contents = ImmutableMap.copyOf(contents); } @Override public V valueOfLocalVariable(LocalVariableNode node, V defaultValue) { V result = getInformation(node.getElement()); return result != null ? result : defaultValue; } /** * Returns the value for the given variable. {@code element} must come from a call to {@link * LocalVariableNode#getElement()} or {@link * org.checkerframework.javacutil.TreeUtils#elementFromDeclaration} ({@link * org.checkerframework.dataflow.cfg.node.VariableDeclarationNode#getTree()}). */ @Nullable private V getInformation(Element element) { checkElementType(element); return contents.get(checkNotNull(element)); } public Builder<V> toBuilder() { return new Builder<V>(this); } /** * Builder for {@link LocalStore} instances. To obtain an instance, obtain a {@link LocalStore} * (such as {@link LocalStore#empty()}), and call {@link LocalStore#toBuilder() toBuilder()} on * it. */ public static final class Builder<V extends AbstractValue<V>> { private final Map<Element, V> contents; Builder(LocalStore<V> prototype) { contents = new HashMap<>(prototype.contents); } /** * Sets the value for the given variable. {@code element} must come from a call to {@link * LocalVariableNode#getElement()} or {@link * org.checkerframework.javacutil.TreeUtils#elementFromDeclaration} ({@link * org.checkerframework.dataflow.cfg.node.VariableDeclarationNode#getTree()}). */ public Builder<V> setInformation(Element element, V value) { checkElementType(element); contents.put(checkNotNull(element), checkNotNull(value)); return this; } public LocalStore<V> build() { return new LocalStore<V>(contents); } } @Override public LocalStore<V> copy() { // No need to copy because it's immutable. return this; } @Override public LocalStore<V> leastUpperBound(LocalStore<V> other) { Builder<V> result = LocalStore.<V>empty().toBuilder(); for (Element var : intersection(contents.keySet(), other.contents.keySet())) { result.contents.put(var, contents.get(var).leastUpperBound(other.contents.get(var))); } return result.build(); } @Override public boolean equals(Object o) { if (!(o instanceof LocalStore)) { return false; } LocalStore<?> other = (LocalStore<?>) o; return contents.equals(other.contents); } @Override public int hashCode() { return contents.hashCode(); } @Override public String toString() { return contents.toString(); } @Override public boolean canAlias(FlowExpressions.Receiver a, FlowExpressions.Receiver b) { return true; } @Override public boolean hasDOToutput() { return false; } @Override public String toDOToutput() { throw new UnsupportedOperationException("DOT output not supported"); } private static void checkElementType(Element element) { checkArgument( element.getKind() == LOCAL_VARIABLE || element.getKind() == PARAMETER || element.getKind() == EXCEPTION_PARAMETER || element.getKind() == RESOURCE_VARIABLE, "unexpected element type: %s (%s)", element.getKind(), element); } }