Skip to content
This repository has been archived by the owner on Feb 1, 2019. It is now read-only.

Commit

Permalink
Merge pull request #46 from mattbasta/jsm
Browse files Browse the repository at this point in the history
JSM/Namespace Pollution/reference overwriting
  • Loading branch information
mattbasta committed Jun 1, 2011
2 parents 04e51ee + 0c1beb5 commit b77ffa6
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 54 deletions.
11 changes: 7 additions & 4 deletions extras/unbundle.py
Expand Up @@ -8,16 +8,19 @@
source = sys.argv[1]
target = sys.argv[2]

if not target.endswith("/"):
target = "%s/" % target

def _unbundle(path, target):
zf = ZipFile(path, 'r')
contents = zf.namelist()
for item in contents:
sp = item.split("/")
if not sp[-1]:
continue

print item, ">", target + item

cpath = target + "/".join(sp[:-1])
if not os.path.exists(cpath):
os.makedirs(cpath)
Expand All @@ -26,7 +29,7 @@ def _unbundle(path, target):
path_item = item.split("/")
path_item[-1] = "_" + path_item[-1]
path = target + "/".join(path_item)

buff = StringIO(zf.read(item))
_unbundle(buff, path + "/")
else:
Expand All @@ -38,4 +41,4 @@ def _unbundle(path, target):
if not os.path.exists(target):
os.mkdir(target)

_unbundle(source, target)
_unbundle(source, target)
6 changes: 5 additions & 1 deletion tests/js_helper.py
Expand Up @@ -7,13 +7,17 @@ def _do_test(path):
script = open(path).read()
return _do_test_raw(script, path)

def _do_test_raw(script, path="foo", bootstrap=False):
def _do_test_raw(script, path="foo", bootstrap=False, ignore_pollution=True):
"Performs a test on a JS file"

err = validator.testcases.scripting.traverser.MockBundler()
if bootstrap:
err.save_resource("em:bootstrap", True)

if ignore_pollution:
validator.testcases.scripting.traverser.IGNORE_POLLUTION = True
validator.testcases.scripting.test_js_file(err, path, script)
validator.testcases.scripting.traverser.IGNORE_POLLUTION = False
if err.final_context is not None:
print err.final_context.output()

Expand Down
5 changes: 0 additions & 5 deletions tests/resources/javascript/dups.js

This file was deleted.

24 changes: 18 additions & 6 deletions tests/test_js_duplicates.py
@@ -1,10 +1,22 @@
from js_helper import _do_test
from js_helper import _do_test_raw


def test_no_dups():
"Tests that errors are not duplicated."
"""Test that errors are not duplicated."""

assert _do_test_raw("""
eval("test");
""").message_count == 1

assert _do_test_raw("""
var x = eval();
""").message_count == 1

assert _do_test_raw("""
eval = 123;
""").message_count == 1

err = _do_test("tests/resources/javascript/dups.js")
assert err.message_count == 6
# 6 because prototypes are readonly in addition to accessing a member
# of a dangerous object.
assert _do_test_raw("""
eval.prototype = true;
""").message_count == 2

26 changes: 26 additions & 0 deletions tests/test_js_jsm.py
@@ -0,0 +1,26 @@
from js_helper import _do_test_raw


def test_jsm_global_overwrites():
"""
JavaScript modules do not cause global scope conflicts, so we should not
make errors if globals are overwritten.
"""

assert _do_test_raw("""
String.prototype.foo = "bar";
""").failed()

assert not _do_test_raw("""
String.prototype.foo = "bar";
""", path="test.jsm").failed()


def test_jsm_EXPORTED_SYMBOLS():
"""Test that EXPORTED_SYMBOLS is a trigger for JSM."""

assert not _do_test_raw("""
var EXPORTED_SYMBOLS = foo;
String.prototype.foo = "bar";
""").failed()

31 changes: 26 additions & 5 deletions tests/test_js_overwrite.py
@@ -1,5 +1,6 @@
from js_helper import _do_test_raw, _get_var


def test_new_overwrite():
"Tests that objects created with `new` can be overwritten"

Expand All @@ -10,6 +11,7 @@ def test_new_overwrite():
""")
assert not results.message_count


def test_redefine_new_instance():
"Test the redefinition of an instance of a global type."

Expand All @@ -21,6 +23,7 @@ def test_redefine_new_instance():
""")
assert not results.message_count


def test_property_members():
"Tests that properties and members are treated fairly"

Expand All @@ -32,19 +35,27 @@ def test_property_members():
assert _get_var(results, "y") == "bar"
assert _get_var(results, "z") == "bar"


def test_bug621106():
"Tests that important objects cannot be overridden by JS"

err = _do_test_raw("""
assert _do_test_raw("""
Number.prototype = "This is the new prototype";
""").failed()

assert _do_test_raw("""
Object.prototype.test = "bar";
""").failed()

assert _do_test_raw("""
Object = "asdf";
""").failed()

assert _do_test_raw("""
var x = Object.prototype;
x.test = "asdf";
""")
# There should be four errors (prototypes are only readonly)
print err.message_count
assert err.message_count == 4
""").failed()


