Refactoring and window.document updates (for bug 680597)

This commit is contained in:
Matt Basta 2011-08-24 13:02:05 -05:00
Родитель 15b110b16b
Коммит b355e6a96d
6 изменённых файлов: 155 добавлений и 109 удалений

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

@ -7,6 +7,7 @@ from validator.errorbundler import ErrorBundle
from validator.outputhandlers.shellcolors import OutputHandler
import validator.testcases.scripting as scripting
import validator.testcases.javascript.traverser
from validator.testcases.javascript.predefinedentities import GLOBAL_ENTITIES
import validator.testcases.javascript.spidermonkey as spidermonkey
validator.testcases.javascript.traverser.DEBUG = True
@ -23,6 +24,31 @@ if __name__ == '__main__':
else:
trav = validator.testcases.javascript.traverser.Traverser(err, "stdin")
trav._push_context()
def do_inspect(wrapper, arguments, traverser):
print "~" * 50
for arg in arguments:
if arg["type"] == "Identifier":
print 'Identifier: "%s"' % arg["name"]
else:
print arg["type"]
a = traverser._traverse_node(arg)
print a.output()
if a.is_global:
print a.value
print "Context: %s" % a.context
print "<"
print "~" * 50
def do_exit(wrapper, arguments, traverser):
print "Goodbye!"
sys.exit()
GLOBAL_ENTITIES[u"inspect"] = {"return": do_inspect}
GLOBAL_ENTITIES[u"exit"] = {"return": do_exit}
while True:
line = sys.stdin.readline()

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

@ -28,11 +28,21 @@ def _expand_globals(traverser, node):
result = node.value["value"](t=traverser)
if isinstance(result, dict):
return traverser._build_global("--", result)
output = traverser._build_global("--", result)
elif isinstance(result, JSWrapper):
return result
output = result
else:
return JSWrapper(result, traverser)
output = JSWrapper(result, traverser)
# Set the node context.
if "context" in node.value:
traverser._debug("CONTEXT>>%s" % node.value["context"])
output.context = node.value["context"]
else:
traverser._debug("CONTEXT>>INHERITED")
output.context = node.context
return output
return node
@ -47,20 +57,25 @@ def trace_member(traverser, node, instantiate=False):
base = trace_member(traverser, node["object"], instantiate)
base = _expand_globals(traverser, base)
# If we've got an XPCOM wildcard, just return the base, minus the WC
if base.is_global and "xpcom_wildcard" in base.value:
traverser._debug("MEMBER_EXP>>XPCOM_WILDCARD")
base.value = base.value.copy()
del base.value["xpcom_wildcard"]
return base
# Handle the various global entity properties.
if base.is_global:
# If we've got an XPCOM wildcard, return a copy of the entity.
if "xpcom_wildcard" in base.value:
traverser._debug("MEMBER_EXP>>XPCOM_WILDCARD")
base.value = base.value.copy()
del base.value["xpcom_wildcard"]
return base
identifier = _get_member_exp_property(traverser, node)
test_identifier(traverser, identifier)
traverser._debug("MEMBER_EXP>>PROPERTY: %s" % identifier)
return base.get(traverser=traverser,
instantiate=instantiate,
name=identifier)
output = base.get(traverser=traverser,
instantiate=instantiate,
name=identifier)
output.context = base.context
return output
elif node["type"] == "Identifier":
traverser._debug("MEMBER_EXP>>ROOT:IDENTIFIER")
test_identifier(traverser, node["name"])
@ -318,9 +333,7 @@ def _define_literal(traverser, node):
def _call_expression(traverser, node):
args = node["arguments"]
for arg in args:
traverser._traverse_node(arg)
map(traverser._traverse_node, args)
member = traverser._traverse_node(node["callee"])
if (member.is_global and
@ -357,15 +370,12 @@ def _call_expression(traverser, node):
identifier_name = node["callee"]["property"]["name"]
if identifier_name in instanceactions.INSTANCE_DEFINITIONS:
result = instanceactions.INSTANCE_DEFINITIONS[identifier_name](
args, traverser, node)
args, traverser, node, wrapper=member)
return result
if member.is_global and "return" in member.value:
return member.value["return"](wrapper=member,
arguments=args,
return member.value["return"](wrapper=member, arguments=args,
traverser=traverser)
return True
@ -411,10 +421,9 @@ def _expression(traverser, node):
def _get_this(traverser, node):
"Returns the `this` object"
if not traverser.this_stack:
return JSWrapper(traverser=traverser)
from predefinedentities import GLOBAL_ENTITIES
return traverser._build_global(GLOBAL_ENTITIES[u"window"])
return traverser.this_stack[-1]

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

@ -9,13 +9,14 @@ traverser
node
the current node being evaluated
"""
import types
import actions
from jstypes import *
def createElement(args, traverser, node):
def createElement(args, traverser, node, wrapper):
"""Handles createElement calls"""
if not args:
@ -30,7 +31,7 @@ def createElement(args, traverser, node):
_create_variable_element(traverser)
def createElementNS(args, traverser, node):
def createElementNS(args, traverser, node, wrapper):
"""Handles createElementNS calls"""
if not args or len(args) < 2:
@ -45,7 +46,7 @@ def createElementNS(args, traverser, node):
_create_variable_element(traverser)
def QueryInterface(args, traverser, node):
def QueryInterface(args, traverser, node, wrapper):
"""Handles QueryInterface calls"""
if not args:
@ -57,7 +58,7 @@ def QueryInterface(args, traverser, node):
arguments=args,
traverser=traverser)
def getInterface(args, traverser, node):
def getInterface(args, traverser, node, wrapper):
"""Handles getInterface calls"""
# This really only needs to be handled for nsIInterfaceRequestor
@ -109,7 +110,7 @@ def _create_variable_element(traverser):
context=traverser.context)
def setAttribute(args, traverser, node):
def setAttribute(args, traverser, node, wrapper):
"""This ensures that setAttribute calls don't set on* attributes"""
if not args:
@ -132,7 +133,7 @@ def setAttribute(args, traverser, node):
context=traverser.context)
def nsIDOMFile_deprec(args, traverser, node):
def nsIDOMFile_deprec(args, traverser, node, wrapper):
"""A wrapper for call_definitions.nsIDOMFile_deprec."""
from call_definitions import nsIDOMFile_deprec as cd_nsIDOMFile_deprec
cd_nsIDOMFile_deprec(None, [], traverser)

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

