Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'loadfix'

  • Loading branch information...
commit 484dc929e71004ea8492a16d028d098b5ff9bc2d 2 parents cffb57e + c402e4a
@mattbasta authored
View
2  jsmain.py
@@ -52,7 +52,7 @@ def do_exit(wrapper, arguments, traverser):
while True:
line = raw_input("js> ")
- trav._debug_level = 0
+ trav.debug_level = 0
if line == "enable bootstrap\n":
err.save_resource("em:bootstrap", True)
continue
View
37 tests/test_js_basicstrings.py
@@ -1,11 +1,15 @@
+from nose.tools import eq_
+
+from validator.constants import MAX_STR_SIZE
+
from js_helper import _do_test, _do_test_raw, _get_var
def test_basic_concatenation():
"Tests that contexts work and that basic concat ops are executed properly"
-
+
err = _do_test("tests/resources/javascript/basicstrings.js")
assert err.message_count == 0
-
+
assert _get_var(err, "x") == "foo"
assert _get_var(err, "y") == "bar"
assert _get_var(err, "z") == "foobar"
@@ -16,9 +20,10 @@ def test_basic_concatenation():
assert _get_var(err, "e") == 30
assert _get_var(err, "f") == 5
+
def test_augconcat():
"Tests augmented concatenation operators"
-
+
err = _do_test_raw("""
var x = "foo";
x += "bar";
@@ -36,12 +41,36 @@ def test_augconcat():
print xyz_val
assert xyz_val == "foobar"
+
def test_typecasting():
"Tests that strings are treated as numbers when necessary"
-
+
err = _do_test("tests/resources/javascript/strings_typecasting.js")
assert err.message_count == 0
assert _get_var(err, "x") == "44"
assert _get_var(err, "y") == 16
+
+def test_max_str_size_aug_assig():
+ """Test that the max string size is enforced for augmented assignment."""
+
+ # Create a string and max out its size.
+ err = _do_test_raw("""
+ var x = "%s";
+ x += x;
+ x += x;
+ """ % ("x" * (MAX_STR_SIZE / 2)))
+ eq_(len(_get_var(err, "x")), MAX_STR_SIZE)
+
+
+def test_max_str_size_binop():
+ """Test that the max string size is enforced for binary operators."""
+
+ # Create a string and max out its size.
+ err = _do_test_raw("""
+ var x = "%s";
+ x = x + x;
+ x = x + x;
+ """ % ("x" * (MAX_STR_SIZE / 2)))
+ eq_(len(_get_var(err, "x")), MAX_STR_SIZE)
View
14 tests/test_js_operators.py
@@ -237,3 +237,17 @@ def test_concat_plus_infinity():
"c": "fooInfinity",
"d": "foo-Infinity"})
+
+def test_simple_operators_when_dirty():
+ """
+ Test that when we're dealing with dirty objects, binary operations don't
+ cave in the roof.
+
+ Note that this test (if it fails) may cause some ugly crashes.
+ """
+
+ _do_test_raw("""
+ var x = foo(); // x is now a dirty object.
+ y = foo(); // y is now a dirty object as well.
+ """ +
+ """y += y + x;""" * 100) # This bit makes the validator's head explode.
View
7 validator/constants.py
@@ -48,6 +48,13 @@
JETPACK_URI_URL = "https://wiki.mozilla.org/Labs/Jetpack/Release_Notes/" \
"1.4#Known_Issues"
+# The maximum size of any string in JS analysis.
+MAX_STR_SIZE = 1024 * 24 # 24KB
+
+# The maximum number of JS files that can be exhaustively validated in one
+# package.
+MAX_JS_THRESHOLD = 900
+
# Graciously provided by @kumar in bug 614574
if (not SPIDERMONKEY_INSTALLATION or
not os.path.exists(SPIDERMONKEY_INSTALLATION)):
View
3  validator/testcases/content.py
@@ -5,6 +5,7 @@
from regex import run_regex_tests
from validator.contextgenerator import ContextGenerator
+from validator.constants import MAX_JS_THRESHOLD
from validator import decorator
from validator import submain as testendpoint_validator
from validator import unicodehelper
@@ -203,7 +204,7 @@ def test_packed_scripts(err, xpi_package):
total_scripts = sum(len(bundle["scripts"]) for bundle in scripts)
exhaustive = True
- if total_scripts > 1000:
+ if total_scripts > MAX_JS_THRESHOLD:
err.warning(
err_id=("testcases_content", "packed_js", "too_much_js"),
warning="TOO MUCH JS FOR EXHAUSTIVE VALIDATION",
View
138 validator/testcases/javascript/actions.py
@@ -6,11 +6,35 @@
import spidermonkey
import instanceactions
import instanceproperties
-from validator.constants import BUGZILLA_BUG, FENNEC_GUID, FIREFOX_GUID
+from validator.constants import (BUGZILLA_BUG, FENNEC_GUID, FIREFOX_GUID,
+ MAX_STR_SIZE)
from validator.decorator import version_range
from jstypes import *
+NUMERIC_TYPES = (int, long, float, complex)
+
+# None of these operations (or their augmented assignment counterparts) should
+# be performed on non-numeric data. Any time we get non-numeric data for these
+# guys, we just return window.NaN.
+NUMERIC_OPERATORS = ("-", "*", "/", "%", "<<", ">>", ">>>", "|", "^", "&")
+NUMERIC_OPERATORS += tuple("%s=" % op for op in NUMERIC_OPERATORS)
+
+
+def get_NaN(traverser):
+ # If we've cached the traverser's NaN instance, just use that.
+ ncache = getattr(traverser, "NAN_CACHE", None)
+ if ncache is not None:
+ return ncache
+
+ # Otherwise, we need to import GLOBAL_ENTITIES and build a raw copy.
+ from predefinedentities import GLOBAL_ENTITIES
+ ncache = traverser._build_global("NaN", GLOBAL_ENTITIES[u"NaN"])
+ # Cache it so we don't need to do this again.
+ traverser.NAN_CACHE = ncache
+ return ncache
+
+
def _get_member_exp_property(traverser, node):
"""Return the string value of a member expression's property."""
@@ -100,15 +124,14 @@ def test_identifier(traverser, name):
import predefinedentities
if name in predefinedentities.BANNED_IDENTIFIERS:
- traverser.err.warning(("testcases_scripting",
- "create_identifier",
- "banned_identifier"),
- "Banned or deprecated JavaScript Identifier",
- predefinedentities.BANNED_IDENTIFIERS[name],
- filename=traverser.filename,
- line=traverser.line,
- column=traverser.position,
- context=traverser.context)
+ traverser.err.warning(
+ err_id=("js", "actions", "banned_identifier"),
+ warning="Banned or deprecated JavaScript Identifier",
+ description=predefinedentities.BANNED_IDENTIFIERS[name],
+ filename=traverser.filename,
+ line=traverser.line,
+ column=traverser.position,
+ context=traverser.context)
def _function(traverser, node):
@@ -618,9 +641,14 @@ def _expr_assignment(traverser, node):
traverser._debug("ASSIGNMENT>>PARSING LEFT")
left = traverser._traverse_node(node["left"])
traverser._debug("ASSIGNMENT>>DONE PARSING LEFT")
+ traverser.debug_level -= 1
if isinstance(left, JSWrapper):
+ if left.dirty:
+ return left
+
lit_left = left.get_literal_value()
+ token = node["operator"]
# Don't perform an operation on None. Python freaks out
if lit_left is None:
@@ -628,13 +656,9 @@ def _expr_assignment(traverser, node):
if lit_right is None:
lit_right = 0
- if (isinstance(lit_left, types.StringTypes) or
- isinstance(lit_right, types.StringTypes)):
- lit_left = _get_as_str(lit_left)
- lit_right = _get_as_str(lit_right)
+ # Give them default values so we have them in scope.
+ gleft, gright = 0, 0
- gleft = _get_as_num(left)
- gright = _get_as_num(right)
# All of the assignment operators
operators = {"=": lambda: right,
"+=": lambda: lit_left + lit_right,
@@ -649,35 +673,53 @@ def _expr_assignment(traverser, node):
"^=": lambda: int(gleft) ^ int(gright),
"&=": lambda: int(gleft) & int(gright)}
- token = node["operator"]
+ # If we're modifying a non-numeric type with a numeric operator, return
+ # NaN.
+ if (not isinstance(lit_left, NUMERIC_TYPES) and
+ token in NUMERIC_OPERATORS):
+ left.set_value(get_NaN(traverser), traverser=traverser)
+ return left
+
+ # If either side of the assignment operator is a string, both sides
+ # need to be casted to strings first.
+ if (isinstance(lit_left, types.StringTypes) or
+ isinstance(lit_right, types.StringTypes)):
+ lit_left = _get_as_str(lit_left)
+ lit_right = _get_as_str(lit_right)
+
+ gleft, gright = _get_as_num(left), _get_as_num(right)
+
traverser._debug("ASSIGNMENT>>OPERATION:%s" % token)
if token not in operators:
- traverser._debug("ASSIGNMENT>>OPERATOR NOT FOUND")
- traverser.debug_level -= 1
+ # We don't support that operator. (yet?)
+ traverser._debug("ASSIGNMENT>>OPERATOR NOT FOUND", 1)
return left
elif token in ("<<=", ">>=", ">>>=") and gright < 0:
+ # The user is doing weird bitshifting that will return 0 in JS but
+ # not in Python.
left.set_value(0, traverser=traverser)
return left
elif (token in ("<<=", ">>=", ">>>=", "|=", "^=", "&=") and
(abs(gleft) == float('inf') or abs(gright) == float('inf'))):
# Don't bother handling infinity for integer-converted operations.
- from predefinedentities import GLOBAL_ENTITIES
- left.set_value(traverser._build_global("NaN",
- GLOBAL_ENTITIES[u"NaN"]),
- traverser=traverser)
+ left.set_value(get_NaN(traverser), traverser=traverser)
return left
traverser._debug("ASSIGNMENT::L-value global? (%s)" %
- ("Y" if left.is_global else "N"))
+ ("Y" if left.is_global else "N"), 1)
new_value = operators[token]()
- traverser._debug("ASSIGNMENT::New value >> %s" % new_value)
+
+ # Cap the length of analyzed strings.
+ if (isinstance(new_value, types.StringTypes)
+ and len(new_value) > MAX_STR_SIZE):
+ new_value = new_value[:MAX_STR_SIZE]
+
+ traverser._debug("ASSIGNMENT::New value >> %s" % new_value, 1)
left.set_value(new_value, traverser=traverser)
- traverser.debug_level -= 1
return left
# Though it would otherwise be a syntax error, we say that 4=5 should
# evaluate out to 5.
- traverser.debug_level -= 1
return right
@@ -704,21 +746,21 @@ def _expr_binary(traverser, node):
else:
left = traverser._traverse_node(node["left"])
- traverser.debug_level -= 1
-
# Traverse the right half of the binary expression.
- traverser._debug("BIN_EXP>>r-value")
- traverser.debug_level += 1
+ traverser._debug("BIN_EXP>>r-value", -1)
if (operator == "instanceof" and
- node["right"]["type"] == "Identifier" and
- node["right"]["name"] == "Function"):
+ node["right"]["type"] == "Identifier" and
+ node["right"]["name"] == "Function"):
# We make an exception for instanceof's r-value if it's a dangerous
# global, specifically Function.
+ traverser.debug_level -= 1
return JSWrapper(True, traverser=traverser)
else:
right = traverser._traverse_node(node["right"])
- traverser._debug("Is dirty? %r" % right.dirty)
+ traverser._debug("Is dirty? %r" % right.dirty, 1)
+
+ traverser.debug_level -= 1
# Dirty l or r values mean we can skip the expression. A dirty value
# indicates that a lazy operation took place that introduced some
@@ -728,8 +770,6 @@ def _expr_binary(traverser, node):
elif right.dirty:
return right
- traverser.debug_level -= 1
-
# Binary expressions are only executed on literals.
left_wrap = left
left = left.get_literal_value()
@@ -744,7 +784,7 @@ def _expr_binary(traverser, node):
"==": lambda: left == right or gleft == gright,
"!=": lambda: left != right,
"===": lambda: left == right, # Be flexible.
- "!==": lambda: not (type(left) == type(right) or left != right),
+ "!==": lambda: type(left) != type(right) or left != right,
">": lambda: left > right,
"<": lambda: left < right,
"<=": lambda: left <= right,
@@ -761,13 +801,9 @@ def _expr_binary(traverser, node):
# TODO : implement instanceof
}
- traverser.debug_level -= 1
-
- operator = node["operator"]
output = None
if (operator in (">>", "<<", ">>>") and
- ((left is None or right is None) or
- gright < 0)):
+ (left is None or right is None or gright < 0)):
output = False
elif operator in operators:
# Concatenation can be silly, so always turn undefineds into empty
@@ -777,22 +813,24 @@ def _expr_binary(traverser, node):
left = ""
if right is None:
right = ""
- if isinstance(left, types.StringTypes) or \
- isinstance(right, types.StringTypes):
+ if (isinstance(left, types.StringTypes) or
+ isinstance(right, types.StringTypes)):
left = _get_as_str(left)
right = _get_as_str(right)
# Don't even bother handling infinity if it's a numeric computation.
if (operator in ("<<", ">>", ">>>") and
- (abs(gleft) == float('inf') or abs(gright) == float('inf'))):
- from predefinedentities import GLOBAL_ENTITIES
- return traverser._build_global("NaN", GLOBAL_ENTITIES[u"NaN"])
+ (abs(gleft) == float('inf') or abs(gright) == float('inf'))):
+ return get_NaN(traverser)
output = operators[operator]()
- if not isinstance(output, JSWrapper):
- return JSWrapper(output, traverser=traverser)
- return output
+ # Cap the length of analyzed strings.
+ if (isinstance(output, types.StringTypes)
+ and len(output) > MAX_STR_SIZE):
+ output = output[:MAX_STR_SIZE]
+
+ return JSWrapper(output, traverser=traverser)
def _expr_unary(traverser, node):
View
9 validator/testcases/javascript/traverser.py
@@ -46,17 +46,18 @@ def __init__(self, err, filename, start_line=0, context=None,
# If we're not debugging, don't waste more cycles than we need to.
if not DEBUG:
- self._debug = lambda x: None
+ self._debug = lambda *args, **kwargs: None
- def _debug(self, data):
- "Writes a message to the console if debugging is enabled."
+ def _debug(self, data, indent=0):
+ """Write a message to the console if debugging is enabled."""
if DEBUG:
output = data
if isinstance(data, JSObject) or isinstance(data, JSContext):
output = data.output()
output = unicode(output)
- print ". " * self.debug_level + output.encode("ascii", "replace")
+ print (". " * (self.debug_level + indent) +
+ output.encode("ascii", "replace"))
def run(self, data):
if DEBUG:
Please sign in to comment.
Something went wrong with that request. Please try again.