Bug fixes and updates to satiate functional test.

This commit is contained in:
Matt Basta 2011-04-22 05:37:28 +00:00
Родитель 6e2e1fd6b4
Коммит f87e4757b3
8 изменённых файлов: 133 добавлений и 95 удалений

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

@ -9,9 +9,11 @@ def _do_test(path):
def _do_test_raw(script, path="foo"):
"Performs a test on a JS file"
err = validator.testcases.scripting.traverser.MockBundler()
validator.testcases.scripting.test_js_file(err, path, script)
if err.final_context is not None:
print err.final_context.output()
return err

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

@ -14,9 +14,14 @@ def test_innerHTML():
x.innerHTML = "<div onclick=\\"foo\\"></div>";
""").failed()
# Test without declaration
assert _do_test_raw("""
x.innerHTML = "<div onclick=\\"foo\\"></div>";
""").failed()
assert _do_test_raw("""
var x = foo();
x.innerHTML = "x" + y + "z";
x.innerHTML = "x" + y;
""").failed()

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

@ -8,8 +8,11 @@ def test_basic_math():
var y = 2;
var z = x + y;
var dbz = 1 / 0; // Should return 0, not break the world.
var dbz1 = 1 % 0;
var dbz = 1;
var dbz1 = 1;
dbz = dbz / 0;
dbz1 = dbz1 % 0;
var dbz2 = 1;
var dbz3 = 1;
dbz2 /= 0;
@ -25,10 +28,10 @@ def test_basic_math():
assert _get_var(err, "y") == 2
assert _get_var(err, "z") == 3
assert _get_var(err, "dbz") is None
assert _get_var(err, "dbz1") is None
assert _get_var(err, "dbz2") is None
assert _get_var(err, "dbz3") is None
assert _get_var(err, "dbz") == 0 # Spidermonkey does this.
assert _get_var(err, "dbz1") == 0 # ...and this.
assert _get_var(err, "dbz2") == 0
assert _get_var(err, "dbz3") == 0
assert _get_var(err, "a") == 5
assert _get_var(err, "b") == 4
@ -50,11 +53,10 @@ def test_in_operator():
var b = "asdf" in dict;
""")
assert err.message_count == 0
print err.final_context.output()
print _get_var(err, "x"), "<<<"
assert _get_var(err, "x") == True
assert _get_var(err, "y") == True
print _get_var(err, "a"), "<<<"
assert _get_var(err, "a") == False
assert _get_var(err, "b") == False

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