@ -82,7 +82,7 @@ class JSWrapper(object):
def __init__(self, value=None, const=False, dirty=False, lazy=False,
is_global=False, traverser=None, callable=False,
setter=None):
setter=None, context="chrome"):
if is_global:
assert not value
@ -99,6 +99,7 @@ class JSWrapper(object):
self.value = None # Instantiate the placeholder value
self.is_global = False # Not yet......
self.dirty = False # Also not yet...
self.context = context
# Used for predetermining set operations
self.setter = setter
@ -145,12 +146,10 @@ class JSWrapper(object):
# We want to obey the permissions of global objects
if (self.is_global and
(not traverser or
not traverser.is_jsm) and
(not traverser or not traverser.is_jsm) and
(isinstance(self.value, dict) and
("overwritable" not in self.value or
self.value["overwritable"] == False))):
# TODO : Write in support for "readonly":False
traverser.err.warning(("testcases_javascript_jstypes",
"JSWrapper_set_value",
"global_overwrite"),
@ -171,6 +170,7 @@ class JSWrapper(object):
self.lazy = value.lazy
self.dirty = value.dirty
self.is_global = value.is_global
self.context = value.context
# const does not carry over on reassignment
return self
elif isinstance(value, types.LambdaType):
@ -178,45 +178,36 @@ class JSWrapper(object):
if not isinstance(value, dict):
self.is_global = False
elif "context" in value:
self.context = value["context"]
self.value = value
return self
def set_value_from_expression(self, traverser, node):
"""Sets the value of the variable from a node object"""
self.set_value(traverser._traverse_node(node),
traverser=traverser)
def has_property(self, property):
"""Returns a boolean value representing the presence of a property"""
if self.value is None:
return False
if isinstance(self.value, JSLiteral):
return False
elif isinstance(self.value, (JSObject, JSPrototype)):
# JSPrototype and JSObject always have a value
return True
return isinstance(self.value, JSObject)
def get(self, traverser, name, instantiate=False):
"""Retrieves a property from the variable"""
value = self.value
dirty = value is None
context = self.context
if self.is_global:
if "value" not in value:
output = JSWrapper(traverser=traverser)
output = JSWrapper(JSObject(), traverser=traverser)
output.value = {}
def apply_value(name):
if name in self.value:
output.value[name] = self.value[name]
apply_value("dangerous")
apply_value("readonly")
map(apply_value, ("dangerous", "readonly", "context"))
output.is_global = True
output.context = self.context
return output
def _evaluate_lambdas(node):
@ -231,8 +222,11 @@ class JSWrapper(object):
if isinstance(value_val, dict):
if name in value_val:
value_val = _evaluate_lambdas(value_val[name])
return traverser._build_global(name=name,
entity=value_val)
output = traverser._build_global(name=name,
entity=value_val)
if "context" not in value_val:
output.context = self.context
return output
else:
value = value_val
@ -252,6 +246,8 @@ class JSWrapper(object):
traverser=traverser,
dirty=output is None or dirty)
output.context = context
# If we can predetermine the setter for the wrapper, we can save a ton
# of lookbehinds in the future. This greatly simplifies the
# MemberExpression support.

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

