1400 lines
43 KiB
Java
1400 lines
43 KiB
Java
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (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.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Rhino code, released
|
|
* May 6, 1999.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1997-1999
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Norris Boyd
|
|
* Roshan James
|
|
* Roger Lawrence
|
|
* Mike McCabe
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* the GNU General Public License Version 2 or later (the "GPL"), in which
|
|
* case the provisions of the GPL are applicable instead of those above. If
|
|
* you wish to allow use of your version of this file only under the terms of
|
|
* the GPL and not to allow others to use your version of this file under the
|
|
* MPL, indicate your decision by deleting the provisions above and replacing
|
|
* them with the notice and other provisions required by the GPL. If you do
|
|
* not delete the provisions above, a recipient may use your version of this
|
|
* file under either the MPL or the GPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
package org.mozilla.javascript;
|
|
|
|
import java.util.Map;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.Iterator;
|
|
import java.util.Collections;
|
|
|
|
/**
|
|
* This class implements the root of the intermediate representation.
|
|
*
|
|
* @author Norris Boyd
|
|
* @author Mike McCabe
|
|
*/
|
|
|
|
public class Node
|
|
{
|
|
public static final int
|
|
FUNCTION_PROP = 1,
|
|
LOCAL_PROP = 2,
|
|
LOCAL_BLOCK_PROP = 3,
|
|
REGEXP_PROP = 4,
|
|
CASEARRAY_PROP = 5,
|
|
/*
|
|
the following properties are defined and manipulated by the
|
|
optimizer -
|
|
TARGETBLOCK_PROP - the block referenced by a branch node
|
|
VARIABLE_PROP - the variable referenced by a BIND or NAME node
|
|
ISNUMBER_PROP - this node generates code on Number children and
|
|
delivers a Number result (as opposed to Objects)
|
|
DIRECTCALL_PROP - this call node should emit code to test the function
|
|
object against the known class and call direct if it
|
|
matches.
|
|
*/
|
|
|
|
TARGETBLOCK_PROP = 6,
|
|
VARIABLE_PROP = 7,
|
|
ISNUMBER_PROP = 8,
|
|
DIRECTCALL_PROP = 9,
|
|
SPECIALCALL_PROP = 10,
|
|
SKIP_INDEXES_PROP = 11, // array of skipped indexes of array literal
|
|
OBJECT_IDS_PROP = 12, // array of properties for object literal
|
|
INCRDECR_PROP = 13, // pre or post type of increment/decrement
|
|
CATCH_SCOPE_PROP = 14, // index of catch scope block in catch
|
|
LABEL_ID_PROP = 15, // label id: code generation uses it
|
|
MEMBER_TYPE_PROP = 16, // type of element access operation
|
|
NAME_PROP = 17, // property name
|
|
CONTROL_BLOCK_PROP = 18, // flags a control block that can drop off
|
|
PARENTHESIZED_PROP = 19, // expression is parenthesized
|
|
GENERATOR_END_PROP = 20,
|
|
DESTRUCTURING_ARRAY_LENGTH = 21,
|
|
DESTRUCTURING_NAMES= 22,
|
|
LAST_PROP = 22;
|
|
|
|
// values of ISNUMBER_PROP to specify
|
|
// which of the children are Number types
|
|
public static final int
|
|
BOTH = 0,
|
|
LEFT = 1,
|
|
RIGHT = 2;
|
|
|
|
public static final int // values for SPECIALCALL_PROP
|
|
NON_SPECIALCALL = 0,
|
|
SPECIALCALL_EVAL = 1,
|
|
SPECIALCALL_WITH = 2;
|
|
|
|
public static final int // flags for INCRDECR_PROP
|
|
DECR_FLAG = 0x1,
|
|
POST_FLAG = 0x2;
|
|
|
|
public static final int // flags for MEMBER_TYPE_PROP
|
|
PROPERTY_FLAG = 0x1, // property access: element is valid name
|
|
ATTRIBUTE_FLAG = 0x2, // x.@y or x..@y
|
|
DESCENDANTS_FLAG = 0x4; // x..y or x..@i
|
|
|
|
private static class NumberNode extends Node
|
|
{
|
|
NumberNode(double number)
|
|
{
|
|
super(Token.NUMBER);
|
|
this.number = number;
|
|
}
|
|
|
|
double number;
|
|
}
|
|
|
|
private static class StringNode extends Node
|
|
{
|
|
StringNode(int type, String str) {
|
|
super(type);
|
|
this.str = str;
|
|
}
|
|
|
|
String str;
|
|
Node.Scope scope;
|
|
}
|
|
|
|
public static class Jump extends Node
|
|
{
|
|
public Jump(int type)
|
|
{
|
|
super(type);
|
|
}
|
|
|
|
Jump(int type, int lineno)
|
|
{
|
|
super(type, lineno);
|
|
}
|
|
|
|
Jump(int type, Node child)
|
|
{
|
|
super(type, child);
|
|
}
|
|
|
|
Jump(int type, Node child, int lineno)
|
|
{
|
|
super(type, child, lineno);
|
|
}
|
|
|
|
public final Jump getJumpStatement()
|
|
{
|
|
if (!(type == Token.BREAK || type == Token.CONTINUE)) Kit.codeBug();
|
|
return jumpNode;
|
|
}
|
|
|
|
public final void setJumpStatement(Jump jumpStatement)
|
|
{
|
|
if (!(type == Token.BREAK || type == Token.CONTINUE)) Kit.codeBug();
|
|
if (jumpStatement == null) Kit.codeBug();
|
|
if (this.jumpNode != null) Kit.codeBug(); //only once
|
|
this.jumpNode = jumpStatement;
|
|
}
|
|
|
|
public final Node getDefault()
|
|
{
|
|
if (!(type == Token.SWITCH)) Kit.codeBug();
|
|
return target2;
|
|
}
|
|
|
|
public final void setDefault(Node defaultTarget)
|
|
{
|
|
if (!(type == Token.SWITCH)) Kit.codeBug();
|
|
if (defaultTarget.type != Token.TARGET) Kit.codeBug();
|
|
if (target2 != null) Kit.codeBug(); //only once
|
|
target2 = defaultTarget;
|
|
}
|
|
|
|
public final Node getFinally()
|
|
{
|
|
if (!(type == Token.TRY)) Kit.codeBug();
|
|
return target2;
|
|
}
|
|
|
|
public final void setFinally(Node finallyTarget)
|
|
{
|
|
if (!(type == Token.TRY)) Kit.codeBug();
|
|
if (finallyTarget.type != Token.TARGET) Kit.codeBug();
|
|
if (target2 != null) Kit.codeBug(); //only once
|
|
target2 = finallyTarget;
|
|
}
|
|
|
|
public final Jump getLoop()
|
|
{
|
|
if (!(type == Token.LABEL)) Kit.codeBug();
|
|
return jumpNode;
|
|
}
|
|
|
|
public final void setLoop(Jump loop)
|
|
{
|
|
if (!(type == Token.LABEL)) Kit.codeBug();
|
|
if (loop == null) Kit.codeBug();
|
|
if (jumpNode != null) Kit.codeBug(); //only once
|
|
jumpNode = loop;
|
|
}
|
|
|
|
public final Node getContinue()
|
|
{
|
|
if (type != Token.LOOP) Kit.codeBug();
|
|
return target2;
|
|
}
|
|
|
|
public final void setContinue(Node continueTarget)
|
|
{
|
|
if (type != Token.LOOP) Kit.codeBug();
|
|
if (continueTarget.type != Token.TARGET) Kit.codeBug();
|
|
if (target2 != null) Kit.codeBug(); //only once
|
|
target2 = continueTarget;
|
|
}
|
|
|
|
public Node target;
|
|
private Node target2;
|
|
private Jump jumpNode;
|
|
}
|
|
|
|
static class Symbol {
|
|
Symbol(int declType, String name) {
|
|
this.declType = declType;
|
|
this.name = name;
|
|
this.index = -1;
|
|
}
|
|
/**
|
|
* One of Token.FUNCTION, Token.LP (for parameters), Token.VAR,
|
|
* Token.LET, or Token.CONST
|
|
*/
|
|
int declType;
|
|
int index;
|
|
String name;
|
|
Node.Scope containingTable;
|
|
}
|
|
|
|
static class Scope extends Jump {
|
|
public Scope(int nodeType) {
|
|
super(nodeType);
|
|
}
|
|
|
|
public Scope(int nodeType, int lineno) {
|
|
super(nodeType, lineno);
|
|
}
|
|
|
|
public Scope(int nodeType, Node n, int lineno) {
|
|
super(nodeType, n, lineno);
|
|
}
|
|
|
|
/*
|
|
* Creates a new scope node, moving symbol table information
|
|
* from "scope" to the new node, and making "scope" a nested
|
|
* scope contained by the new node.
|
|
* Useful for injecting a new scope in a scope chain.
|
|
*/
|
|
public static Scope splitScope(Scope scope) {
|
|
Scope result = new Scope(scope.getType());
|
|
result.symbolTable = scope.symbolTable;
|
|
scope.symbolTable = null;
|
|
result.parent = scope.parent;
|
|
scope.parent = result;
|
|
result.top = scope.top;
|
|
return result;
|
|
}
|
|
|
|
public static void joinScopes(Scope source, Scope dest) {
|
|
source.ensureSymbolTable();
|
|
dest.ensureSymbolTable();
|
|
if (!Collections.disjoint(source.symbolTable.keySet(),
|
|
dest.symbolTable.keySet()))
|
|
{
|
|
throw Kit.codeBug();
|
|
}
|
|
dest.symbolTable.putAll(source.symbolTable);
|
|
}
|
|
|
|
public void setParent(Scope parent) {
|
|
this.parent = parent;
|
|
this.top = parent == null ? (ScriptOrFnNode)this : parent.top;
|
|
}
|
|
|
|
public Scope getParentScope() {
|
|
return parent;
|
|
}
|
|
|
|
public Scope getDefiningScope(String name) {
|
|
for (Scope sn=this; sn != null; sn = sn.parent) {
|
|
if (sn.symbolTable == null)
|
|
continue;
|
|
if (sn.symbolTable.containsKey(name))
|
|
return sn;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public Symbol getSymbol(String name) {
|
|
return symbolTable == null ? null : symbolTable.get(name);
|
|
}
|
|
|
|
public void putSymbol(String name, Symbol symbol) {
|
|
ensureSymbolTable();
|
|
symbolTable.put(name, symbol);
|
|
symbol.containingTable = this;
|
|
top.addSymbol(symbol);
|
|
}
|
|
|
|
public Map<String,Symbol> getSymbolTable() {
|
|
return symbolTable;
|
|
}
|
|
|
|
private void ensureSymbolTable() {
|
|
if (symbolTable == null) {
|
|
symbolTable = new LinkedHashMap<String,Symbol>(5);
|
|
}
|
|
}
|
|
|
|
// Use LinkedHashMap so that the iteration order is the insertion order
|
|
protected LinkedHashMap<String,Symbol> symbolTable;
|
|
private Scope parent;
|
|
private ScriptOrFnNode top;
|
|
}
|
|
|
|
private static class PropListItem
|
|
{
|
|
PropListItem next;
|
|
int type;
|
|
int intValue;
|
|
Object objectValue;
|
|
}
|
|
|
|
|
|
public Node(int nodeType) {
|
|
type = nodeType;
|
|
}
|
|
|
|
public Node(int nodeType, Node child) {
|
|
type = nodeType;
|
|
first = last = child;
|
|
child.next = null;
|
|
}
|
|
|
|
public Node(int nodeType, Node left, Node right) {
|
|
type = nodeType;
|
|
first = left;
|
|
last = right;
|
|
left.next = right;
|
|
right.next = null;
|
|
}
|
|
|
|
public Node(int nodeType, Node left, Node mid, Node right) {
|
|
type = nodeType;
|
|
first = left;
|
|
last = right;
|
|
left.next = mid;
|
|
mid.next = right;
|
|
right.next = null;
|
|
}
|
|
|
|
public Node(int nodeType, int line) {
|
|
type = nodeType;
|
|
lineno = line;
|
|
}
|
|
|
|
public Node(int nodeType, Node child, int line) {
|
|
this(nodeType, child);
|
|
lineno = line;
|
|
}
|
|
|
|
public Node(int nodeType, Node left, Node right, int line) {
|
|
this(nodeType, left, right);
|
|
lineno = line;
|
|
}
|
|
|
|
public Node(int nodeType, Node left, Node mid, Node right, int line) {
|
|
this(nodeType, left, mid, right);
|
|
lineno = line;
|
|
}
|
|
|
|
public static Node newNumber(double number) {
|
|
return new NumberNode(number);
|
|
}
|
|
|
|
public static Node newString(String str) {
|
|
return new StringNode(Token.STRING, str);
|
|
}
|
|
|
|
public static Node newString(int type, String str) {
|
|
return new StringNode(type, str);
|
|
}
|
|
|
|
public int getType() {
|
|
return type;
|
|
}
|
|
|
|
public void setType(int type) {
|
|
this.type = type;
|
|
}
|
|
|
|
public boolean hasChildren() {
|
|
return first != null;
|
|
}
|
|
|
|
public Node getFirstChild() {
|
|
return first;
|
|
}
|
|
|
|
public Node getLastChild() {
|
|
return last;
|
|
}
|
|
|
|
public Node getNext() {
|
|
return next;
|
|
}
|
|
|
|
public Node getChildBefore(Node child) {
|
|
if (child == first)
|
|
return null;
|
|
Node n = first;
|
|
while (n.next != child) {
|
|
n = n.next;
|
|
if (n == null)
|
|
throw new RuntimeException("node is not a child");
|
|
}
|
|
return n;
|
|
}
|
|
|
|
public Node getLastSibling() {
|
|
Node n = this;
|
|
while (n.next != null) {
|
|
n = n.next;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
public void addChildToFront(Node child) {
|
|
child.next = first;
|
|
first = child;
|
|
if (last == null) {
|
|
last = child;
|
|
}
|
|
}
|
|
|
|
public void addChildToBack(Node child) {
|
|
child.next = null;
|
|
if (last == null) {
|
|
first = last = child;
|
|
return;
|
|
}
|
|
last.next = child;
|
|
last = child;
|
|
}
|
|
|
|
public void addChildrenToFront(Node children) {
|
|
Node lastSib = children.getLastSibling();
|
|
lastSib.next = first;
|
|
first = children;
|
|
if (last == null) {
|
|
last = lastSib;
|
|
}
|
|
}
|
|
|
|
public void addChildrenToBack(Node children) {
|
|
if (last != null) {
|
|
last.next = children;
|
|
}
|
|
last = children.getLastSibling();
|
|
if (first == null) {
|
|
first = children;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add 'child' before 'node'.
|
|
*/
|
|
public void addChildBefore(Node newChild, Node node) {
|
|
if (newChild.next != null)
|
|
throw new RuntimeException(
|
|
"newChild had siblings in addChildBefore");
|
|
if (first == node) {
|
|
newChild.next = first;
|
|
first = newChild;
|
|
return;
|
|
}
|
|
Node prev = getChildBefore(node);
|
|
addChildAfter(newChild, prev);
|
|
}
|
|
|
|
/**
|
|
* Add 'child' after 'node'.
|
|
*/
|
|
public void addChildAfter(Node newChild, Node node) {
|
|
if (newChild.next != null)
|
|
throw new RuntimeException(
|
|
"newChild had siblings in addChildAfter");
|
|
newChild.next = node.next;
|
|
node.next = newChild;
|
|
if (last == node)
|
|
last = newChild;
|
|
}
|
|
|
|
public void removeChild(Node child) {
|
|
Node prev = getChildBefore(child);
|
|
if (prev == null)
|
|
first = first.next;
|
|
else
|
|
prev.next = child.next;
|
|
if (child == last) last = prev;
|
|
child.next = null;
|
|
}
|
|
|
|
public void replaceChild(Node child, Node newChild) {
|
|
newChild.next = child.next;
|
|
if (child == first) {
|
|
first = newChild;
|
|
} else {
|
|
Node prev = getChildBefore(child);
|
|
prev.next = newChild;
|
|
}
|
|
if (child == last)
|
|
last = newChild;
|
|
child.next = null;
|
|
}
|
|
|
|
public void replaceChildAfter(Node prevChild, Node newChild) {
|
|
Node child = prevChild.next;
|
|
newChild.next = child.next;
|
|
prevChild.next = newChild;
|
|
if (child == last)
|
|
last = newChild;
|
|
child.next = null;
|
|
}
|
|
|
|
private static final String propToString(int propType)
|
|
{
|
|
if (Token.printTrees) {
|
|
// If Context.printTrees is false, the compiler
|
|
// can remove all these strings.
|
|
switch (propType) {
|
|
case FUNCTION_PROP: return "function";
|
|
case LOCAL_PROP: return "local";
|
|
case LOCAL_BLOCK_PROP: return "local_block";
|
|
case REGEXP_PROP: return "regexp";
|
|
case CASEARRAY_PROP: return "casearray";
|
|
|
|
case TARGETBLOCK_PROP: return "targetblock";
|
|
case VARIABLE_PROP: return "variable";
|
|
case ISNUMBER_PROP: return "isnumber";
|
|
case DIRECTCALL_PROP: return "directcall";
|
|
|
|
case SPECIALCALL_PROP: return "specialcall";
|
|
case SKIP_INDEXES_PROP: return "skip_indexes";
|
|
case OBJECT_IDS_PROP: return "object_ids_prop";
|
|
case INCRDECR_PROP: return "incrdecr_prop";
|
|
case CATCH_SCOPE_PROP: return "catch_scope_prop";
|
|
case LABEL_ID_PROP: return "label_id_prop";
|
|
case MEMBER_TYPE_PROP: return "member_type_prop";
|
|
case NAME_PROP: return "name_prop";
|
|
case CONTROL_BLOCK_PROP: return "control_block_prop";
|
|
case PARENTHESIZED_PROP: return "parenthesized_prop";
|
|
case GENERATOR_END_PROP: return "generator_end";
|
|
case DESTRUCTURING_ARRAY_LENGTH:
|
|
return "destructuring_array_length";
|
|
case DESTRUCTURING_NAMES:return "destructuring_names";
|
|
|
|
default: Kit.codeBug();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private PropListItem lookupProperty(int propType)
|
|
{
|
|
PropListItem x = propListHead;
|
|
while (x != null && propType != x.type) {
|
|
x = x.next;
|
|
}
|
|
return x;
|
|
}
|
|
|
|
private PropListItem ensureProperty(int propType)
|
|
{
|
|
PropListItem item = lookupProperty(propType);
|
|
if (item == null) {
|
|
item = new PropListItem();
|
|
item.type = propType;
|
|
item.next = propListHead;
|
|
propListHead = item;
|
|
}
|
|
return item;
|
|
}
|
|
|
|
public void removeProp(int propType)
|
|
{
|
|
PropListItem x = propListHead;
|
|
if (x != null) {
|
|
PropListItem prev = null;
|
|
while (x.type != propType) {
|
|
prev = x;
|
|
x = x.next;
|
|
if (x == null) { return; }
|
|
}
|
|
if (prev == null) {
|
|
propListHead = x.next;
|
|
} else {
|
|
prev.next = x.next;
|
|
}
|
|
}
|
|
}
|
|
|
|
public Object getProp(int propType)
|
|
{
|
|
PropListItem item = lookupProperty(propType);
|
|
if (item == null) { return null; }
|
|
return item.objectValue;
|
|
}
|
|
|
|
public int getIntProp(int propType, int defaultValue)
|
|
{
|
|
PropListItem item = lookupProperty(propType);
|
|
if (item == null) { return defaultValue; }
|
|
return item.intValue;
|
|
}
|
|
|
|
public int getExistingIntProp(int propType)
|
|
{
|
|
PropListItem item = lookupProperty(propType);
|
|
if (item == null) { Kit.codeBug(); }
|
|
return item.intValue;
|
|
}
|
|
|
|
public void putProp(int propType, Object prop)
|
|
{
|
|
if (prop == null) {
|
|
removeProp(propType);
|
|
} else {
|
|
PropListItem item = ensureProperty(propType);
|
|
item.objectValue = prop;
|
|
}
|
|
}
|
|
|
|
public void putIntProp(int propType, int prop)
|
|
{
|
|
PropListItem item = ensureProperty(propType);
|
|
item.intValue = prop;
|
|
}
|
|
|
|
public int getLineno() {
|
|
return lineno;
|
|
}
|
|
|
|
/** Can only be called when <tt>getType() == Token.NUMBER</tt> */
|
|
public final double getDouble() {
|
|
return ((NumberNode)this).number;
|
|
}
|
|
|
|
public final void setDouble(double number) {
|
|
((NumberNode)this).number = number;
|
|
}
|
|
|
|
/** Can only be called when node has String context. */
|
|
public final String getString() {
|
|
return ((StringNode)this).str;
|
|
}
|
|
|
|
/** Can only be called when node has String context. */
|
|
public final void setString(String s) {
|
|
if (s == null) Kit.codeBug();
|
|
((StringNode)this).str = s;
|
|
}
|
|
|
|
/** Can only be called when node has String context. */
|
|
public final Scope getScope() {
|
|
return ((StringNode)this).scope;
|
|
}
|
|
|
|
/** Can only be called when node has String context. */
|
|
public final void setScope(Scope s) {
|
|
if (s == null) Kit.codeBug();
|
|
if (!(this instanceof StringNode)) {
|
|
throw Kit.codeBug();
|
|
}
|
|
((StringNode)this).scope = s;
|
|
}
|
|
|
|
public static Node newTarget()
|
|
{
|
|
return new Node(Token.TARGET);
|
|
}
|
|
|
|
public final int labelId()
|
|
{
|
|
if (type != Token.TARGET && type != Token.YIELD) Kit.codeBug();
|
|
return getIntProp(LABEL_ID_PROP, -1);
|
|
}
|
|
|
|
public void labelId(int labelId)
|
|
{
|
|
if (type != Token.TARGET && type != Token.YIELD) Kit.codeBug();
|
|
putIntProp(LABEL_ID_PROP, labelId);
|
|
}
|
|
|
|
|
|
/**
|
|
* Does consistent-return analysis on the function body when strict mode is
|
|
* enabled.
|
|
*
|
|
* function (x) { return (x+1) }
|
|
* is ok, but
|
|
* function (x) { if (x < 0) return (x+1); }
|
|
* is not becuase the function can potentially return a value when the
|
|
* condition is satisfied and if not, the function does not explicitly
|
|
* return value.
|
|
*
|
|
* This extends to checking mismatches such as "return" and "return <value>"
|
|
* used in the same function. Warnings are not emitted if inconsistent
|
|
* returns exist in code that can be statically shown to be unreachable.
|
|
* Ex.
|
|
* function (x) { while (true) { ... if (..) { return value } ... } }
|
|
* emits no warning. However if the loop had a break statement, then a
|
|
* warning would be emitted.
|
|
*
|
|
* The consistency analysis looks at control structures such as loops, ifs,
|
|
* switch, try-catch-finally blocks, examines the reachable code paths and
|
|
* warns the user about an inconsistent set of termination possibilities.
|
|
*
|
|
* Caveat: Since the parser flattens many control structures into almost
|
|
* straight-line code with gotos, it makes such analysis hard. Hence this
|
|
* analyser is written to taken advantage of patterns of code generated by
|
|
* the parser (for loops, try blocks and such) and does not do a full
|
|
* control flow analysis of the gotos and break/continue statements.
|
|
* Future changes to the parser will affect this analysis.
|
|
*/
|
|
|
|
/**
|
|
* These flags enumerate the possible ways a statement/function can
|
|
* terminate. These flags are used by endCheck() and by the Parser to
|
|
* detect inconsistent return usage.
|
|
*
|
|
* END_UNREACHED is reserved for code paths that are assumed to always be
|
|
* able to execute (example: throw, continue)
|
|
*
|
|
* END_DROPS_OFF indicates if the statement can transfer control to the
|
|
* next one. Statement such as return dont. A compound statement may have
|
|
* some branch that drops off control to the next statement.
|
|
*
|
|
* END_RETURNS indicates that the statement can return (without arguments)
|
|
* END_RETURNS_VALUE indicates that the statement can return a value.
|
|
*
|
|
* A compound statement such as
|
|
* if (condition) {
|
|
* return value;
|
|
* }
|
|
* Will be detected as (END_DROPS_OFF | END_RETURN_VALUE) by endCheck()
|
|
*/
|
|
static final int END_UNREACHED = 0;
|
|
static final int END_DROPS_OFF = 1;
|
|
static final int END_RETURNS = 2;
|
|
static final int END_RETURNS_VALUE = 4;
|
|
static final int END_YIELDS = 8;
|
|
|
|
/**
|
|
* Checks that every return usage in a function body is consistent with the
|
|
* requirements of strict-mode.
|
|
* @return true if the function satisfies strict mode requirement.
|
|
*/
|
|
public boolean hasConsistentReturnUsage()
|
|
{
|
|
int n = endCheck();
|
|
return (n & END_RETURNS_VALUE) == 0 ||
|
|
(n & (END_DROPS_OFF|END_RETURNS|END_YIELDS)) == 0;
|
|
}
|
|
|
|
/**
|
|
* Returns in the then and else blocks must be consistent with each other.
|
|
* If there is no else block, then the return statement can fall through.
|
|
* @return logical OR of END_* flags
|
|
*/
|
|
private int endCheckIf()
|
|
{
|
|
Node th, el;
|
|
int rv = END_UNREACHED;
|
|
|
|
th = next;
|
|
el = ((Jump)this).target;
|
|
|
|
rv = th.endCheck();
|
|
|
|
if (el != null)
|
|
rv |= el.endCheck();
|
|
else
|
|
rv |= END_DROPS_OFF;
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Consistency of return statements is checked between the case statements.
|
|
* If there is no default, then the switch can fall through. If there is a
|
|
* default,we check to see if all code paths in the default return or if
|
|
* there is a code path that can fall through.
|
|
* @return logical OR of END_* flags
|
|
*/
|
|
private int endCheckSwitch()
|
|
{
|
|
Node n;
|
|
int rv = END_UNREACHED;
|
|
|
|
// examine the cases
|
|
for (n = first.next; n != null; n = n.next)
|
|
{
|
|
if (n.type == Token.CASE) {
|
|
rv |= ((Jump)n).target.endCheck();
|
|
} else
|
|
break;
|
|
}
|
|
|
|
// we don't care how the cases drop into each other
|
|
rv &= ~END_DROPS_OFF;
|
|
|
|
// examine the default
|
|
n = ((Jump)this).getDefault();
|
|
if (n != null)
|
|
rv |= n.endCheck();
|
|
else
|
|
rv |= END_DROPS_OFF;
|
|
|
|
// remove the switch block
|
|
rv |= getIntProp(CONTROL_BLOCK_PROP, END_UNREACHED);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* If the block has a finally, return consistency is checked in the
|
|
* finally block. If all code paths in the finally returns, then the
|
|
* returns in the try-catch blocks don't matter. If there is a code path
|
|
* that does not return or if there is no finally block, the returns
|
|
* of the try and catch blocks are checked for mismatch.
|
|
* @return logical OR of END_* flags
|
|
*/
|
|
private int endCheckTry()
|
|
{
|
|
Node n;
|
|
int rv = END_UNREACHED;
|
|
|
|
// check the finally if it exists
|
|
n = ((Jump)this).getFinally();
|
|
if(n != null) {
|
|
rv = n.next.first.endCheck();
|
|
} else {
|
|
rv = END_DROPS_OFF;
|
|
}
|
|
|
|
// if the finally block always returns, then none of the returns
|
|
// in the try or catch blocks matter
|
|
if ((rv & END_DROPS_OFF) != 0) {
|
|
rv &= ~END_DROPS_OFF;
|
|
|
|
// examine the try block
|
|
rv |= first.endCheck();
|
|
|
|
// check each catch block
|
|
n = ((Jump)this).target;
|
|
if (n != null)
|
|
{
|
|
// point to the first catch_scope
|
|
for (n = n.next.first; n != null; n = n.next.next)
|
|
{
|
|
// check the block of user code in the catch_scope
|
|
rv |= n.next.first.next.first.endCheck();
|
|
}
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Return statement in the loop body must be consistent. The default
|
|
* assumption for any kind of a loop is that it will eventually terminate.
|
|
* The only exception is a loop with a constant true condition. Code that
|
|
* follows such a loop is examined only if one can statically determine
|
|
* that there is a break out of the loop.
|
|
* for(<> ; <>; <>) {}
|
|
* for(<> in <> ) {}
|
|
* while(<>) { }
|
|
* do { } while(<>)
|
|
* @return logical OR of END_* flags
|
|
*/
|
|
private int endCheckLoop()
|
|
{
|
|
Node n;
|
|
int rv = END_UNREACHED;
|
|
|
|
// To find the loop body, we look at the second to last node of the
|
|
// loop node, which should be the predicate that the loop should
|
|
// satisfy.
|
|
// The target of the predicate is the loop-body for all 4 kinds of
|
|
// loops.
|
|
for (n = first; n.next != last; n = n.next) {
|
|
/* skip */
|
|
}
|
|
if (n.type != Token.IFEQ)
|
|
return END_DROPS_OFF;
|
|
|
|
// The target's next is the loop body block
|
|
rv = ((Jump)n).target.next.endCheck();
|
|
|
|
// check to see if the loop condition is true
|
|
if (n.first.type == Token.TRUE)
|
|
rv &= ~END_DROPS_OFF;
|
|
|
|
// look for effect of breaks
|
|
rv |= getIntProp(CONTROL_BLOCK_PROP, END_UNREACHED);
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
/**
|
|
* A general block of code is examined statement by statement. If any
|
|
* statement (even compound ones) returns in all branches, then subsequent
|
|
* statements are not examined.
|
|
* @return logical OR of END_* flags
|
|
*/
|
|
private int endCheckBlock()
|
|
{
|
|
Node n;
|
|
int rv = END_DROPS_OFF;
|
|
|
|
// check each statment and if the statement can continue onto the next
|
|
// one, then check the next statement
|
|
for (n=first; ((rv & END_DROPS_OFF) != 0) && n != null; n = n.next)
|
|
{
|
|
rv &= ~END_DROPS_OFF;
|
|
rv |= n.endCheck();
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* A labelled statement implies that there maybe a break to the label. The
|
|
* function processes the labelled statement and then checks the
|
|
* CONTROL_BLOCK_PROP property to see if there is ever a break to the
|
|
* particular label.
|
|
* @return logical OR of END_* flags
|
|
*/
|
|
private int endCheckLabel()
|
|
{
|
|
int rv = END_UNREACHED;
|
|
|
|
rv = next.endCheck();
|
|
rv |= getIntProp(CONTROL_BLOCK_PROP, END_UNREACHED);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* When a break is encountered annotate the statement being broken
|
|
* out of by setting its CONTROL_BLOCK_PROP property.
|
|
* @return logical OR of END_* flags
|
|
*/
|
|
private int endCheckBreak()
|
|
{
|
|
Node n = ((Jump) this).jumpNode;
|
|
n.putIntProp(CONTROL_BLOCK_PROP, END_DROPS_OFF);
|
|
return END_UNREACHED;
|
|
}
|
|
|
|
/**
|
|
* endCheck() examines the body of a function, doing a basic reachability
|
|
* analysis and returns a combination of flags END_* flags that indicate
|
|
* how the function execution can terminate. These constitute only the
|
|
* pessimistic set of termination conditions. It is possible that at
|
|
* runtime certain code paths will never be actually taken. Hence this
|
|
* analysis will flag errors in cases where there may not be errors.
|
|
* @return logical OR of END_* flags
|
|
*/
|
|
private int endCheck()
|
|
{
|
|
switch(type)
|
|
{
|
|
case Token.BREAK:
|
|
return endCheckBreak();
|
|
|
|
case Token.EXPR_VOID:
|
|
if (this.first != null)
|
|
return first.endCheck();
|
|
return END_DROPS_OFF;
|
|
|
|
case Token.YIELD:
|
|
return END_YIELDS;
|
|
|
|
case Token.CONTINUE:
|
|
case Token.THROW:
|
|
return END_UNREACHED;
|
|
|
|
case Token.RETURN:
|
|
if (this.first != null)
|
|
return END_RETURNS_VALUE;
|
|
else
|
|
return END_RETURNS;
|
|
|
|
case Token.TARGET:
|
|
if (next != null)
|
|
return next.endCheck();
|
|
else
|
|
return END_DROPS_OFF;
|
|
|
|
case Token.LOOP:
|
|
return endCheckLoop();
|
|
|
|
case Token.LOCAL_BLOCK:
|
|
case Token.BLOCK:
|
|
// there are several special kinds of blocks
|
|
if (first == null)
|
|
return END_DROPS_OFF;
|
|
|
|
switch(first.type) {
|
|
case Token.LABEL:
|
|
return first.endCheckLabel();
|
|
|
|
case Token.IFNE:
|
|
return first.endCheckIf();
|
|
|
|
case Token.SWITCH:
|
|
return first.endCheckSwitch();
|
|
|
|
case Token.TRY:
|
|
return first.endCheckTry();
|
|
|
|
default:
|
|
return endCheckBlock();
|
|
}
|
|
|
|
default:
|
|
return END_DROPS_OFF;
|
|
}
|
|
}
|
|
|
|
public boolean hasSideEffects()
|
|
{
|
|
switch (type) {
|
|
case Token.EXPR_VOID:
|
|
case Token.COMMA:
|
|
if (last != null)
|
|
return last.hasSideEffects();
|
|
else
|
|
return true;
|
|
|
|
case Token.HOOK:
|
|
if (first == null ||
|
|
first.next == null ||
|
|
first.next.next == null)
|
|
Kit.codeBug();
|
|
return first.next.hasSideEffects() &&
|
|
first.next.next.hasSideEffects();
|
|
|
|
case Token.AND:
|
|
case Token.OR:
|
|
if (first == null || last == null)
|
|
Kit.codeBug();
|
|
return first.hasSideEffects() || last.hasSideEffects();
|
|
|
|
case Token.ERROR: // Avoid cascaded error messages
|
|
case Token.EXPR_RESULT:
|
|
case Token.ASSIGN:
|
|
case Token.ASSIGN_ADD:
|
|
case Token.ASSIGN_SUB:
|
|
case Token.ASSIGN_MUL:
|
|
case Token.ASSIGN_DIV:
|
|
case Token.ASSIGN_MOD:
|
|
case Token.ASSIGN_BITOR:
|
|
case Token.ASSIGN_BITXOR:
|
|
case Token.ASSIGN_BITAND:
|
|
case Token.ASSIGN_LSH:
|
|
case Token.ASSIGN_RSH:
|
|
case Token.ASSIGN_URSH:
|
|
case Token.ENTERWITH:
|
|
case Token.LEAVEWITH:
|
|
case Token.RETURN:
|
|
case Token.GOTO:
|
|
case Token.IFEQ:
|
|
case Token.IFNE:
|
|
case Token.NEW:
|
|
case Token.DELPROP:
|
|
case Token.SETNAME:
|
|
case Token.SETPROP:
|
|
case Token.SETELEM:
|
|
case Token.CALL:
|
|
case Token.THROW:
|
|
case Token.RETHROW:
|
|
case Token.SETVAR:
|
|
case Token.CATCH_SCOPE:
|
|
case Token.RETURN_RESULT:
|
|
case Token.SET_REF:
|
|
case Token.DEL_REF:
|
|
case Token.REF_CALL:
|
|
case Token.TRY:
|
|
case Token.SEMI:
|
|
case Token.INC:
|
|
case Token.DEC:
|
|
case Token.EXPORT:
|
|
case Token.IMPORT:
|
|
case Token.IF:
|
|
case Token.ELSE:
|
|
case Token.SWITCH:
|
|
case Token.WHILE:
|
|
case Token.DO:
|
|
case Token.FOR:
|
|
case Token.BREAK:
|
|
case Token.CONTINUE:
|
|
case Token.VAR:
|
|
case Token.CONST:
|
|
case Token.LET:
|
|
case Token.LETEXPR:
|
|
case Token.WITH:
|
|
case Token.WITHEXPR:
|
|
case Token.CATCH:
|
|
case Token.FINALLY:
|
|
case Token.BLOCK:
|
|
case Token.LABEL:
|
|
case Token.TARGET:
|
|
case Token.LOOP:
|
|
case Token.JSR:
|
|
case Token.SETPROP_OP:
|
|
case Token.SETELEM_OP:
|
|
case Token.LOCAL_BLOCK:
|
|
case Token.SET_REF_OP:
|
|
case Token.YIELD:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString()
|
|
{
|
|
if (Token.printTrees) {
|
|
StringBuffer sb = new StringBuffer();
|
|
toString(new ObjToIntMap(), sb);
|
|
return sb.toString();
|
|
}
|
|
return String.valueOf(type);
|
|
}
|
|
|
|
private void toString(ObjToIntMap printIds, StringBuffer sb)
|
|
{
|
|
if (Token.printTrees) {
|
|
sb.append(Token.name(type));
|
|
if (this instanceof StringNode) {
|
|
sb.append(' ');
|
|
sb.append(getString());
|
|
Scope scope = getScope();
|
|
if (scope != null) {
|
|
sb.append("[scope: ");
|
|
appendPrintId(scope, printIds, sb);
|
|
sb.append("]");
|
|
}
|
|
} else if (this instanceof Node.Scope) {
|
|
if (this instanceof ScriptOrFnNode) {
|
|
ScriptOrFnNode sof = (ScriptOrFnNode)this;
|
|
if (this instanceof FunctionNode) {
|
|
FunctionNode fn = (FunctionNode)this;
|
|
sb.append(' ');
|
|
sb.append(fn.getFunctionName());
|
|
}
|
|
sb.append(" [source name: ");
|
|
sb.append(sof.getSourceName());
|
|
sb.append("] [encoded source length: ");
|
|
sb.append(sof.getEncodedSourceEnd()
|
|
- sof.getEncodedSourceStart());
|
|
sb.append("] [base line: ");
|
|
sb.append(sof.getBaseLineno());
|
|
sb.append("] [end line: ");
|
|
sb.append(sof.getEndLineno());
|
|
sb.append(']');
|
|
}
|
|
if (((Node.Scope)this).symbolTable != null) {
|
|
sb.append(" [scope ");
|
|
appendPrintId(this, printIds, sb);
|
|
sb.append(": ");
|
|
Iterator<String> iter =
|
|
((Node.Scope) this).symbolTable.keySet().iterator();
|
|
while (iter.hasNext()) {
|
|
sb.append(iter.next());
|
|
sb.append(" ");
|
|
}
|
|
sb.append("]");
|
|
}
|
|
} else if (this instanceof Jump) {
|
|
Jump jump = (Jump)this;
|
|
if (type == Token.BREAK || type == Token.CONTINUE) {
|
|
sb.append(" [label: ");
|
|
appendPrintId(jump.getJumpStatement(), printIds, sb);
|
|
sb.append(']');
|
|
} else if (type == Token.TRY) {
|
|
Node catchNode = jump.target;
|
|
Node finallyTarget = jump.getFinally();
|
|
if (catchNode != null) {
|
|
sb.append(" [catch: ");
|
|
appendPrintId(catchNode, printIds, sb);
|
|
sb.append(']');
|
|
}
|
|
if (finallyTarget != null) {
|
|
sb.append(" [finally: ");
|
|
appendPrintId(finallyTarget, printIds, sb);
|
|
sb.append(']');
|
|
}
|
|
} else if (type == Token.LABEL || type == Token.LOOP
|
|
|| type == Token.SWITCH)
|
|
{
|
|
sb.append(" [break: ");
|
|
appendPrintId(jump.target, printIds, sb);
|
|
sb.append(']');
|
|
if (type == Token.LOOP) {
|
|
sb.append(" [continue: ");
|
|
appendPrintId(jump.getContinue(), printIds, sb);
|
|
sb.append(']');
|
|
}
|
|
} else {
|
|
sb.append(" [target: ");
|
|
appendPrintId(jump.target, printIds, sb);
|
|
sb.append(']');
|
|
}
|
|
} else if (type == Token.NUMBER) {
|
|
sb.append(' ');
|
|
sb.append(getDouble());
|
|
} else if (type == Token.TARGET) {
|
|
sb.append(' ');
|
|
appendPrintId(this, printIds, sb);
|
|
}
|
|
if (lineno != -1) {
|
|
sb.append(' ');
|
|
sb.append(lineno);
|
|
}
|
|
|
|
for (PropListItem x = propListHead; x != null; x = x.next) {
|
|
int type = x.type;
|
|
sb.append(" [");
|
|
sb.append(propToString(type));
|
|
sb.append(": ");
|
|
String value;
|
|
switch (type) {
|
|
case TARGETBLOCK_PROP : // can't add this as it recurses
|
|
value = "target block property";
|
|
break;
|
|
case LOCAL_BLOCK_PROP : // can't add this as it is dull
|
|
value = "last local block";
|
|
break;
|
|
case ISNUMBER_PROP:
|
|
switch (x.intValue) {
|
|
case BOTH:
|
|
value = "both";
|
|
break;
|
|
case RIGHT:
|
|
value = "right";
|
|
break;
|
|
case LEFT:
|
|
value = "left";
|
|
break;
|
|
default:
|
|
throw Kit.codeBug();
|
|
}
|
|
break;
|
|
case SPECIALCALL_PROP:
|
|
switch (x.intValue) {
|
|
case SPECIALCALL_EVAL:
|
|
value = "eval";
|
|
break;
|
|
case SPECIALCALL_WITH:
|
|
value = "with";
|
|
break;
|
|
default:
|
|
// NON_SPECIALCALL should not be stored
|
|
throw Kit.codeBug();
|
|
}
|
|
break;
|
|
case OBJECT_IDS_PROP: {
|
|
Object[] a = (Object[]) x.objectValue;
|
|
value = "[";
|
|
for (int i=0; i < a.length; i++) {
|
|
value += a[i].toString();
|
|
if (i+1 < a.length)
|
|
value += ", ";
|
|
}
|
|
value += "]";
|
|
break;
|
|
}
|
|
default :
|
|
Object obj = x.objectValue;
|
|
if (obj != null) {
|
|
value = obj.toString();
|
|
} else {
|
|
value = String.valueOf(x.intValue);
|
|
}
|
|
break;
|
|
}
|
|
sb.append(value);
|
|
sb.append(']');
|
|
}
|
|
}
|
|
}
|
|
|
|
public String toStringTree(ScriptOrFnNode treeTop) {
|
|
if (Token.printTrees) {
|
|
StringBuffer sb = new StringBuffer();
|
|
toStringTreeHelper(treeTop, this, null, 0, sb);
|
|
return sb.toString();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static void toStringTreeHelper(ScriptOrFnNode treeTop, Node n,
|
|
ObjToIntMap printIds,
|
|
int level, StringBuffer sb)
|
|
{
|
|
if (Token.printTrees) {
|
|
if (printIds == null) {
|
|
printIds = new ObjToIntMap();
|
|
generatePrintIds(treeTop, printIds);
|
|
}
|
|
for (int i = 0; i != level; ++i) {
|
|
sb.append(" ");
|
|
}
|
|
n.toString(printIds, sb);
|
|
sb.append('\n');
|
|
for (Node cursor = n.getFirstChild(); cursor != null;
|
|
cursor = cursor.getNext())
|
|
{
|
|
if (cursor.getType() == Token.FUNCTION) {
|
|
int fnIndex = cursor.getExistingIntProp(Node.FUNCTION_PROP);
|
|
FunctionNode fn = treeTop.getFunctionNode(fnIndex);
|
|
toStringTreeHelper(fn, fn, null, level + 1, sb);
|
|
} else {
|
|
toStringTreeHelper(treeTop, cursor, printIds, level + 1, sb);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void generatePrintIds(Node n, ObjToIntMap map)
|
|
{
|
|
if (Token.printTrees) {
|
|
map.put(n, map.size());
|
|
for (Node cursor = n.getFirstChild(); cursor != null;
|
|
cursor = cursor.getNext())
|
|
{
|
|
generatePrintIds(cursor, map);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void appendPrintId(Node n, ObjToIntMap printIds,
|
|
StringBuffer sb)
|
|
{
|
|
if (Token.printTrees) {
|
|
if (n != null) {
|
|
int id = printIds.get(n, -1);
|
|
sb.append('#');
|
|
if (id != -1) {
|
|
sb.append(id + 1);
|
|
} else {
|
|
sb.append("<not_available>");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int type; // type of the node; Token.NAME for example
|
|
Node next; // next sibling
|
|
private Node first; // first element of a linked list of children
|
|
private Node last; // last element of a linked list of children
|
|
protected int lineno = -1;
|
|
|
|
/**
|
|
* Linked list of properties. Since vast majority of nodes would have
|
|
* no more then 2 properties, linked list saves memory and provides
|
|
* fast lookup. If this does not holds, propListHead can be replaced
|
|
* by UintMap.
|
|
*/
|
|
private PropListItem propListHead;
|
|
}
|