@ -1,3 +1,4 @@
import math
import types
import spidermonkey
@ -432,14 +433,14 @@ def _expr_assignment(traverser, node):
"+=": lambda: lit_left + lit_right,
"-=": lambda: gleft - gright,
"*=": lambda: gleft * gright,
"/=": lambda: None if gright == 0 else (gleft / gright),
"%=": lambda: None if gright == 0 else (gleft % gright),
"<<=": lambda: gleft << gright,
">>=": lambda: gleft >> lit_right,
">>>=": lambda: math.fabs(gleft) >> gright,
"|=": lambda: gleft | gright,
"^=": lambda: gleft ^ gright,
"&=": lambda: gleft & gright}
"/=": lambda: 0 if gright == 0 else (gleft / gright),
"%=": lambda: 0 if gright == 0 else (gleft % gright),
"<<=": lambda: int(gleft) << int(gright),
">>=": lambda: int(gleft) >> int(gright),
">>>=": lambda: float(abs(int(gleft)) >> gright),
"|=": lambda: int(gleft) | int(gright),
"^=": lambda: int(gleft) ^ int(gright),
"&=": lambda: int(gleft) & int(gright)}
token = node["operator"]
traverser._debug("ASSIGNMENT>>OPERATION:%s" % token)
@ -448,9 +449,11 @@ def _expr_assignment(traverser, node):
traverser.debug_level -= 1
return left
traverser._debug("ASSIGNMENT::LEFT>>%s" % unicode(left.is_global))
traverser._debug("ASSIGNMENT::RIGHT>>%s" % unicode(operators[token]()))
left.set_value(operators[token](), traverser=traverser)
traverser._debug("ASSIGNMENT::L-value global? (%s)" %
("Y" if left.is_global else "N"))
new_value = operators[token]()
traverser._debug("ASSIGNMENT::New value >> %s" % new_value)
left.set_value(new_value, traverser=traverser)
traverser.debug_level -= 1
return left
@ -465,24 +468,29 @@ def _expr_binary(traverser, node):
traverser.debug_level += 1
traverser._debug("BIN_EXP>>LEFT")
# Traverse the left half of the binary expression.
traverser._debug("BIN_EXP>>l-value")
traverser.debug_level += 1
left = traverser._traverse_node(node["left"])
if not isinstance(left, JSWrapper):
left = JSWrapper(left, traverser=traverser)
traverser._debug(unicode(left.dirty))
traverser._debug("Is dirty? %r" % left.dirty)
traverser.debug_level -= 1
traverser._debug("BIN_EXP>>RIGHT")
# Traverse the right half of the binary expression.
traverser._debug("BIN_EXP>>r-value")
traverser.debug_level += 1
right = traverser._traverse_node(node["right"])
if not isinstance(right, JSWrapper):
right = JSWrapper(right, traverser=traverser)
traverser._debug(unicode(right.dirty))
traverser._debug("Is dirty? %r" % right.dirty)
# Dirty l or r values mean we can skip the expression. A dirty value
# indicates that a lazy operation took place that introduced some
# nondeterminacy.
if left.dirty:
return left
elif right.dirty:
@ -490,17 +498,22 @@ def _expr_binary(traverser, node):
traverser.debug_level -= 1
# Binary expressions are only executed on literals.
left_wrap = left
left = left.get_literal_value()
right_wrap = right
right = right.get_literal_value()
# Select the proper operator.
operator = node["operator"]
traverser._debug("BIN_OPERATOR>>%s" % operator)
type_operators = (">>", "<<", ">>>")
# Coerce the literals to numbers for numeric operations.
gleft = _get_as_num(left)
gright = _get_as_num(right)
operators = {
"==": lambda: left == right or gleft == gright,
"!=": lambda: left != right,
@ -510,13 +523,14 @@ def _expr_binary(traverser, node):
"<": lambda: left < right,
"<=": lambda: left <= right,
">=": lambda: left >= right,
"<<": lambda: gleft << gright,
">>": lambda: gleft >> gright,
">>>": lambda: math.fabs(gleft) >> gright,
"<<": lambda: int(gleft) << int(gright),
">>": lambda: int(gleft) >> int(gright),
">>>": lambda: float(abs(int(gleft)) >> int(gright)),
"+": lambda: left + right,
"-": lambda: gleft - gright,
"*": lambda: gleft * gright,
"/": lambda: 0 if gright == 0 else (gleft / gright),
"%": lambda: 0 if gright == 0 else (gleft % gright),
"in": lambda: right_wrap.contains(left),
# TODO : implement instanceof
}
@ -529,7 +543,8 @@ def _expr_binary(traverser, node):
left is None or right is None):
output = False
elif operator in operators:
# Concatenation can be silly, so always turn undefineds into empty
# strings and if there are strings, make everything strings.
if operator == "+":
if left is None:
left = ""
@ -593,7 +608,7 @@ def _get_as_num(value):
try:
if isinstance(value, types.StringTypes):
return float(value)
elif isinstance(value, int) or isinstance(value, float):
elif isinstance(value, (int, float, long)):
return value
else:
return int(value)

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

@ -19,13 +19,11 @@ def createElement(args, traverser, node):
if not args:
return
simple_args = [traverser._traverse_node(a) for a in args]
simple_args = map(traverser._traverse_node, args)
if unicode(simple_args[0].get_literal_value()).lower() == u"script":
_create_script_tag(traverser)
elif not (simple_args[0].is_literal() or
isinstance(simple_args[0].get_literal_value(),
types.StringTypes)):
elif not simple_args[0].is_literal():
_create_variable_element(traverser)
@ -35,13 +33,11 @@ def createElementNS(args, traverser, node):
if not args or len(args) < 2:
return
simple_args = [traverser._traverse_node(a) for a in args]
simple_args = map(traverser._traverse_node, args)
if "script" in unicode(simple_args[1].get_literal_value()).lower():
_create_script_tag(traverser)
elif not (simple_args[1].is_literal() or
isinstance(simple_args[1].get_literal_value(),
types.StringTypes)):
elif not simple_args[1].is_literal():
_create_variable_element(traverser)

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

