();
this.isAliased = false;
}
/** Returns the JS variable name to be substituted for this string. */
String getVariableName(String stringLiteral) {
if (aliasName == null) {
aliasName =
encodeStringAsIdentifier(STRING_ALIAS_PREFIX, stringLiteral);
}
return aliasName;
}
/**
* Returns a legal identifier that uniquely characterizes string 's'.
*
* We want the identifier to be a function of the string value because that
* makes the identifiers stable as the program is changed.
*
* The digits of a good hash function would be adequate, but for short
* strings the following algorithm is easier to work with for unit tests.
*
* ASCII alphanumerics are mapped to themselves. Other characters are
* mapped to $XXX or $XXX_ where XXX is a variable number of hex digits.
* The underscore is inserted as necessary to avoid ambiguity when the
* character following is a hex digit. E.g. '\n1' maps to '$a_1',
* distinguished by the underscore from '\u00A1' which maps to '$a1'.
*
* If the string is short enough, this is sufficient. Longer strings are
* truncated after encoding an initial prefix and appended with a hash
* value.
*/
String encodeStringAsIdentifier(String prefix, String s) {
// Limit to avoid generating very long identifiers
final int MAX_LIMIT = 20;
final int length = s.length();
final int limit = Math.min(length, MAX_LIMIT);
StringBuilder sb = new StringBuilder();
sb.append(prefix);
boolean protectHex = false;
for (int i = 0; i < limit; i++) {
char ch = s.charAt(i);
if (protectHex) {
if ((ch >= '0' && ch <= '9') ||
(ch >= 'a' && ch <= 'f')) { // toHexString generate lowercase
sb.append('_');
}
protectHex = false;
}
if ((ch >= '0' && ch <= '9') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= 'a' && ch <= 'z')) {
sb.append(ch);
} else {
sb.append('$');
sb.append(Integer.toHexString(ch));
protectHex = true;
}
}
if (length == limit) {
return sb.toString();
}
// The identifier is not unique because we omitted part, so add a
// checksum as a hashcode.
CRC32 crc32 = new CRC32();
crc32.update(s.getBytes());
long hash = crc32.getValue() & unitTestHashReductionMask;
sb.append('_');
sb.append(Long.toHexString(hash));
String encoded = sb.toString();
if (!usedHashedAliases.add(encoded)) {
// A collision has been detected (which is very rare). Use the sequence
// id to break the tie. This means that the name is no longer invariant
// across source code changes and recompilations.
encoded += "_" + id;
}
return encoded;
}
}
}
closure-compiler-20130227+dfsg1/src/com/google/javascript/jscomp/AmbiguateProperties.java 0000664 0000000 0000000 00000051353 12115204405 0031341 0 ustar 00root root 0000000 0000000 /*
* Copyright 2008 The Closure Compiler Authors.
*
* 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.javascript.jscomp;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.TypeValidator.TypeMismatch;
import com.google.javascript.jscomp.graph.AdjacencyGraph;
import com.google.javascript.jscomp.graph.Annotation;
import com.google.javascript.jscomp.graph.GraphColoring;
import com.google.javascript.jscomp.graph.GraphColoring.GreedyGraphColoring;
import com.google.javascript.jscomp.graph.GraphNode;
import com.google.javascript.jscomp.graph.SubGraph;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.BitSet;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
/**
* Renames unrelated properties to the same name, using type information.
* This allows better compression as more properties can be given short names.
*
* Properties are considered unrelated if they are never referenced from the
* same type or from a subtype of each others' types, thus this pass is only
* effective if type checking is enabled.
*
* Example:
*
* Foo.fooprop = 0;
* Foo.fooprop2 = 0;
* Bar.barprop = 0;
*
*
* becomes:
*
*
* Foo.a = 0;
* Foo.b = 0;
* Bar.a = 0;
*
*
*/
class AmbiguateProperties implements CompilerPass {
private static final Logger logger = Logger.getLogger(
AmbiguateProperties.class.getName());
private final AbstractCompiler compiler;
private final List stringNodesToRename = Lists.newArrayList();
private final char[] reservedCharacters;
/** Map from property name to Property object */
private final Map propertyMap = Maps.newHashMap();
/** Property names that don't get renamed */
private final Set externedNames = Sets.newHashSet();
/** Names to which properties shouldn't be renamed, to avoid name conflicts */
private final Set quotedNames = Sets.newHashSet();
/** Map from original property name to new name. */
private final Map renamingMap = Maps.newHashMap();
/**
* Sorts Property objects by their count, breaking ties alphabetically to
* ensure a deterministic total ordering.
*/
private static final Comparator FREQUENCY_COMPARATOR =
new Comparator() {
@Override
public int compare(Property p1, Property p2) {
if (p1.numOccurrences != p2.numOccurrences) {
return p2.numOccurrences - p1.numOccurrences;
}
return p1.oldName.compareTo(p2.oldName);
}
};
/** A map from JSType to a unique representative Integer. */
private BiMap intForType = HashBiMap.create();
/**
* A map from JSType to JSTypeBitSet representing the types related
* to the type.
*/
private Map relatedBitsets = Maps.newHashMap();
/** A set of types that invalidate properties from ambiguation. */
private final Set invalidatingTypes;
/**
* Prefix of properties to skip renaming. These should be renamed in the
* RenameProperties pass.
*/
static final String SKIP_PREFIX = "JSAbstractCompiler";
AmbiguateProperties(AbstractCompiler compiler,
char[] reservedCharacters) {
Preconditions.checkState(compiler.getLifeCycleStage().isNormalized());
this.compiler = compiler;
this.reservedCharacters = reservedCharacters;
JSTypeRegistry r = compiler.getTypeRegistry();
invalidatingTypes = Sets.newHashSet(
r.getNativeType(JSTypeNative.ALL_TYPE),
r.getNativeType(JSTypeNative.NO_OBJECT_TYPE),
r.getNativeType(JSTypeNative.NO_TYPE),
r.getNativeType(JSTypeNative.NULL_TYPE),
r.getNativeType(JSTypeNative.VOID_TYPE),
r.getNativeType(JSTypeNative.FUNCTION_FUNCTION_TYPE),
r.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
r.getNativeType(JSTypeNative.FUNCTION_PROTOTYPE),
r.getNativeType(JSTypeNative.GLOBAL_THIS),
r.getNativeType(JSTypeNative.OBJECT_TYPE),
r.getNativeType(JSTypeNative.OBJECT_PROTOTYPE),
r.getNativeType(JSTypeNative.OBJECT_FUNCTION_TYPE),
r.getNativeType(JSTypeNative.TOP_LEVEL_PROTOTYPE),
r.getNativeType(JSTypeNative.UNKNOWN_TYPE));
for (TypeMismatch mis : compiler.getTypeValidator().getMismatches()) {
addInvalidatingType(mis.typeA);
addInvalidatingType(mis.typeB);
}
}
/**
* Invalidates the given type, so that no properties on it will be renamed.
*/
private void addInvalidatingType(JSType type) {
type = type.restrictByNotNullOrUndefined();
if (type.isUnionType()) {
for (JSType alt : type.toMaybeUnionType().getAlternates()) {
addInvalidatingType(alt);
}
}
invalidatingTypes.add(type);
ObjectType objType = ObjectType.cast(type);
if (objType != null && objType.isInstanceType()) {
invalidatingTypes.add(objType.getImplicitPrototype());
}
}
Map getRenamingMap() {
return renamingMap;
}
/** Returns an integer that uniquely identifies a JSType. */
private int getIntForType(JSType type) {
if (intForType.containsKey(type)) {
return intForType.get(type).intValue();
}
int newInt = intForType.size() + 1;
intForType.put(type, newInt);
return newInt;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, externs, new ProcessExterns());
NodeTraversal.traverse(compiler, root, new ProcessProperties());
Set reservedNames =
new HashSet(externedNames.size() + quotedNames.size());
reservedNames.addAll(externedNames);
reservedNames.addAll(quotedNames);
int numRenamedPropertyNames = 0;
int numSkippedPropertyNames = 0;
Set propsByFreq = new TreeSet(FREQUENCY_COMPARATOR);
for (Property p : propertyMap.values()) {
if (!p.skipAmbiguating) {
++numRenamedPropertyNames;
propsByFreq.add(p);
} else {
++numSkippedPropertyNames;
reservedNames.add(p.oldName);
}
}
PropertyGraph graph = new PropertyGraph(Lists.newLinkedList(propsByFreq));
GraphColoring coloring =
new GreedyGraphColoring(graph, FREQUENCY_COMPARATOR);
int numNewPropertyNames = coloring.color();
NameGenerator nameGen = new NameGenerator(
reservedNames, "", reservedCharacters);
Map colorMap = Maps.newHashMap();
for (int i = 0; i < numNewPropertyNames; ++i) {
colorMap.put(i, nameGen.generateNextName());
}
for (GraphNode node : graph.getNodes()) {
node.getValue().newName = colorMap.get(node.getAnnotation().hashCode());
renamingMap.put(node.getValue().oldName, node.getValue().newName);
}
// Update the string nodes.
for (Node n : stringNodesToRename) {
String oldName = n.getString();
Property p = propertyMap.get(oldName);
if (p != null && p.newName != null) {
Preconditions.checkState(oldName.equals(p.oldName));
if (!p.newName.equals(oldName)) {
n.setString(p.newName);
compiler.reportCodeChange();
}
}
}
logger.fine("Collapsed " + numRenamedPropertyNames + " properties into "
+ numNewPropertyNames + " and skipped renaming "
+ numSkippedPropertyNames + " properties.");
}
private BitSet getRelatedTypesOnNonUnion(JSType type) {
// All of the types we encounter should have been added to the
// relatedBitsets via computeRelatedTypes.
if (relatedBitsets.containsKey(type)) {
return relatedBitsets.get(type);
} else {
throw new RuntimeException("Related types should have been computed for"
+ " type: " + type + " but have not been.");
}
}
/**
* Adds subtypes - and implementors, in the case of interfaces - of the type
* to its JSTypeBitSet of related types. Union types are decomposed into their
* alternative types.
*
* The 'is related to' relationship is best understood graphically. Draw an
* arrow from each instance type to the prototype of each of its
* subclass. Draw an arrow from each prototype to its instance type. Draw an
* arrow from each interface to its implementors. A type is related to another
* if there is a directed path in the graph from the type to other. Thus, the
* 'is related to' relationship is reflexive and transitive.
*
*
Example with Foo extends Bar which extends Baz and Bar implements I:
*
* Foo -> Bar.prototype -> Bar -> Baz.prototype -> Baz
* ^
* |
* I
*
*
* Note that we don't need to correctly handle the relationships between
* functions, because the function type is invalidating (i.e. its properties
* won't be ambiguated).
*/
private void computeRelatedTypes(JSType type) {
if (type.isUnionType()) {
type = type.restrictByNotNullOrUndefined();
if (type.isUnionType()) {
for (JSType alt : type.toMaybeUnionType().getAlternates()) {
computeRelatedTypes(alt);
}
return;
}
}
if (relatedBitsets.containsKey(type)) {
// We only need to generate the bit set once.
return;
}
JSTypeBitSet related = new JSTypeBitSet(intForType.size());
relatedBitsets.put(type, related);
related.set(getIntForType(type));
// A prototype is related to its instance.
if (type.isFunctionPrototypeType()) {
addRelatedInstance(((ObjectType) type).getOwnerFunction(), related);
return;
}
// An instance is related to its subclasses.
FunctionType constructor = type.toObjectType().getConstructor();
if (constructor != null && constructor.getSubTypes() != null) {
for (FunctionType subType : constructor.getSubTypes()) {
addRelatedInstance(subType, related);
}
}
// An interface is related to its implementors.
for (FunctionType implementor : compiler.getTypeRegistry()
.getDirectImplementors(type.toObjectType())) {
addRelatedInstance(implementor, related);
}
}
/**
* Adds the instance of the given constructor, its implicit prototype and all
* its related types to the given bit set.
*/
private void addRelatedInstance(
FunctionType constructor, JSTypeBitSet related) {
// TODO(user): A constructor which doesn't have an instance type
// (e.g. it's missing the @constructor annotation) should be an invalidating
// type which doesn't reach this code path.
if (constructor.hasInstanceType()) {
ObjectType instanceType = constructor.getInstanceType();
related.set(getIntForType(instanceType.getImplicitPrototype()));
computeRelatedTypes(instanceType);
related.or(relatedBitsets.get(instanceType));
}
}
class PropertyGraph implements AdjacencyGraph {
protected final Map nodes = Maps.newHashMap();
PropertyGraph(Collection props) {
for (Property prop : props) {
nodes.put(prop, new PropertyGraphNode(prop));
}
}
@Override
public List> getNodes() {
return Lists.>newArrayList(nodes.values());
}
@Override
public GraphNode getNode(Property property) {
return nodes.get(property);
}
@Override
public SubGraph newSubGraph() {
return new PropertySubGraph();
}
@Override
public void clearNodeAnnotations() {
for (PropertyGraphNode node : nodes.values()) {
node.setAnnotation(null);
}
}
@Override
public int getWeight(Property value) {
return value.numOccurrences;
}
}
/**
* A {@link SubGraph} that represents properties. The related types of
* the properties are used to efficiently calculate adjacency information.
*/
class PropertySubGraph implements SubGraph {
/** Types related to properties referenced in this subgraph. */
JSTypeBitSet relatedTypes = new JSTypeBitSet(intForType.size());
/**
* Returns true if prop is in an independent set from all properties in this
* sub graph. That is, if none of its related types intersects with the
* related types for this sub graph.
*/
@Override
public boolean isIndependentOf(Property prop) {
return !relatedTypes.intersects(prop.relatedTypes);
}
/**
* Adds the node to the sub graph, adding all its related types to the
* related types for the sub graph.
*/
@Override
public void addNode(Property prop) {
relatedTypes.or(prop.relatedTypes);
}
}
class PropertyGraphNode implements GraphNode {
Property property;
protected Annotation annotation;
PropertyGraphNode(Property property) {
this.property = property;
}
@Override
public Property getValue() {
return property;
}
@Override
@SuppressWarnings("unchecked")
public A getAnnotation() {
return (A) annotation;
}
@Override
public void setAnnotation(Annotation data) {
annotation = data;
}
}
/** A traversal callback that collects externed property names. */
private class ProcessExterns extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.GETPROP:
Node dest = n.getFirstChild().getNext();
externedNames.add(dest.getString());
break;
case Token.OBJECTLIT:
for (Node child = n.getFirstChild();
child != null;
child = child.getNext()) {
// names: STRING, GET, SET
externedNames.add(child.getString());
}
break;
}
}
}
/** Finds all property references, recording the types on which they occur. */
private class ProcessProperties extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.GETPROP: {
Node propNode = n.getFirstChild().getNext();
JSType jstype = getJSType(n.getFirstChild());
maybeMarkCandidate(propNode, jstype, t);
break;
}
case Token.OBJECTLIT:
// The children of an OBJECTLIT node are keys, where the values
// are the children of the keys.
for (Node key = n.getFirstChild(); key != null;
key = key.getNext()) {
// We only want keys that were unquoted.
// Keys are STRING, GET, SET
if (!key.isQuotedString()) {
JSType jstype = getJSType(n.getFirstChild());
maybeMarkCandidate(key, jstype, t);
} else {
// Ensure that we never rename some other property in a way
// that could conflict with this quoted key.
quotedNames.add(key.getString());
}
}
break;
case Token.GETELEM:
// If this is a quoted property access (e.g. x['myprop']), we need to
// ensure that we never rename some other property in a way that
// could conflict with this quoted name.
Node child = n.getLastChild();
if (child.isString()) {
quotedNames.add(child.getString());
}
break;
}
}
/**
* If a property node is eligible for renaming, stashes a reference to it
* and increments the property name's access count.
*
* @param n The STRING node for a property
* @param t The traversal
*/
private void maybeMarkCandidate(Node n, JSType type, NodeTraversal t) {
String name = n.getString();
if (!externedNames.contains(name)) {
stringNodesToRename.add(n);
recordProperty(name, type);
}
}
private Property recordProperty(String name, JSType type) {
Property prop = getProperty(name);
prop.addType(type);
return prop;
}
}
/** Returns true if properties on this type should not be renamed. */
private boolean isInvalidatingType(JSType type) {
if (type.isUnionType()) {
type = type.restrictByNotNullOrUndefined();
if (type.isUnionType()) {
for (JSType alt : type.toMaybeUnionType().getAlternates()) {
if (isInvalidatingType(alt)) {
return true;
}
}
return false;
}
}
ObjectType objType = ObjectType.cast(type);
return objType == null
|| invalidatingTypes.contains(objType)
|| !objType.hasReferenceName()
|| objType.isUnknownType()
|| objType.isEmptyType() /* unresolved types */
|| objType.isEnumType()
|| objType.autoboxesTo() != null;
}
private Property getProperty(String name) {
Property prop = propertyMap.get(name);
if (prop == null) {
prop = new Property(name);
propertyMap.put(name, prop);
}
return prop;
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(user): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return compiler.getTypeRegistry().getNativeType(
JSTypeNative.UNKNOWN_TYPE);
} else {
return jsType;
}
}
/** Encapsulates the information needed for renaming a property. */
private class Property {
final String oldName;
String newName;
int numOccurrences;
boolean skipAmbiguating;
JSTypeBitSet relatedTypes = new JSTypeBitSet(intForType.size());
Property(String name) {
this.oldName = name;
// Properties with this suffix are handled in RenameProperties.
if (name.startsWith(SKIP_PREFIX)) {
skipAmbiguating = true;
}
}
/** Add this type to this property, calculating */
void addType(JSType newType) {
if (skipAmbiguating) {
return;
}
++numOccurrences;
if (newType.isUnionType()) {
newType = newType.restrictByNotNullOrUndefined();
if (newType.isUnionType()) {
for (JSType alt : newType.toMaybeUnionType().getAlternates()) {
addNonUnionType(alt);
}
return;
}
}
addNonUnionType(newType);
}
private void addNonUnionType(JSType newType) {
if (skipAmbiguating || isInvalidatingType(newType)) {
skipAmbiguating = true;
return;
}
if (!relatedTypes.get(getIntForType(newType))) {
computeRelatedTypes(newType);
relatedTypes.or(getRelatedTypesOnNonUnion(newType));
}
}
}
// A BitSet that stores type info. Adds pretty-print routines.
private class JSTypeBitSet extends BitSet {
private static final long serialVersionUID = 1L;
private JSTypeBitSet(int size) {
super(size);
}
private JSTypeBitSet() {
super();
}
/**
* Pretty-printing, for diagnostic purposes.
*/
@Override
public String toString() {
int from = 0;
int current = 0;
List types = Lists.newArrayList();
while (-1 != (current = nextSetBit(from))) {
types.add(intForType.inverse().get(current).toString());
from = current + 1;
}
return Joiner.on(" && ").join(types);
}
}
}
closure-compiler-20130227+dfsg1/src/com/google/javascript/jscomp/AnalyzeNameReferences.java 0000664 0000000 0000000 00000011162 12115204405 0031566 0 ustar 00root root 0000000 0000000 /*
* Copyright 2009 The Closure Compiler Authors.
*
* 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.javascript.jscomp;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.NameReferenceGraph.Name;
import com.google.javascript.jscomp.NameReferenceGraph.Reference;
import com.google.javascript.jscomp.graph.FixedPointGraphTraversal;
import com.google.javascript.jscomp.graph.Annotation;
import com.google.javascript.jscomp.graph.GraphNode;
import com.google.javascript.jscomp.graph.FixedPointGraphTraversal.EdgeCallback;
import com.google.javascript.rhino.Node;
/**
* Analyzes names and references usage by determining:
*
* - If the name is reachable from the {@link NameReferenceGraph#MAIN}.
* - as well as the deepest common module that references it.
*
*
* The two pieces of information will be annotated to {@link NameReferenceGraph}
* by {@link NameInfo} objects.
*
* This is an analysis based on {@link AnalyzeNameReferences} using the more
* accurate graph and will soon replace it.
*
*/
class AnalyzeNameReferences implements CompilerPass {
private NameReferenceGraph graph;
private final JSModuleGraph moduleGraph;
private final AbstractCompiler compiler;
AnalyzeNameReferences(AbstractCompiler compiler) {
this.compiler = compiler;
this.moduleGraph = compiler.getModuleGraph();
}
@Override
public void process(Node externs, Node root) {
NameReferenceGraphConstruction gc =
new NameReferenceGraphConstruction(compiler);
gc.process(externs, root);
graph = gc.getNameReferenceGraph();
FixedPointGraphTraversal t =
FixedPointGraphTraversal.newTraversal(new PropagateReferences());
getInfo(graph.MAIN).markReference(null);
t.computeFixedPoint(graph, Sets.newHashSet(graph.MAIN));
}
public NameReferenceGraph getGraph() {
return graph;
}
private class PropagateReferences implements EdgeCallback {
@Override
public boolean traverseEdge(Name start, Reference edge, Name dest) {
NameInfo startInfo = getInfo(start);
NameInfo destInfo = getInfo(dest);
if (startInfo.isReferenced()) {
JSModule startModule = startInfo.getDeepestCommonModuleRef();
if (startModule != null &&
moduleGraph.dependsOn(startModule, edge.getModule())) {
return destInfo.markReference(startModule);
} else {
return destInfo.markReference(edge.getModule());
}
}
return false;
}
}
private NameInfo getInfo(Name symbol) {
GraphNode name = graph.getNode(symbol);
NameInfo info = name.getAnnotation();
if (info == null) {
info = new NameInfo();
name.setAnnotation(info);
}
return info;
}
final class NameInfo implements Annotation {
private boolean referenced = false;
private JSModule deepestCommonModuleRef = null;
/** Determines whether we've marked a reference to this property name. */
boolean isReferenced() {
return referenced;
}
/**
* Returns the deepest common module of all the references to this
* property.
*/
JSModule getDeepestCommonModuleRef() {
return deepestCommonModuleRef;
}
/**
* Mark a reference in a given module to this property name, and record
* the deepest common module reference.
* @param module The module where it was referenced.
* @return Whether the name info has changed.
*/
boolean markReference(JSModule module) {
boolean hasChanged = false;
if (!referenced) {
referenced = true;
hasChanged = true;
}
if (moduleGraph != null) {
JSModule originalDeepestCommon = deepestCommonModuleRef;
if (deepestCommonModuleRef == null) {
deepestCommonModuleRef = module;
} else {
deepestCommonModuleRef =
moduleGraph.getDeepestCommonDependencyInclusive(
deepestCommonModuleRef, module);
}
if (originalDeepestCommon != deepestCommonModuleRef) {
hasChanged = true;
}
}
return hasChanged;
}
}
}
closure-compiler-20130227+dfsg1/src/com/google/javascript/jscomp/AnalyzePrototypeProperties.java 0000664 0000000 0000000 00000063734 12115204405 0033002 0 ustar 00root root 0000000 0000000 /*
* Copyright 2006 The Closure Compiler Authors.
*
* 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.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.jscomp.graph.FixedPointGraphTraversal;
import com.google.javascript.jscomp.graph.LinkedDirectedGraph;
import com.google.javascript.jscomp.graph.FixedPointGraphTraversal.EdgeCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
/**
* Analyzes properties on prototypes.
*
* Uses a reference graph to analyze prototype properties. Each unique property
* name is represented by a node in this graph. An edge from property A to
* property B means that there's a GETPROP access of a property B on some
* object inside of a method named A.
*
* Global functions are also represented by nodes in this graph, with
* similar semantics.
*
*/
class AnalyzePrototypeProperties implements CompilerPass {
// Constants for symbol types, for easier readability.
private final SymbolType PROPERTY = SymbolType.PROPERTY;
private final SymbolType VAR = SymbolType.VAR;
private final AbstractCompiler compiler;
private final boolean canModifyExterns;
private final boolean anchorUnusedVars;
private final JSModuleGraph moduleGraph;
private final JSModule firstModule;
// Properties that are implicitly used as part of the JS language.
private static final Set IMPLICITLY_USED_PROPERTIES =
ImmutableSet.of("length", "toString", "valueOf");
// A graph where the nodes are property names or variable names,
// and the edges signify the modules where the property is referenced.
// For example, if we had the code:
//
// Foo.prototype.bar = function(x) { x.baz(); }; // in module 2.;
//
// then this would be represented in the graph by a node representing
// "bar", a node representing "baz", and an edge between them representing
// module #2.
//
// Similarly, if we had:
//
// var scotch = function(f) { return f.age(); };
//
// then there would be a node for "scotch", a node for "age", and an edge
// from scotch to age.
private final LinkedDirectedGraph symbolGraph =
LinkedDirectedGraph.createWithoutAnnotations();
// A dummy node for representing global references.
private final NameInfo globalNode = new NameInfo("[global]");
// A dummy node for representing extern references.
private final NameInfo externNode = new NameInfo("[extern]");
// A dummy node for representing all anonymous functions with no names.
private final NameInfo anonymousNode = new NameInfo("[anonymous]");
// All the real NameInfo for prototype properties, hashed by the name
// of the property that they represent.
private final Map propertyNameInfo = Maps.newHashMap();
// All the NameInfo for global functions, hashed by the name of the
// global variable that it's assigned to.
private final Map varNameInfo = Maps.newHashMap();
/**
* Creates a new pass for analyzing prototype properties.
* @param compiler The compiler.
* @param moduleGraph The graph for resolving module dependencies. May be
* null if we don't care about module dependencies.
* @param canModifyExterns If true, then we can move prototype
* properties that are declared in the externs file.
* @param anchorUnusedVars If true, then we must mark all vars as referenced,
* even if they are never used.
*/
AnalyzePrototypeProperties(AbstractCompiler compiler,
JSModuleGraph moduleGraph, boolean canModifyExterns,
boolean anchorUnusedVars) {
this.compiler = compiler;
this.moduleGraph = moduleGraph;
this.canModifyExterns = canModifyExterns;
this.anchorUnusedVars = anchorUnusedVars;
if (moduleGraph != null) {
firstModule = moduleGraph.getRootModule();
} else {
firstModule = null;
}
globalNode.markReference(null);
externNode.markReference(null);
symbolGraph.createNode(globalNode);
symbolGraph.createNode(externNode);
for (String property : IMPLICITLY_USED_PROPERTIES) {
NameInfo nameInfo = getNameInfoForName(property, PROPERTY);
if (moduleGraph == null) {
symbolGraph.connect(externNode, null, nameInfo);
} else {
for (JSModule module : moduleGraph.getAllModules()) {
symbolGraph.connect(externNode, module, nameInfo);
}
}
}
}
@Override
public void process(Node externRoot, Node root) {
if (!canModifyExterns) {
NodeTraversal.traverse(compiler, externRoot,
new ProcessExternProperties());
}
NodeTraversal.traverse(compiler, root, new ProcessProperties());
FixedPointGraphTraversal t =
FixedPointGraphTraversal.newTraversal(new PropagateReferences());
t.computeFixedPoint(symbolGraph,
Sets.newHashSet(externNode, globalNode));
}
/**
* Returns information on all prototype properties.
*/
public Collection getAllNameInfo() {
List result = Lists.newArrayList(propertyNameInfo.values());
result.addAll(varNameInfo.values());
return result;
}
/**
* Gets the name info for the property or variable of a given name,
* and creates a new one if necessary.
*
* @param name The name of the symbol.
* @param type The type of symbol.
*/
private NameInfo getNameInfoForName(String name, SymbolType type) {
Map map = type == PROPERTY ?
propertyNameInfo : varNameInfo;
if (map.containsKey(name)) {
return map.get(name);
} else {
NameInfo nameInfo = new NameInfo(name);
map.put(name, nameInfo);
symbolGraph.createNode(nameInfo);
return nameInfo;
}
}
private class ProcessProperties implements NodeTraversal.ScopedCallback {
// There are two types of context information on this stack:
// 1) Every scope has a NameContext corresponding to its scope.
// Variables are given VAR contexts.
// Prototype properties are given PROPERTY contexts.
// The global scope is given the special [global] context.
// And function expressions that we aren't able to give a reasonable
// name are given a special [anonymous] context.
// 2) Every assignment of a prototype property of a non-function is
// given a name context. These contexts do not have scopes.
private Stack symbolStack = new Stack();
@Override
public void enterScope(NodeTraversal t) {
Node n = t.getCurrentNode();
if (n.isFunction()) {
String propName = getPrototypePropertyNameFromRValue(n);
if (propName != null) {
symbolStack.push(
new NameContext(
getNameInfoForName(propName, PROPERTY),
t.getScope()));
} else if (isGlobalFunctionDeclaration(t, n)) {
Node parent = n.getParent();
String name = parent.isName() ?
parent.getString() /* VAR */ :
n.getFirstChild().getString() /* named function */;
symbolStack.push(
new NameContext(getNameInfoForName(name, VAR), t.getScope()));
} else {
// NOTE(nicksantos): We use the same anonymous node for all
// functions that do not have reasonable names. I can't remember
// at the moment why we do this. I think it's because anonymous
// nodes can never have in-edges. They're just there as a placeholder
// for scope information, and do not matter in the edge propagation.
symbolStack.push(new NameContext(anonymousNode, t.getScope()));
}
} else {
Preconditions.checkState(t.inGlobalScope());
symbolStack.push(new NameContext(globalNode, t.getScope()));
}
}
@Override
public void exitScope(NodeTraversal t) {
symbolStack.pop();
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
// Process prototype assignments to non-functions.
String propName = processNonFunctionPrototypeAssign(n, parent);
if (propName != null) {
symbolStack.push(
new NameContext(
getNameInfoForName(propName, PROPERTY), null));
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isGetProp()) {
String propName = n.getFirstChild().getNext().getString();
if (n.isQualifiedName()) {
if (propName.equals("prototype")) {
if (processPrototypeRef(t, n)) {
return;
}
} else if (compiler.getCodingConvention().isExported(propName)) {
addGlobalUseOfSymbol(propName, t.getModule(), PROPERTY);
return;
} else {
// Do not mark prototype prop assigns as a 'use' in the global scope.
if (n.getParent().isAssign() && n.getNext() != null) {
String rValueName = getPrototypePropertyNameFromRValue(n);
if (rValueName != null) {
return;
}
}
}
}
addSymbolUse(propName, t.getModule(), PROPERTY);
} else if (n.isObjectLit()) {
// Make sure that we're not handling object literals being
// assigned to a prototype, as in:
// Foo.prototype = {bar: 3, baz: 5};
String lValueName = NodeUtil.getBestLValueName(
NodeUtil.getBestLValue(n));
if (lValueName != null && lValueName.endsWith(".prototype")) {
return;
}
// var x = {a: 1, b: 2}
// should count as a use of property a and b.
for (Node propNameNode = n.getFirstChild(); propNameNode != null;
propNameNode = propNameNode.getNext()) {
// May be STRING, GET, or SET, but NUMBER isn't interesting.
if (!propNameNode.isQuotedString()) {
addSymbolUse(propNameNode.getString(), t.getModule(), PROPERTY);
}
}
} else if (n.isName()) {
String name = n.getString();
Var var = t.getScope().getVar(name);
if (var != null) {
// Only process global functions.
if (var.isGlobal()) {
if (var.getInitialValue() != null &&
var.getInitialValue().isFunction()) {
if (t.inGlobalScope()) {
if (!processGlobalFunctionDeclaration(t, n, var)) {
addGlobalUseOfSymbol(name, t.getModule(), VAR);
}
} else {
addSymbolUse(name, t.getModule(), VAR);
}
}
// If it is not a global, it might be accessing a local of the outer
// scope. If that's the case the functions between the variable's
// declaring scope and the variable reference scope cannot be moved.
} else if (var.getScope() != t.getScope()){
for (int i = symbolStack.size() - 1; i >= 0; i--) {
NameContext context = symbolStack.get(i);
if (context.scope == var.getScope()) {
break;
}
context.name.readClosureVariables = true;
}
}
}
}
// Process prototype assignments to non-functions.
if (processNonFunctionPrototypeAssign(n, parent) != null) {
symbolStack.pop();
}
}
private void addSymbolUse(String name, JSModule module, SymbolType type) {
NameInfo info = getNameInfoForName(name, type);
NameInfo def = null;
// Skip all anonymous nodes. We care only about symbols with names.
for (int i = symbolStack.size() - 1; i >= 0; i--) {
def = symbolStack.get(i).name;
if (def != anonymousNode) {
break;
}
}
if (!def.equals(info)) {
symbolGraph.connect(def, module, info);
}
}
/**
* If this is a non-function prototype assign, return the prop name.
* Otherwise, return null.
*/
private String processNonFunctionPrototypeAssign(Node n, Node parent) {
if (isAssignRValue(n, parent) && !n.isFunction()) {
return getPrototypePropertyNameFromRValue(n);
}
return null;
}
/**
* Determines whether {@code n} is the FUNCTION node in a global function
* declaration.
*/
private boolean isGlobalFunctionDeclaration(NodeTraversal t, Node n) {
// Make sure we're either in the global scope, or the function
// we're looking at is the root of the current local scope.
Scope s = t.getScope();
if (!(s.isGlobal() ||
s.getDepth() == 1 && s.getRootNode() == n)) {
return false;
}
return NodeUtil.isFunctionDeclaration(n) ||
n.isFunction() && n.getParent().isName();
}
/**
* Returns true if this is the r-value of an assignment.
*/
private boolean isAssignRValue(Node n, Node parent) {
return parent != null && parent.isAssign() && parent.getFirstChild() != n;
}
/**
* Returns the name of a prototype property being assigned to this r-value.
*
* Returns null if this is not the R-value of a prototype property, or if
* the R-value is used in multiple expressions (i.e., if there's
* a prototype property assignment in a more complex expression).
*/
private String getPrototypePropertyNameFromRValue(Node rValue) {
Node lValue = NodeUtil.getBestLValue(rValue);
if (lValue == null ||
lValue.getParent() == null ||
lValue.getParent().getParent() == null ||
!(NodeUtil.isObjectLitKey(lValue) ||
NodeUtil.isExprAssign(lValue.getParent().getParent()))) {
return null;
}
String lValueName =
NodeUtil.getBestLValueName(NodeUtil.getBestLValue(rValue));
if (lValueName == null) {
return null;
}
int lastDot = lValueName.lastIndexOf('.');
if (lastDot == -1) {
return null;
}
String firstPart = lValueName.substring(0, lastDot);
if (!firstPart.endsWith(".prototype")) {
return null;
}
return lValueName.substring(lastDot + 1);
}
/**
* Processes a NAME node to see if it's a global function declaration.
* If it is, record it and return true. Otherwise, return false.
*/
private boolean processGlobalFunctionDeclaration(NodeTraversal t,
Node nameNode, Var v) {
Node firstChild = nameNode.getFirstChild();
Node parent = nameNode.getParent();
if (// Check for a named FUNCTION.
isGlobalFunctionDeclaration(t, parent) ||
// Check for a VAR declaration.
firstChild != null &&
isGlobalFunctionDeclaration(t, firstChild)) {
String name = nameNode.getString();
getNameInfoForName(name, VAR).getDeclarations().add(
new GlobalFunction(nameNode, v, t.getModule()));
// If the function name is exported, we should create an edge here
// so that it's never removed.
if (compiler.getCodingConvention().isExported(name) ||
anchorUnusedVars) {
addGlobalUseOfSymbol(name, t.getModule(), VAR);
}
return true;
}
return false;
}
/**
* Processes the GETPROP of prototype, which can either be under
* another GETPROP (in the case of Foo.prototype.bar), or can be
* under an assignment (in the case of Foo.prototype = ...).
* @return True if a declaration was added.
*/
private boolean processPrototypeRef(NodeTraversal t, Node ref) {
Node root = NodeUtil.getRootOfQualifiedName(ref);
Node n = ref.getParent();
switch (n.getType()) {
// Foo.prototype.getBar = function() { ... }
case Token.GETPROP:
Node dest = n.getFirstChild().getNext();
Node parent = n.getParent();
Node grandParent = parent.getParent();
if (dest.isString() &&
NodeUtil.isExprAssign(grandParent) &&
NodeUtil.isVarOrSimpleAssignLhs(n, parent)) {
String name = dest.getString();
Property prop = new AssignmentProperty(
grandParent,
maybeGetVar(t, root),
t.getModule());
getNameInfoForName(name, PROPERTY).getDeclarations().add(prop);
return true;
}
break;
// Foo.prototype = { "getBar" : function() { ... } }
case Token.ASSIGN:
Node map = n.getFirstChild().getNext();
if (map.isObjectLit()) {
for (Node key = map.getFirstChild();
key != null; key = key.getNext()) {
// May be STRING, GETTER_DEF, or SETTER_DEF,
String name = key.getString();
Property prop = new LiteralProperty(
key, key.getFirstChild(), map, n,
maybeGetVar(t, root),
t.getModule());
getNameInfoForName(name, PROPERTY).getDeclarations().add(prop);
}
return true;
}
break;
}
return false;
}
private Var maybeGetVar(NodeTraversal t, Node maybeName) {
return maybeName.isName()
? t.getScope().getVar(maybeName.getString()) : null;
}
private void addGlobalUseOfSymbol(String name, JSModule module,
SymbolType type) {
symbolGraph.connect(globalNode, module, getNameInfoForName(name, type));
}
}
private class ProcessExternProperties extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isGetProp()) {
symbolGraph.connect(externNode, firstModule,
getNameInfoForName(n.getLastChild().getString(), PROPERTY));
}
}
}
private class PropagateReferences
implements EdgeCallback {
@Override
public boolean traverseEdge(NameInfo start, JSModule edge, NameInfo dest) {
if (start.isReferenced()) {
JSModule startModule = start.getDeepestCommonModuleRef();
if (startModule != null &&
moduleGraph.dependsOn(startModule, edge)) {
return dest.markReference(startModule);
} else {
return dest.markReference(edge);
}
}
return false;
}
}
// TODO(user): We can use DefinitionsRemover and UseSite here. Then all
// we need to do is call getDefinition() and we'll magically know everything
// about the definition.
/**
* The declaration of an abstract symbol.
*/
interface Symbol {
/**
* Remove the declaration from the AST.
*/
void remove();
/**
* The variable for the root of this symbol.
*/
Var getRootVar();
/**
* Returns the module where this appears.
*/
JSModule getModule();
}
private enum SymbolType {
PROPERTY,
VAR;
}
/**
* A function initialized as a VAR statement or a function declaration.
*/
class GlobalFunction implements Symbol {
private final Node nameNode;
private final Var var;
private final JSModule module;
GlobalFunction(Node nameNode, Var var, JSModule module) {
Node parent = nameNode.getParent();
Preconditions.checkState(
parent.isVar() ||
NodeUtil.isFunctionDeclaration(parent));
this.nameNode = nameNode;
this.var = var;
this.module = module;
}
@Override
public Var getRootVar() {
return var;
}
@Override
public void remove() {
Node parent = nameNode.getParent();
if (parent.isFunction() || parent.hasOneChild()) {
NodeUtil.removeChild(parent.getParent(), parent);
} else {
Preconditions.checkState(parent.isVar());
parent.removeChild(nameNode);
}
}
@Override
public JSModule getModule() {
return module;
}
public Node getFunctionNode() {
Node parent = nameNode.getParent();
if (parent.isFunction()) {
return parent;
} else {
// we are the name of a var node, so the function is name's second child
return nameNode.getChildAtIndex(1);
}
}
}
/**
* Since there are two ways of assigning properties to prototypes, we hide
* then behind this interface so they can both be removed regardless of type.
*/
interface Property extends Symbol {
/** Returns the GETPROP node that refers to the prototype. */
Node getPrototype();
/** Returns the value of this property. */
Node getValue();
}
/**
* Properties created via EXPR assignment:
*
*