@ -16,15 +16,10 @@ BANNED_IDENTIFIERS = {
"instead wherever possible",
}
# For "dangerous" elements, specifying True will throw an error on all
# detected instances of the particular object. Specifying a lambda function
# will allow the object to be referenced. If the object is called via a
# CallExpression, "a" will contain the raw arguments values and "t" will
# contain a reference to traverser._traverse_node(). "t" will always return a
# JSWrapper object. The optional third argument "e" will be an ErrorBundle
# object. The return value of the lambda function will be used as the value for
# the "dangerous" property. Lastly, specifying a string functions identically to
# "True", except the string will be outputted when the error is thrown.
# See https://github.com/mattbasta/amo-validator/wiki/JS-Predefined-Entities
# for details on entity properties.
CONTENT_DOCUMENT = None
INTERFACES = {
u"nsICategoryManager":
@ -179,31 +174,32 @@ GLOBAL_ENTITIES = {
t)}}},
u"document":
{"value": {u"title":
{"overwritable": True,
"readonly": False},
u"createElement":
{"dangerous":
lambda a, t, e:
not a or
_get_as_str(t(a[0]).get_literal_value())
.lower() == "script"},
u"createElementNS":
{"dangerous":
lambda a, t, e:
not a or
_get_as_str(t(a[0]).get_literal_value())
.lower() == "script"},
u"getSelection":
{"return": call_definitions.document_getSelection},
u"loadOverlay":
{"dangerous":
lambda a, t, e:
not a or
not _get_as_str(t(a[0]).get_literal_value())
.lower()
.startswith(("chrome:",
"resource:"))}}},
{"value":
{u"title":
{"overwriteable": True,
"readonly": False},
u"defaultView":
{"value": lambda t: {"value": GLOBAL_ENTITIES}},
u"createElement":
{"dangerous":
lambda a, t, e:
not a or
unicode(t(a[0]).get_literal_value()).lower() ==
"script"},
u"createElementNS":
{"dangerous":
lambda a, t, e:
not a or
unicode(t(a[0]).get_literal_value()).lower() ==
"script"},
u"getSelection":
{"return": call_definitions.document_getSelection},
u"loadOverlay":
{"dangerous":
lambda a, t, e:
not a or
not unicode(t(a[0]).get_literal_value()).lower()
.startswith(("chrome:", "resource:"))}}},
# The nefariuos timeout brothers!
u"setTimeout": {"dangerous": actions._call_settimeout},
@ -414,16 +410,16 @@ GLOBAL_ENTITIES = {
u"XMLHttpRequest":
{"value":
{u"open": {"dangerous":
# Ban syncrhonous XHR by making sure the third arg
# is not absent and falsey.
lambda a, t, e:
a and len(a) >= 3 and
not t(a[2]).get_literal_value() and
"Synchronous HTTP requests can cause "
"serious UI performance problems, "
"especially to users with slow network "
"connections."}}},
{u"open":
{"dangerous":
# Ban syncrhonous XHR by making sure the third arg
# is absent and false.
lambda a, t, e:
a and len(a) >= 3 and
not t(a[2]).get_literal_value() and
"Synchronous HTTP requests can cause serious UI "
"performance problems, especially to users with "
"slow network connections."}}},
# Global properties are inherently read-only, though this formalizes it.
u"Infinity":
@ -439,5 +435,29 @@ GLOBAL_ENTITIES = {
u"width": {"readonly": False},
u"height": {"readonly": False},
u"top": {"readonly": actions._readonly_top},
u"content":
{"context": "content",
"value":
{u"document":
{"value": lambda t: GLOBAL_ENTITIES[u"document"]}}},
u"contentWindow":
{"context": "content",
"value":
lambda t: {"value": GLOBAL_ENTITIES}},
u"_content": {"value": lambda t: GLOBAL_ENTITIES[u"content"]},
u"gBrowser":
{"value":
{u"contentDocument":
{"context": "content",
"value": lambda t: CONTENT_DOCUMENT},
u"contentWindow":
{"value":
lambda t: {"value": GLOBAL_ENTITIES}}}},
u"opener":
{"value":
lambda t: {"value": GLOBAL_ENTITIES}}
}
CONTENT_DOCUMENT = GLOBAL_ENTITIES[u"content"]["value"][u"document"]

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

@ -285,28 +285,19 @@ class Traverser:
if depth == -1:
return JSWrapper(traverser=self)
def _is_global(self, name, globs=None):
def _is_global(self, name):
"Returns whether a name is a global entity"
return name in GLOBAL_ENTITIES
if globs is None:
globs = GLOBAL_ENTITIES
return name in globs
def _get_global(self, name, globs=None):
def _get_global(self, name):
"Gets a variable from the predefined variable context."
# Allow overriding of the global entities
if globs is None:
globs = GLOBAL_ENTITIES
self._debug("SEEK_GLOBAL>>%s" % name)
if not self._is_global(name, globs):
if not self._is_global(name):
self._debug("SEEK_GLOBAL>>FAILED")
return JSWrapper(traverser=self)
self._debug("SEEK_GLOBAL>>FOUND>>%s" % name)
return self._build_global(name, globs[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,6 +325,9 @@ class Traverser:
result.value = entity
result = actions._expand_globals(self, result)
if "context" in entity:
result.context = entity["context"]
self._debug("BUILT_GLOBAL")
return result