@ -4,37 +4,40 @@ import types
import jstypes
def set_innerHTML(new_value, traverser):
"Tests that values being assigned to innerHTML are not dangerous"
"""Tests that values being assigned to innerHTML are not dangerous."""
if not isinstance(new_value, jstypes.JSWrapper):
new_value = jstypes.JSWrapper(new_value, traverser=traverser)
literal_value = new_value.get_literal_value()
if isinstance(literal_value, types.StringTypes):
# Static string assignments
# Test for on* attributes
event_assignment = re.compile("<.+ on[a-z]+=")
if event_assignment.search(literal_value.lower()):
traverser.err.warning(
err_id=("testcases_javascript_instancetypes", "set_innerHTML",
"event_assignment"),
warning="Event handler assignment via innerHTML",
description=["When assigning event handlers, innerHTML "
"should never be used. Rather, use a "
"proper technique, like addEventListener.",
"Event handler code: %s" %
literal_value.encode("ascii", "replace")],
filename=traverser.filename,
line=traverser.line,
column=traverser.position,
context=traverser.context)
else:
# Everything checks out, but we still want to pass it through the
# markup validator. Turn off strict mode so we don't get warnings
# about malformed HTML.
from validator.testcases.markup.markuptester import MarkupParser
parser = MarkupParser(traverser.err, strict=False, debug=True)
parser.process(traverser.filename, literal_value, "xul")
if new_value.is_literal():
literal_value = new_value.get_literal_value()
if isinstance(literal_value, types.StringTypes):
# Static string assignments
# Test for on* attributes
event_assignment = re.compile("<.+ on[a-z]+=")
if event_assignment.search(literal_value.lower()):
traverser.err.warning(
err_id=("testcases_javascript_instancetypes",
"set_innerHTML", "event_assignment"),
warning="Event handler assignment via innerHTML",
description=["When assigning event handlers, innerHTML "
"should never be used. Rather, use a "
"proper technique, like addEventListener.",
"Event handler code: %s" %
literal_value.encode("ascii", "replace")],
filename=traverser.filename,
line=traverser.line,
column=traverser.position,
context=traverser.context)
else:
# Everything checks out, but we still want to pass it through
# the markup validator. Turn off strict mode so we don't get
# warnings about malformed HTML.
from validator.testcases.markup.markuptester import \
MarkupParser
parser = MarkupParser(traverser.err, strict=False, debug=True)
parser.process(traverser.filename, literal_value, "xul")
else:
# Variable assignments
@ -85,7 +88,7 @@ def get_operation(mode, property):
return OBJECT_DEFINITIONS[property][mode]
elif mode == "set" and property.startswith("on"):
elif mode == "set" and unicode(property).startswith("on"):
# We can't match all of them manually, so grab all the "on*" properties
# and funnel them through the set_on_event function.

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