def test_with_statement():
"Tests that 'with' statements work as intended"
Expand All @@ -71,3 +82,13 @@ def test_with_statement():
""")
assert err.failed()


def test_local_global_overwrite():
"""Test that a global assigned to a local variable can be overwritten."""

err = _do_test_raw("""
foo = String.prototype;
foo = "bar";
""")
assert not err.failed()

32 changes: 32 additions & 0 deletions tests/test_js_pollution.py
@@ -0,0 +1,32 @@
from js_helper import _do_test_raw


def test_pollution():
"""Make sure that the JS namespace pollution tests are done properly."""

assert not _do_test_raw("""
a = "foo";
b = "foo";
c = "foo";
""", ignore_pollution=False).failed()

assert _do_test_raw("""
a = "foo";
b = "foo";
c = "foo";
d = "foo";
""", ignore_pollution=False).failed()


def test_pollution_jsm():
"""
Make sure that JSM files don't have to worry about namespace pollution.
"""

assert not _do_test_raw("""
a = "foo";
b = "foo";
c = "foo";
d = "foo";
""", path="foo.jsm", ignore_pollution=False).failed()

80 changes: 64 additions & 16 deletions validator/testcases/javascript/actions.py
Expand Up @@ -3,9 +3,20 @@

import spidermonkey
import instanceactions
import instanceproperties
from jstypes import *


def _get_member_exp_property(traverser, node):
"""Return the string value of a member expression's property."""

if node["property"]["type"] == "Identifier":
return unicode(node["property"]["name"])
else:
eval_exp = traverser._traverse_node(node["property"])
return unicode(eval_exp.get_literal_value())


def trace_member(traverser, node):
"Traces a MemberExpression and returns the appropriate object"

Expand All @@ -14,9 +25,6 @@ def trace_member(traverser, node):
# x.y or x[y]
# x = base
base = trace_member(traverser, node["object"])
if not isinstance(base, JSWrapper):
base = JSWrapper(base, traverser=traverser)

if (base.is_global and
"value" in base.value and
isinstance(base.value["value"], types.LambdaType)):
Expand All @@ -30,22 +38,14 @@ def trace_member(traverser, node):
# 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

identifier = None
if node["property"]["type"] == "Identifier":
# y = token identifier
identifier = unicode(node["property"]["name"])
traverser._debug("MEMBER_EXP>>IDENT : %s" % identifier)
else:
# y = literal value
identifier = unicode(
traverser._traverse_node(node["property"]).get_literal_value())
traverser._debug("MEMBER_EXP>>LITERAL : %s" % identifier)

identifier = _get_member_exp_property(traverser, node)
test_identifier(traverser, identifier)
traverser._debug("MEMBER_EXP>>PROPERTY: %s" % identifier)
return base.get(traverser=traverser,
name=identifier)

Expand Down Expand Up @@ -399,8 +399,8 @@ def _ident(traverser, node):
# Ban bits like "newThread"
test_identifier(traverser, name)

if traverser._is_local_variable(name) or \
traverser._is_global(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
Expand All @@ -422,6 +422,54 @@ def _expr_assignment(traverser, node):
traverser._debug("ASSIGNMENT>>PARSING RIGHT")
right = traverser._traverse_node(node["right"])
right = JSWrapper(right, traverser=traverser)

# Treat direct assignment different than augmented assignment.
if node["operator"] == "=":

global_overwrite = False
node_left = node["left"]
traverser._debug("ASSIGNMENT:DIRECT(%s)" % node_left["type"])
if node_left["type"] == "Identifier":
# Identifiers just need the ID name and a value to push.
# Raise a global overwrite issue if the identifier is global.
global_overwrite = traverser._is_global(node_left["name"])
traverser._set_variable(node_left["name"], right)
elif node_left["type"] == "MemberExpression":
member_object = trace_member(traverser, node_left["object"])
global_overwrite = member_object.is_global

member_property = _get_member_exp_property(traverser, node_left)
traverser._debug("ASSIGNMENT:MEMBER_PROPERTY(%s)" % member_property)

# Make sure to perform any setter operations.
setter = instanceproperties.get_operation("set", member_property)
if setter:
right = setter(right, traverser) or right or None

# NoneType error protection
if not global_overwrite and member_object.value is None:
member_object.value = JSObject()

# Don't do the assignment if we're facing a global.
if not global_overwrite:
member_object.value.set(member_property, right)

traverser._debug("ASSIGNMENT:DIRECT:GLOB_OVERWRITE %s" %
global_overwrite)
if global_overwrite and not traverser.is_jsm:
traverser.err.warning(
err_id=("testcases_javascript_actions",
"_expr_assignment",
"global_overwrite"),
warning="Global variable overwrite",
description="An attempt was made to overwrite a global "
"variable in some JavaScript code.",
line=traverser.line,
column=traverser.position,
context=traverser.context)

return right

lit_right = right.get_literal_value()

traverser._debug("ASSIGNMENT>>PARSING LEFT")
Expand Down
12 changes: 9 additions & 3 deletions validator/testcases/javascript/jstypes.py
Expand Up @@ -91,6 +91,9 @@ def __init__(self, value=None, const=False, dirty=False, lazy=False,
self.lazy = lazy
self.callable = callable

# This will be set in actions.py if needed.
self.global_parent = False

def set_value(self, value, traverser=None, overwrite_const=False):
"""Assigns a value to the wrapper"""

Expand Down Expand Up @@ -119,9 +122,12 @@ def set_value(self, value, traverser=None, overwrite_const=False):
return

# We want to obey the permissions of global objects
if self.is_global and (isinstance(self.value, dict) and
("overwriteable" not in self.value or
self.value["overwriteable"] == False)):
if (self.is_global and
(not traverser or
not traverser.is_jsm) and
(isinstance(self.value, dict) and
("overwriteable" not in self.value or
self.value["overwriteable"] == False))):
# TODO : Write in support for "readonly":False
traverser.err.warning(("testcases_javascript_jstypes",
"JSWrapper_set_value",
Expand Down

0 comments on commit b77ffa6

Please sign in to comment.