Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Update app validator with JS engine bump

  • Loading branch information...
commit 9ce5cc8e8e58dbcf0525790b28f8a152529927b4 1 parent 74e71b1
@mattbasta authored
View
92 appvalidator/testcases/javascript/actions.py
@@ -3,10 +3,10 @@
import re
import types
+from appvalidator.constants import BUGZILLA_BUG
import spidermonkey
import instanceactions
import instanceproperties
-from appvalidator.constants import BUGZILLA_BUG
from jstypes import *
@@ -70,9 +70,8 @@ def trace_member(traverser, node, instantiate=False):
test_identifier(traverser, identifier)
traverser._debug("MEMBER_EXP>>PROPERTY: %s" % identifier)
- output = base.get(traverser=traverser,
- instantiate=instantiate,
- name=identifier)
+ output = base.get(
+ traverser=traverser, instantiate=instantiate, name=identifier)
output.context = base.context
return output
@@ -82,23 +81,17 @@ def trace_member(traverser, node, instantiate=False):
# If we're supposed to instantiate the object and it doesn't already
# exist, instantitate the object.
- if (instantiate and not (traverser._is_global(node["name"]) and
- traverser._is_local_variable(node["name"]))):
+ if instantiate and not traverser._is_defined(node["name"]):
output = JSWrapper(JSObject(), traverser=traverser)
traverser.contexts[0].set(node["name"], output)
else:
output = traverser._seek_variable(node["name"])
- output = _expand_globals(traverser, output)
-
- return output
+ return _expand_globals(traverser, output)
else:
traverser._debug("MEMBER_EXP>>ROOT:EXPRESSION")
# It's an expression, so just try your damndest.
- traversed = traverser._traverse_node(node)
- if not isinstance(traversed, JSWrapper):
- return JSWrapper(traversed, traverser=traverser)
- return traversed
+ return traverser._traverse_node(node)
def test_identifier(traverser, name):
@@ -176,12 +169,11 @@ def wrap(traverser, node):
def _define_function(traverser, node):
me = _function(traverser, node)
traverser._peek_context(2).set(node["id"]["name"], me)
-
- return True
+ return me
-def _func_expr(traverser, node):
- return _function(traverser, node)
+# Just make it a refernce to the function that it aliases.
+_func_expr = _function
def _define_with(traverser, node):
@@ -296,10 +288,7 @@ def _define_obj(traverser, node):
for prop in node["properties"]:
var_name = ""
key = prop["key"]
- if key["type"] == "Literal":
- var_name = key["value"]
- else:
- var_name = key["name"]
+ var_name = key["value" if key["type"] == "Literal" else "name"]
var_value = traverser._traverse_node(prop["value"])
var.set(var_name, var_value, traverser)
@@ -353,7 +342,6 @@ def _call_expression(traverser, node):
column=traverser.position,
context=traverser.context)
-
if (member.is_global and
"dangerous" in member.value and
isinstance(member.value["dangerous"], types.LambdaType)):
@@ -362,26 +350,26 @@ def _call_expression(traverser, node):
t = traverser._traverse_node
result = dangerous(a=args, t=t, e=traverser.err)
if result and "name" in member.value:
- # Generate a string representation of the params
- params = u", ".join([_get_as_str(t(p).get_literal_value()) for
- p in args])
- traverser.err.warning(("testcases_javascript_actions",
- "_call_expression",
- "called_dangerous_global"),
- "'%(name)s' function called in potentially dangerous manner"
- % member.value,
- result if
- isinstance(result, (types.StringTypes, list, tuple)) else
- "The global %(name)s function was called using a set "
- "of dangerous parameters. %(name)s calls of this nature "
- "are deprecated." % member.value,
- filename=traverser.filename,
- line=traverser.line,
- column=traverser.position,
- context=traverser.context)
-
- elif node["callee"]["type"] == "MemberExpression" and \
- node["callee"]["property"]["type"] == "Identifier":
+ ## Generate a string representation of the params
+ #params = u", ".join([_get_as_str(t(p).get_literal_value()) for
+ # p in args])
+ traverser.err.warning(
+ err_id=("testcases_javascript_actions", "_call_expression",
+ "called_dangerous_global"),
+ warning="`%s` called in potentially dangerous manner" %
+ member.value["name"],
+ description=result if isinstance(result, (types.StringTypes,
+ list, tuple)) else
+ "The global `%s` function was called using a set "
+ "of dangerous parameters. Calls of this nature "
+ "are deprecated." % member.value["name"],
+ filename=traverser.filename,
+ line=traverser.line,
+ column=traverser.position,
+ context=traverser.context)
+
+ elif (node["callee"]["type"] == "MemberExpression" and
+ node["callee"]["property"]["type"] == "Identifier"):
# If we can identify the function being called on any member of any
# instance, we can use that to either generate an output value or test
@@ -395,7 +383,7 @@ def _call_expression(traverser, node):
if member.is_global and "return" in member.value:
return member.value["return"](wrapper=member, arguments=args,
traverser=traverser)
- return JSWrapper(traverser=traverser)
+ return JSWrapper(JSObject(), dirty=True, traverser=traverser)
def _call_settimeout(a, t, e):
@@ -447,11 +435,11 @@ def _call_create_pref(a, t, e):
def _expression(traverser, node):
- "Evaluates an expression and returns the result"
- result = traverser._traverse_node(node["expression"])
- if not isinstance(result, JSWrapper):
- return JSWrapper(result, traverser=traverser)
- return result
+ """
+ This is a helper method that allows node definitions to point at
+ `_traverse_node` without needing a reference to a traverser.
+ """
+ return traverser._traverse_node(node["expression"])
def _get_this(traverser, node):
@@ -493,11 +481,8 @@ def _ident(traverser, node):
# Ban bits like "newThread"
test_identifier(traverser, name)
- if (traverser._is_local_variable(name) or
- traverser._is_global(name)):
- # This function very nicely wraps with JSWrapper for us :)
- found = traverser._seek_variable(name)
- return found
+ if traverser._is_defined(name):
+ return traverser._seek_variable(name)
return JSWrapper(JSObject(), traverser=traverser, dirty=True)
@@ -848,4 +833,3 @@ def _get_as_str(value):
except (ValueError, TypeError):
pass
return unicode(value)
-
View
1  appvalidator/testcases/javascript/call_definitions.py
@@ -1,4 +1,3 @@
-import copy
import math
import re
import types
View
141 appvalidator/testcases/javascript/jstypes.py
@@ -1,8 +1,6 @@
import types
import instanceproperties
-recursion_buster = []
-
class JSObject(object):
"""
@@ -10,11 +8,15 @@ class JSObject(object):
context to enable static analysis of `with` statements.
"""
- def __init__(self, unwrapped=False):
- self.type_ = "object"
- self.data = {u"prototype": JSPrototype()}
+ def __init__(self, data=None, unwrapped=False):
+ self.type_ = "object" # For use when an object is pushed as a context.
+ self.data = {u"prototype": lambda: JSPrototype()}
+ if data:
+ self.data.update(data)
self.is_unwrapped = unwrapped
+ self.recursing = False
+
def get(self, name, instantiate=False, traverser=None):
"Returns the value associated with a property name"
name = unicode(name)
@@ -29,22 +31,25 @@ def get(self, name, instantiate=False, traverser=None):
if name in self.data:
output = self.data[name]
+ if isinstance(output, types.LambdaType):
+ output = output()
elif instantiate:
- output = JSWrapper(JSObject(), traverser=traverser)
+ output = JSWrapper(JSObject(), dirty=True, traverser=traverser)
self.set(name, output, traverser=traverser)
+
if traverser:
modifier = instanceproperties.get_operation("get", name)
if modifier:
modifier(traverser)
+
+ if output is None:
+ return JSWrapper(JSObject(), dirty=True, traverser=traverser)
return output
def get_literal_value(self):
- "Objects evaluate to empty strings"
- return "[object Object]"
+ return u"[object Object]"
def set(self, name, value, traverser=None):
- "Sets the value of a property"
-
if traverser:
modifier = instanceproperties.get_operation("set", name)
if modifier:
@@ -73,24 +78,26 @@ def has_var(self, name):
return name in self.data
def output(self):
- if self in recursion_buster:
- return "(recursion)"
+ if self.recursing:
+ return u"(recursion)"
# Prevent unruly recursion with a recursion buster.
- recursion_buster.append(self)
+ self.recursing = True
output_dict = {}
for key in self.data.keys():
- if (isinstance(self.data[key], JSWrapper) and
- self.data[key].value == self):
- output_dict[key] = "(self)"
+ if isinstance(self.data[key], types.LambdaType):
+ continue
+ elif (isinstance(self.data[key], JSWrapper) and
+ self.data[key].value == self):
+ output_dict[key] = u"(self)"
elif self.data[key] is None:
- output_dict[key] = "(None)"
+ output_dict[key] = u"(None)"
else:
output_dict[key] = self.data[key].output()
# Pop from the recursion buster.
- recursion_buster.pop()
+ self.recursing = False
output = []
if self.is_unwrapped:
@@ -103,12 +110,9 @@ class JSContext(JSObject):
"""A variable context"""
def __init__(self, context_type, unwrapped=False):
+ super(JSContext, self).__init__(unwrapped=unwrapped)
self.type_ = context_type
self.data = {}
- self.is_unwrapped = False # Contexts cannot be unwrapped.
-
- def set(self, name, value):
- JSObject.set(self, name, value, None)
class JSWrapper(object):
@@ -159,16 +163,16 @@ def set_value(self, value, traverser=None, overwrite_const=False):
traverser = self.traverser
if self.const and not overwrite_const:
- traverser.err.warning(("testcases_javascript_traverser",
- "JSWrapper_set_value",
- "const_overwrite"),
- "Overwritten constant value",
- "A variable declared as constant has been "
- "overwritten in some JS code.",
- traverser.filename,
- line=traverser.line,
- column=traverser.position,
- context=traverser.context)
+ traverser.err.warning(
+ err_id=("testcases_javascript_traverser", "JSWrapper_set_value",
+ "const_overwrite"),
+ warning="Overwritten constant value",
+ description="A variable declared as constant has been "
+ "overwritten in some JS code.",
+ filename=traverser.filename,
+ line=traverser.line,
+ column=traverser.position,
+ context=traverser.context)
# Process any setter/modifier
if self.setter:
@@ -176,7 +180,7 @@ def set_value(self, value, traverser=None, overwrite_const=False):
value = self.setter(value, traverser) or value or None
if value == self.value:
- return
+ return self
if isinstance(value, (bool, str, int, float, long, unicode)):
self.inspect_literal(value)
@@ -208,7 +212,7 @@ def has_property(self, property):
return isinstance(self.value, JSObject)
def get(self, traverser, name, instantiate=False):
- """Retrieves a property from the variable"""
+ """Retrieve a property from the variable."""
value = self.value
dirty = value is None
@@ -259,9 +263,8 @@ def _evaluate_lambdas(node):
output = None
if not isinstance(output, JSWrapper):
- output = JSWrapper(output,
- traverser=traverser,
- dirty=output is None or dirty)
+ output = JSWrapper(
+ output, traverser=traverser, dirty=output is None or dirty)
output.context = context
@@ -276,16 +279,15 @@ def _evaluate_lambdas(node):
def del_value(self, member):
"""The member `member` will be deleted from the value of the wrapper"""
if self.is_global:
- self.traverser.err.warning(("testcases_js_jstypes",
- "del_value",
- "global_member_deletion"),
- "Global member deletion",
- "Members of global object cannot be "
- "deleted.",
- filename=self.traverser.filename,
- line=self.traverser.line,
- column=self.traverser.position,
- context=self.traverser.context)
+ self.traverser.err.warning(
+ err_id=("testcases_js_jstypes", "del_value",
+ "global_member_deletion"),
+ warning="Global member deletion",
+ description="Members of global object cannot be deleted.",
+ filename=self.traverser.filename,
+ line=self.traverser.line,
+ column=self.traverser.position,
+ context=self.traverser.context)
return
elif isinstance(self.value, (JSObject, JSPrototype)):
if member not in self.value.data:
@@ -295,14 +297,17 @@ def del_value(self, member):
def contains(self, value):
"""Serves 'in' for BinaryOperators for lists and dictionaries"""
+ # Unwrap the rvalue.
if isinstance(value, JSWrapper):
value = value.get_literal_value()
+
if isinstance(self.value, JSArray):
- for val in self.value.elements:
- if val.get_literal_value() == value:
- return True
- elif isinstance(self.value, (JSObject, JSPrototype)):
- # Dictionaries lookat keys
+ from actions import _get_as_num
+ index = int(_get_as_num(value))
+ if len(self.value.elements) > index >= 0:
+ return True
+
+ if isinstance(self.value, (JSArray, JSObject, JSPrototype)):
return self.value.has_var(value)
# Nothing else supports "in"
@@ -366,7 +371,6 @@ def set_value(self, value):
self.value = value
def __str__(self):
- "Returns a human-readable version of the variable's contents"
if isinstance(self.value, bool):
return str(self.output()).lower()
return str(self.output())
@@ -386,17 +390,16 @@ class JSPrototype(JSObject):
"""
def __init__(self, unwrapped=False):
- self.is_unwrapped = unwrapped
+ super(JSPrototype, self).__init__(unwrapped=unwrapped)
self.data = {}
def get(self, name, instantiate=False, traverser=None):
- "Enables static analysis of `with` statements"
name = unicode(name)
output = super(JSPrototype, self).get(name, instantiate, traverser)
if output is not None:
return output
if name == "prototype":
- prototype = JSPrototype()
+ prototype = JSWrapper(JSPrototype(), traverser=traverser)
self.data[name] = prototype
return output
@@ -422,35 +425,32 @@ def get(self, index, instantiate=False, traverser=None):
def get_literal_value(self):
"""Arrays return a comma-delimited version of themselves."""
- if self in recursion_buster:
- return "(recursion)"
+ if self.recursing:
+ return u"(recursion)"
- recursion_buster.append(self)
+ self.recursing = True
# Interestingly enough, this allows for things like:
# x = [4]
# y = x * 3 // y = 12 since x equals "4"
- output = u",".join([unicode(w.get_literal_value() if w else "") for
- w in
- self.elements if
- not (isinstance(w, JSWrapper) and
- w.value == self)])
+ output = u",".join(
+ [unicode(w.get_literal_value() if w is not None else u"") for w in
+ self.elements if
+ not (isinstance(w, JSWrapper) and w.value == self)])
- recursion_buster.pop()
+ self.recursing = False
return output
def set(self, index, value, traverser=None):
- """Follow the rules of JS for creating an array"""
-
try:
index = int(index)
f_index = float(index)
# Ignore floating point indexes
if index != float(index):
- return
+ return super(JSArray, self).set(value, traverser)
except ValueError:
- return
+ return super(JSArray, self).set(index, value, traverser)
# JS ignores indexes less than 0
if index < 0:
@@ -468,5 +468,4 @@ def set(self, index, value, traverser=None):
self.elements.append(JSWrapper(value=value, traverser=traverser))
def output(self):
- return "[%s]" % self.get_literal_value()
-
+ return u"[%s]" % self.get_literal_value()
View
14 appvalidator/testcases/javascript/spidermonkey.py
@@ -19,9 +19,6 @@ def get_tree(code, err=None, filename=None, shell=None):
if not code:
return None
- # Filter characters, convert to Unicode, etc.
- code = prepare_code(code, err, filename)
-
try:
return _get_tree(code, shell or SPIDERMONKEY_INSTALLATION)
except JSReflectException as exc:
@@ -72,14 +69,13 @@ def line_num(self, line_num):
return self
-def prepare_code(code, err, filename):
+def prepare_code(code):
"""Prepare code for tree generation."""
- # Acceptable unicode characters still need to be stripped. Just remove the
- # slash: a character is necessary to prevent bad identifier errors
- code = JS_ESCAPE.sub("u", code)
code = unicodehelper.decode(code)
- return code
+ # Acceptable unicode characters still need to be stripped. Just remove the
+ # slash: a character is necessary to prevent bad identifier errors.
+ return JS_ESCAPE.sub("u", code)
def _get_tree(code, shell=SPIDERMONKEY_INSTALLATION):
@@ -88,7 +84,7 @@ def _get_tree(code, shell=SPIDERMONKEY_INSTALLATION):
if not code:
return None
- code = unicodehelper.decode(code)
+ code = prepare_code(code)
temp = tempfile.NamedTemporaryFile(mode="w+b", delete=False)
temp.write(code.encode("utf_8"))
View
66 appvalidator/testcases/javascript/traverser.py
@@ -1,4 +1,3 @@
-import copy
import re
import types
@@ -206,64 +205,46 @@ def _peek_context(self, depth=1):
return self.contexts[len(self.contexts) - depth]
- def _seek_variable(self, variable, depth=-1):
+ def _seek_variable(self, variable):
"Returns the value of a variable that has been declared in a context"
- self._debug("SEEK>>%s>>%d" % (variable, depth))
+ self._debug("SEEK>>%s" % variable)
# Look for the variable in the local contexts first
- local_variable = self._seek_local_variable(variable, depth)
+ local_variable = self._seek_local_variable(variable)
if local_variable is not None:
- if not isinstance(local_variable, JSWrapper):
- return JSWrapper(local_variable, traverser=self)
return local_variable
- self._debug("SEEK_FAIL>>TRYING GLOBAL")
-
# Seek in globals for the variable instead.
- return self._get_global(variable)
+ self._debug("SEEK_GLOBAL>>%s" % variable)
+ if self._is_global(variable):
+ self._debug("SEEK_GLOBAL>>FOUND>>%s" % variable)
+ return self._build_global(variable, GLOBAL_ENTITIES[variable])
+
+ self._debug("SEEK_GLOBAL>>FAILED")
+ # If we can't find a variable, we always return a dummy object.
+ return JSWrapper(JSObject(), traverser=self)
+
+ def _is_defined(self, variable):
+ return variable in GLOBAL_ENTITIES or self._is_local_variable(variable)
def _is_local_variable(self, variable):
"""Return whether a variable is defined in the current scope."""
return any(ctx.has_var(variable) for ctx in self.contexts)
- def _seek_local_variable(self, variable, depth=-1):
+ def _seek_local_variable(self, variable):
# Loop through each context in reverse order looking for the defined
# variable.
- context_count = len(self.contexts)
- for c in range(context_count):
- context = self.contexts[context_count - c - 1]
-
+ for context in reversed(self.contexts):
# If it has the variable, return it
if context.has_var(variable):
- self._debug("SEEK>>FOUND AT DEPTH %d" % c)
- var = context.get(variable, traverser=self)
- if not isinstance(var, JSWrapper):
- return JSWrapper(var, traverser=self)
- return var
-
- # Decrease the level that's being searched through. If we've
- # reached the bottom (relative to where the user defined it to be),
- # end the search.
- depth -= 1
- if depth == -1:
- return JSWrapper(JSObject(), traverser=self)
+ self._debug("SEEK>>FOUND")
+ return context.get(variable, traverser=self)
def _is_global(self, name):
"Returns whether a name is a global entity"
return not self._is_local_variable(name) and name in GLOBAL_ENTITIES
- def _get_global(self, name):
- "Gets a variable from the predefined variable context."
- self._debug("SEEK_GLOBAL>>%s" % name)
- if not self._is_global(name):
- self._debug("SEEK_GLOBAL>>FAILED")
- # If we can't find a variable, we always return a dummy object.
- return JSWrapper(JSObject(), traverser=self)
-
- self._debug("SEEK_GLOBAL>>FOUND>>%s" % name)
- return self._build_global(name, GLOBAL_ENTITIES[name])
-
def _build_global(self, name, entity):
"Builds an object based on an entity from the predefined entity list"
@@ -272,12 +253,11 @@ def _build_global(self, name, entity):
if dang and not isinstance(dang, types.LambdaType):
self._debug("DANGEROUS")
self.err.warning(
- err_id=("js", "_build_global", "dangerous_global"),
- warning="Illegal or deprecated access to the '%s' global" % name,
- description=[dang if isinstance(
- dang, (types.StringTypes, list, tuple)) else
- "Access to the '%s' property is deprecated "
- "for security or other reasons." % name],
+ ("js", "traverser", "dangerous_global"),
+ "Illegal or deprecated access to the `%s` global" % name,
+ [dang if isinstance(dang, (types.StringTypes, list, tuple))
+ else "Access to the `%s` property is deprecated "
+ "for security or other reasons." % name],
filename=self.filename,
line=self.line,
column=self.position,
View
2  appvalidator/validate.py
@@ -1,6 +1,6 @@
import json
-from . import constants
+import constants
from errorbundle import ErrorBundle
import loader
import submain
View
1  jsmain.py
@@ -52,6 +52,7 @@ def do_exit(wrapper, arguments, traverser):
while True:
line = raw_input("js> ")
+ trav._debug_level = 0
if line == "enable bootstrap\n":
err.save_resource("em:bootstrap", True)
continue
View
24 tests/js/test_arrays.py
@@ -0,0 +1,24 @@
+from js_helper import TestCase
+
+
+class TestJSArrays(TestCase):
+ """Test that arrays in the JS engine behave properly."""
+
+ def test_object_overloading(self):
+ self.run_script("""
+ var x = [];
+ x[1] = "asdf";
+ x["asdf"] = "zxcv";
+ var a = x[1],
+ b = x.asdf;
+ """)
+ self.assert_var_eq("a", "asdf")
+ self.assert_var_eq("b", "zxcv")
+
+ def test_stringification(self):
+ self.run_script("""
+ var x = [4];
+ var a = x * 3;
+ """)
+ self.assert_var_eq("a", 12)
+
View
2  tests/js/test_duplicates.py
@@ -19,4 +19,4 @@ def test(self, script, message_count):
yield test, self, 'eval("test");', 1
yield test, self, 'var x = eval();', 1
yield test, self, 'eval = 123;', 0
- yield test, self, 'eval.prototype = true;', 0
+ yield test, self, 'eval.prototype = true;', 1
View
5 tests/js/test_operators.py
@@ -48,18 +48,17 @@ def test_in_operator():
var dict = {"abc":123, "foo":"bar"};
// Must be true
- var x = "a" in list;
+ var x = 0 in list;
var y = "abc" in dict;
// Must be false
- var a = "bar" in list;
+ var a = 5 in list;
var b = "asdf" in dict;
""")
assert err.message_count == 0
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
View
36 tests/test_packagelayout.py
@@ -1,3 +1,7 @@
+from itertools import repeat
+
+from mock import MagicMock
+
import appvalidator.testcases.packagelayout as packagelayout
from appvalidator.errorbundle import ErrorBundle
from helper import _do_test, MockXPI
@@ -21,11 +25,8 @@ def test_java_jar_detection():
"""
classes = ("c%d.class" % i for i in xrange(1000))
- def strings(): # Look at how functional this is. How functional!
- while 1:
- yield ""
- mock_xpi = MockXPI(dict(zip(classes, strings())))
- err = ErrorBundle(None, True)
+ mock_xpi = MockXPI(dict(zip(classes, repeat(""))))
+ err = ErrorBundle()
packagelayout.test_blacklisted_files(err, mock_xpi)
assert not err.failed()
@@ -42,27 +43,16 @@ def test_blacklisted_magic_numbers():
assert "binary_components" not in err.metadata
-class MockDupeZipFile(object):
- """Mock a ZipFile class, simulating duplicate filename entries."""
-
- def namelist(self):
- return ["foo.bar", "foo.bar"]
-
-
-class MockDupeXPI(object):
- """Mock the XPIManager class, simulating duplicate filename entries."""
-
- def __init__(self):
- self.zf = MockDupeZipFile()
- self.subpackage = False
-
-
def test_duplicate_files():
"""Test that duplicate files in a package are caught."""
+ package = MagicMock()
+ package.subpackage = False
+ zf = MagicMock()
+ zf.namelist.return_value = ["foo.bar", "foo.bar"]
+ package.zf = zf
+
err = ErrorBundle()
err.save_resource("has_install_rdf", True)
- packagelayout.test_layout_all(err, MockDupeXPI())
+ packagelayout.test_layout_all(err, package)
assert err.failed()
-
-
Please sign in to comment.
Something went wrong with that request. Please try again.