Updated JS function traversal ordering (bug 767566)

This commit is contained in:
Matt Basta 2012-06-29 16:41:39 -07:00
Родитель c294750842
Коммит d8427aaab3
3 изменённых файлов: 129 добавлений и 43 удалений

Просмотреть файл

@ -121,52 +121,62 @@ def test_identifier(traverser, name):
def _function(traverser, node):
"Prevents code duplication"
me = JSObject()
def wrap(traverser, node):
me = JSObject()
# Replace the current context with a prototypeable JS object.
traverser._pop_context()
traverser._push_context(me)
traverser._debug("THIS_PUSH")
traverser.this_stack.append(me) # Allow references to "this"
traverser.function_collection.append([])
# Declare parameters in the local scope
params = []
for param in node["params"]:
if param["type"] == "Identifier":
params.append(param["name"])
elif param["type"] == "ArrayPattern":
for element in param["elements"]:
# Array destructuring in function prototypes? LOL!
if element is None or element["type"] != "Identifier":
continue
params.append(element["name"])
# Replace the current context with a prototypeable JS object.
traverser._pop_context()
traverser._push_context(me)
traverser._debug("THIS_PUSH")
traverser.this_stack.append(me) # Allow references to "this"
local_context = traverser._peek_context(1)
for param in params:
var = JSWrapper(lazy=True, traverser=traverser)
# Declare parameters in the local scope
params = []
for param in node["params"]:
if param["type"] == "Identifier":
params.append(param["name"])
elif param["type"] == "ArrayPattern":
for element in param["elements"]:
# Array destructuring in function prototypes? LOL!
if element is None or element["type"] != "Identifier":
continue
params.append(element["name"])
# We can assume that the params are static because we don't care about
# what calls the function. We want to know whether the function solely
# returns static values. If so, it is a static function.
#var.dynamic = False
local_context.set(param, var)
local_context = traverser._peek_context(1)
for param in params:
var = JSWrapper(lazy=True, traverser=traverser)
traverser._traverse_node(node["body"])
# We can assume that the params are static because we don't care
# about what calls the function. We want to know whether the
# function solely returns static values. If so, it is a static
# function.
local_context.set(param, var)
# Since we need to manually manage the "this" stack, pop off that context.
traverser._debug("THIS_POP")
traverser.this_stack.pop()
traverser._traverse_node(node["body"])
return me
# Since we need to manually manage the "this" stack, pop off that
# context.
traverser._debug("THIS_POP")
traverser.this_stack.pop()
# Call all of the function collection's members to traverse all of the
# child functions.
func_coll = traverser.function_collection.pop()
for func in func_coll:
func()
# Put the function off for traversal at the end of the current block scope.
traverser.function_collection[-1].append(lambda: wrap(traverser, node))
return JSWrapper(traverser=traverser, callable=True, dirty=True)
def _define_function(traverser, node):
"Makes a function happy"
me = _function(traverser, node)
me = JSWrapper(value=me,
traverser=traverser,
callable=True)
traverser._peek_context(2).set(node["id"]["name"], me)
return True
@ -175,12 +185,7 @@ def _define_function(traverser, node):
def _func_expr(traverser, node):
"Represents a lambda function"
# Collect the result as an object
results = _function(traverser, node)
if not isinstance(results, JSWrapper):
results = JSWrapper(value=results, traverser=traverser)
results.callable = True
return results
return _function(traverser, node)
def _define_with(traverser, node):

Просмотреть файл

@ -11,8 +11,8 @@ from .predefinedentities import GLOBAL_ENTITIES, BANNED_IDENTIFIERS
DEBUG = False
class Traverser:
"Traverses the AST Tree and determines problems with a chunk of JS."
class Traverser(object):
"""Traverses the AST Tree and determines problems with a chunk of JS."""
def __init__(self, err, filename, start_line=0, context=None):
self.err = err
@ -29,6 +29,9 @@ class Traverser:
self.can_use_this = False
self.this_stack = []
# For ordering of function traversal.
self.function_collection = []
# For debugging
self.debug_level = 0
@ -54,7 +57,12 @@ class Traverser:
self._debug("START>>")
try:
self.function_collection.append([])
self._traverse_node(data)
func_coll = self.function_collection.pop()
for func in func_coll:
func()
except Exception:
print "Exception in JS traversal; %s (%d;%d)" % (
self.filename, self.line, self.position)
@ -77,12 +85,12 @@ class Traverser:
if "__traversal" in node and node["__traversal"] is not None:
return node["__traversal"]
if isinstance(node, (str, unicode)):
if isinstance(node, types.StringTypes):
return JSWrapper(JSLiteral(node), traverser=self)
elif "type" not in node or node["type"] not in DEFINITIONS:
return JSWrapper(JSObject(), traverser=self, dirty=True)
self._debug("TRAVERSE>>%s" % (node["type"]))
self._debug("TRAVERSE>>%s" % node["type"])
self.debug_level += 1
# Extract location information if it's available

Просмотреть файл

@ -0,0 +1,73 @@
from js_helper import TestCase
class TestFunctionTraversal(TestCase):
"""
Consider the following tree:
- body
|-function a()
| |- foo
| |- bar
|-zip
|-function b()
| |-abc
| |-def
|-zap
In the old traversal technique, it would be evaluated in the order:
body a() foo bar zip b() abc def zap
If the tree is considered as a graph, this would be prefix notation
traversal.
This is not optimal, however, as JS commonly uses callbacks which are set
up before delegation code. The new traversal technique should access nodes
in the following order:
body zip zap a() foo bar b() abc def
If the tree is considered a graph, this would be a custom prefix notation
traversal where all non-function nodes are traversed before all function
nodes.
"""
def test_function_declaration_order(self):
"""Test that function declarations happen in the right time."""
self.run_script("""
foo = "first";
function test() {
foo = "second";
}
bar = foo;
""")
self.assert_var_eq("bar", "first")
self.assert_var_eq("foo", "second")
def test_function_expression_order(self):
"""Test that function expressions happen in the right time."""
self.run_script("""
foo = "first"
var x = function() {
foo = "second";
}
bar = foo;
""")
self.assert_var_eq("bar", "first")
self.assert_var_eq("foo", "second")
def test_nested_functions(self):
"""Test that nested functions are considered in the right order."""
self.run_script("""
foo = "first"
function test() {
function a() {foo = "second"}
foo = "third";
}
""")
self.assert_var_eq("foo", "second")