@ -13,8 +13,7 @@ class JSObject(object):
def __init__(self):
self.data = {
"prototype": JSPrototype(),
"constructor": lambda **keys: JSObject(keys["anon"])
u"prototype": JSPrototype()
}
def get(self, name):
@ -40,7 +39,9 @@ class JSObject(object):
return name in self.data
def output(self):
return unicode(self.data)
return json.dumps(dict(zip(self.data.keys(),
map(lambda v:v.output(),
self.data.values()))))
class JSContext(JSObject):
@ -53,12 +54,6 @@ class JSContext(JSObject):
def set(self, name, value):
JSObject.set(self, name, value, None)
def output(self):
output = {}
for (name, item) in self.data.items():
output[name] = unicode(item)
return json.dumps(output)
class JSWrapper(object):
"""Wraps a JS value and handles contextual functions for it."""
@ -114,6 +109,7 @@ class JSWrapper(object):
# Process any setter/modifier
if self.setter:
traverser._debug("Running setter on JSWrapper...");
value = self.setter(value, traverser) or value or None
if value == self.value:
@ -136,7 +132,7 @@ class JSWrapper(object):
context=traverser.context)
return self
if isinstance(value, (bool, str, int, float, unicode)):
if isinstance(value, (bool, str, int, float, long, unicode)):
value = JSLiteral(value)
# If the value being assigned is a wrapper as well, copy it in
elif isinstance(value, JSWrapper):
@ -146,6 +142,8 @@ class JSWrapper(object):
self.is_global = value.is_global
# const does not carry over on reassignment
return self
elif isinstance(value, types.LambdaType):
value = value()
self.value = value
return self
@ -172,10 +170,8 @@ class JSWrapper(object):
def get(self, traverser, name):
"""Retrieves a property from the variable"""
if self.value is None:
return JSWrapper(traverser=traverser, dirty=True)
value = self.value
dirty = value is None
if self.is_global:
if "value" not in value:
return JSWrapper(traverser=traverser)
@ -202,10 +198,17 @@ class JSWrapper(object):
if modifier:
modifier(traverser)
output = value.get(name) if issubclass(type(value), JSObject) else None
if value is not None:
output = (value.get(name) if
issubclass(type(value), JSObject) else
None)
else:
output = None
if not isinstance(output, JSWrapper):
output = JSWrapper(output, traverser=traverser, dirty=output is None)
output = JSWrapper(output,
traverser=traverser,
dirty=output is None or dirty)
# If we can predetermine the setter for the wrapper, we can save a ton
# of lookbehinds in the future. This greatly simplifies the
@ -258,16 +261,19 @@ class JSWrapper(object):
"""Returns the literal value of the wrapper"""
if self.is_global:
return None
return ""
if self.value is None:
return None
return ""
return self.value.get_literal_value()
output = self.value.get_literal_value()
return output if output is not None else ""
def output(self):
"""Returns a readable version of the object"""
if self.value is None or self.is_global:
return ""
if self.value is None:
return "(None)"
elif self.is_global:
return "(Global)"
return self.value.output()
@ -288,10 +294,10 @@ class JSLiteral(JSObject):
def __str__(self):
"Returns a human-readable version of the variable's contents"
return json.dumps(self.value)
return self.output()
def output(self):
return self.__str__()
return self.value
def get_literal_value(self):
"Returns the literal value of a this literal. Heh."
@ -351,7 +357,9 @@ class JSArray(JSObject):
# Interestingly enough, this allows for things like:
# x = [4]
# y = x * 3 // y = 12 since x equals "4"
return u",".join([unicode(w.get_literal_value()) for w in self.elements])
return u",".join([unicode(w.get_literal_value()) for
w in self.elements if
not (isinstance(w, JSWrapper) and w.value == self)])
def set(self, index, value, traverser=None):
"""Follow the rules of JS for creating an array"""
@ -379,5 +387,5 @@ class JSArray(JSObject):
self.elements.append(JSWrapper(value=value, traverser=traverser))
def output(self):
return self.get_literal_value()
return [x.output() for x in self.elements]

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

@ -166,6 +166,7 @@ class Traverser:
return wrapper
self._debug("TRAVERSE>>%s" % (node["type"]))
self.debug_level += 1
# Extract location information if it's available
if "loc" in node and node["loc"] is not None:
@ -192,11 +193,15 @@ class Traverser:
action_result = None
if action is not None:
action_result = action(self, node)
self._debug("ACTION>>%s (%s)" %
("halt>>%s" % unicode(action_result) if
action_result else
"continue",
node["type"]))
if DEBUG:
action_debug = "continue"
if action_result is not None:
action_debug = (action_result.output() if
isinstance(action_result, JSWrapper) else
action_result)
self._debug("ACTION>>%s (%s)" % (action_debug,
node["type"]))
# print node["type"], branches
if action_result is None:
@ -219,6 +224,8 @@ class Traverser:
if establish_context or block_level:
self._pop_context()
self.debug_level -= 1
# If there is an action and the action returned a value, it should be
# returned to the node traversal that initiated this node's traversal.
if returns: