diff --git a/validator/testcases/javascript/actions.py b/validator/testcases/javascript/actions.py index 655e38fb8..00f5b153e 100644 --- a/validator/testcases/javascript/actions.py +++ b/validator/testcases/javascript/actions.py @@ -71,9 +71,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 @@ -83,23 +82,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"]) or - 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): @@ -175,12 +168,9 @@ def wrap(traverser, node): def _define_function(traverser, node): - "Makes a function happy" - me = _function(traverser, node) traverser._peek_context(2).set(node["id"]["name"], me) - - return True + return me def _func_expr(traverser, node): @@ -364,7 +354,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)): @@ -373,26 +362,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, + description=result if isinstance(result, (types.StringTypes, + list, tuple)) else + "The global `%s` function was called using a set " + "of dangerous parameters. `%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"): # 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 @@ -406,7 +395,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): @@ -463,9 +452,9 @@ def _readonly_top(t, r, rn): err_id=("testcases_javascript_actions", "_readonly_top"), notice="window.top is a reserved variable", - description="The 'top' global variable is reserved and cannot be " + description="The `top` global variable is reserved and cannot be " "assigned any values starting with Gecko 6. Review your " - "code for any uses of the 'top' global, and refer to " + "code for any uses of the `top` global, and refer to " "%s for more information." % BUGZILLA_BUG % 654137, filename=t.filename, line=t.line, @@ -480,11 +469,11 @@ def _readonly_top(t, r, rn): 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): @@ -526,11 +515,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) @@ -642,8 +628,8 @@ 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): + 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) @@ -896,4 +882,3 @@ def _get_as_str(value): except ValueError: pass return unicode(value) - diff --git a/validator/testcases/javascript/call_definitions.py b/validator/testcases/javascript/call_definitions.py index 5c342ab49..48f518b0f 100644 --- a/validator/testcases/javascript/call_definitions.py +++ b/validator/testcases/javascript/call_definitions.py @@ -188,8 +188,8 @@ def definition(wrapper, arguments, traverser): traverser._debug("(Building XPCOM...)") - inst = traverser._build_global(method, - copy.deepcopy(argz.value["xpcom_map"]())) + inst = traverser._build_global( + method, argz.value["xpcom_map"]()) inst.value["overwritable"] = True if extend or mutate: @@ -212,7 +212,7 @@ def definition(wrapper, arguments, traverser): if extend and mutate: if callable(parent.value["value"]): parent.value["value"] = \ - copy.deepcopy(parent.value["value"](t=traverser)) + parent.value["value"](t=traverser) parent.value["value"].update(inst.value["value"]) return parent diff --git a/validator/testcases/javascript/instanceactions.py b/validator/testcases/javascript/instanceactions.py index 34bf3c852..b35268859 100644 --- a/validator/testcases/javascript/instanceactions.py +++ b/validator/testcases/javascript/instanceactions.py @@ -214,8 +214,10 @@ def PageMod(args, traverser, node, wrapper): return content_script = pm_properties.get(traverser, "contentScript") - content_script = content_script.get_literal_value() - if not isinstance(content_script, (str, unicode)): + if not content_script.is_literal(): + return + content_script = actions._get_as_str(content_script.get_literal_value()) + if not content_script.strip(): return import validator.testcases.scripting as sub_scripting diff --git a/validator/testcases/javascript/spidermonkey.py b/validator/testcases/javascript/spidermonkey.py index 49a23667d..7342c1279 100644 --- a/validator/testcases/javascript/spidermonkey.py +++ b/validator/testcases/javascript/spidermonkey.py @@ -18,12 +18,8 @@ def get_tree(code, err=None, filename=None, shell=None): if not code or not shell: return None - # Filter characters, convert to Unicode, etc. - code = prepare_code(code, err, filename) - try: - tree = _get_tree(code, shell) - return tree + return _get_tree(code, shell) except JSReflectException as exc: str_exc = str(exc).strip("'\"") if ("SyntaxError" in str_exc or @@ -74,14 +70,13 @@ def line_num(self, line_num): return self -def prepare_code(code, err, filename): - "Prepares 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) +def prepare_code(code): + """Prepare code for tree generation.""" 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): @@ -90,10 +85,9 @@ def _get_tree(code, shell): if not code: return None - code = unicodehelper.decode(code) + code = prepare_code(code) temp = tempfile.NamedTemporaryFile(mode="w+b", delete=False) - #temp.write(codecs.BOM_UTF8) temp.write(code.encode("utf_8")) temp.flush() diff --git a/validator/testcases/javascript/traverser.py b/validator/testcases/javascript/traverser.py index 63be36378..f31c07f5d 100644 --- a/validator/testcases/javascript/traverser.py +++ b/validator/testcases/javascript/traverser.py @@ -1,4 +1,4 @@ -import copy +from itertools import count import re import types @@ -91,31 +91,27 @@ def run(self, data): return # This performs the namespace pollution test. - final_globals = copy.deepcopy(self.contexts[0].data) - for global_name in self.contexts[0].data: - if global_name in POLLUTION_EXCEPTIONS: - del final_globals[global_name] - - global_context_size = len(final_globals) + global_context_size = count( + "cvan" for name in self.contexts[0].data if + name not in POLLUTION_EXCEPTIONS) self._debug("Final context size: %d" % global_context_size) if (global_context_size > 3 and not self.is_jsm and not "is_jetpack" in self.err.metadata and not self.err.get_resource("em:bootstrap") == "true"): self.err.warning( - err_id=("testcases_javascript_traverser", - "run", + err_id=("testcases_javascript_traverser", "run", "namepsace_pollution"), warning="JavaScript namespace pollution", - description=["Your add-on contains a large number of " - "global variables, which can conflict " - "with other add-ons. For more " - "information, see " - "http://blog.mozilla.com/addons/2009/01/16/" - "firefox-extensions-global-namespace-pollution/" - ", or use JavaScript modules.", - "List of entities: %s" % - ", ".join(self.contexts[0].data.keys())], + description=[ + "Your add-on contains a large number of global " + "variables, which can conflict with other add-ons. " + "For more information, see " + "http://blog.mozilla.com/addons/2009/01/16/" + "firefox-extensions-global-namespace-pollution/" + ", or use JavaScript modules.", + "List of entities: %s" % + ", ".join(self.contexts[0].data.keys())], filename=self.filename) def _traverse_node(self, node): @@ -267,64 +263,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" @@ -334,9 +312,9 @@ def _build_global(self, name, entity): self._debug("DANGEROUS") self.err.warning( ("js", "traverser", "dangerous_global"), - "Illegal or deprecated access to the '%s' global" % name, + "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 " + else "Access to the `%s` property is deprecated " "for security or other reasons." % name], filename=self.filename, line=self.line, diff --git a/validator/version.py b/validator/version.py index 167eb7fc8..e9b72dd32 100644 --- a/validator/version.py +++ b/validator/version.py @@ -74,9 +74,11 @@ def __str__(self): return self._version def __cmp__(self, other): - return reduce(lambda acc, (a, b): acc or cmp(a, b), - izip_longest(self.parts, other.parts), - 0) + for s_part, o_part in izip_longest(self.parts, other.parts): + cres = cmp(s_part, o_part) + if cres: + return cres + return 0 @property def is_release(self):