1 | // Copyright (C) 2007 Google Inc. |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | package com.google.caja.parser.quasiliteral; |
16 | |
17 | import java.io.IOException; |
18 | import java.net.URI; |
19 | import java.util.Arrays; |
20 | import java.util.EnumSet; |
21 | import java.util.List; |
22 | |
23 | import junit.framework.AssertionFailedError; |
24 | |
25 | import com.google.caja.lexer.ExternalReference; |
26 | import com.google.caja.lexer.FetchedData; |
27 | import com.google.caja.lexer.FilePosition; |
28 | import com.google.caja.lexer.InputSource; |
29 | import com.google.caja.lexer.ParseException; |
30 | import com.google.caja.parser.ParseTreeNode; |
31 | import com.google.caja.parser.js.Block; |
32 | import com.google.caja.parser.js.FormalParam; |
33 | import com.google.caja.parser.js.FunctionConstructor; |
34 | import com.google.caja.parser.js.FunctionDeclaration; |
35 | import com.google.caja.parser.js.Identifier; |
36 | import com.google.caja.parser.js.Operation; |
37 | import com.google.caja.parser.js.Operator; |
38 | import com.google.caja.parser.js.Reference; |
39 | import com.google.caja.parser.js.ReturnStmt; |
40 | import com.google.caja.parser.js.Statement; |
41 | import com.google.caja.parser.js.SyntheticNodes; |
42 | import com.google.caja.parser.js.UncajoledModule; |
43 | import com.google.caja.plugin.UriFetcher; |
44 | import com.google.caja.reporting.MessageLevel; |
45 | import com.google.caja.reporting.MessageType; |
46 | import com.google.caja.reporting.TestBuildInfo; |
47 | import com.google.caja.util.Executor; |
48 | import com.google.caja.util.FailureIsAnOption; |
49 | import com.google.caja.util.Lists; |
50 | import com.google.caja.util.RhinoTestBed; |
51 | |
52 | public class ES53RewriterTest extends CommonJsRewriterTestCase { |
53 | protected class TestUriFetcher implements UriFetcher { |
54 | public FetchedData fetch(ExternalReference ref, String mimeType) |
55 | throws UriFetchException { |
56 | try { |
57 | URI uri = ref.getReferencePosition().source().getUri() |
58 | .resolve(ref.getUri()); |
59 | if ("resource".equals(uri.getScheme())) { |
60 | return dataFromResource(uri.getPath(), new InputSource(uri)); |
61 | } else { |
62 | throw new UriFetchException(ref, mimeType); |
63 | } |
64 | } catch (IOException ex) { |
65 | throw new UriFetchException(ref, mimeType, ex); |
66 | } |
67 | } |
68 | } |
69 | |
70 | private Rewriter es53Rewriter; |
71 | |
72 | /** |
73 | * Tests that an inherited <tt>*_w___</tt> flag does not enable |
74 | * bogus writability. |
75 | * <p> |
76 | * See <a href="http://code.google.com/p/google-caja/issues/detail?id=1052" |
77 | * >issue 1052</a>. |
78 | */ |
79 | public final void testNoFastpathWritableInheritance() throws Exception { |
80 | rewriteAndExecute( |
81 | "(function() {" + |
82 | " var a = {};" + |
83 | " var b = Object.freeze(Object.create(a));" + |
84 | " a.x = 8;" + |
85 | " assertThrows(function(){b.x = 9;});" + |
86 | " assertEquals(b.x, 8);" + |
87 | "})();"); |
88 | } |
89 | |
90 | public final void testConstant() throws Exception { |
91 | assertConsistent("1;"); |
92 | } |
93 | |
94 | public final void testInit() throws Exception { |
95 | assertConsistent("var a = 0; a;"); |
96 | } |
97 | |
98 | public final void testNew() throws Exception { |
99 | assertConsistent( |
100 | "function f() { this.x = 1; }" + |
101 | "var g = new f();" + |
102 | "g.x;"); |
103 | } |
104 | |
105 | public final void testThrowCatch() throws Exception { |
106 | assertConsistent( |
107 | "var x = 0; try { throw 1; }" + |
108 | "catch (e) { x = e; }" + |
109 | "x;"); |
110 | assertConsistent( |
111 | "var x = 0; try { throw { a: 1 }; }" + |
112 | "catch (e) { x = e; }" + |
113 | "'' + x;"); |
114 | assertConsistent( |
115 | "var x = 0; try { throw 'err'; }" + |
116 | "catch (e) { x = e; }" + |
117 | "x;"); |
118 | assertConsistent( |
119 | "var x = 0; try { throw new Error('err'); }" + |
120 | "catch (e) { x = e.message; }" + |
121 | "x;"); |
122 | assertConsistent( |
123 | "var x = 0; try { throw 1; }" + |
124 | "catch (e) { x = e; }" + |
125 | "finally { x = 2; }" + |
126 | "x;"); |
127 | assertConsistent( |
128 | "var x = 0; try { throw { a: 1 }; }" + |
129 | "catch (e) { x = e; }" + |
130 | "finally { x = 2; }" + |
131 | "x;"); |
132 | assertConsistent( |
133 | "var x = 0; try { throw 'err'; }" + |
134 | "catch (e) { x = e; }" + |
135 | "finally { x = 2; }" + |
136 | "x;"); |
137 | assertConsistent( |
138 | "var x = 0; try { throw new Error('err'); }" + |
139 | "catch (e) { x = e.message; }" + |
140 | "finally { x = 2; }" + |
141 | "x;"); |
142 | } |
143 | |
144 | public final void testProtoCall() throws Exception { |
145 | assertConsistent("Array.prototype.sort.call([3, 1, 2]);"); |
146 | assertConsistent("[3, 1, 2].sort();"); |
147 | assertConsistent("[3, 1, 2].sort.call([4, 2, 7]);"); |
148 | |
149 | assertConsistent("String.prototype.indexOf.call('foo', 'o');"); |
150 | assertConsistent("'foo'.indexOf('o');"); |
151 | |
152 | assertConsistent("'foo'.indexOf.call('bar', 'o');"); |
153 | assertConsistent("'foo'.indexOf.call('bar', 'a');"); |
154 | } |
155 | |
156 | public final void testInherit() throws Exception { |
157 | assertConsistent( |
158 | "function Point(x) { this.x = x; }\n" + |
159 | "Point.prototype.toString = function () {\n" + |
160 | " return '<' + this.x + '>';\n" + |
161 | "};\n" + |
162 | "function WP(x) { Point.call(this,x); }\n" + |
163 | "WP.prototype = Object.create(Point.prototype);\n" + |
164 | "var pt = new WP(3);\n" + |
165 | "pt.toString();"); |
166 | } |
167 | |
168 | /** See bug 528 */ |
169 | public final void testRegExpLeak() throws Exception { |
170 | rewriteAndExecute( |
171 | "assertEquals('' + (/(.*)/).exec(), 'undefined,undefined');"); |
172 | } |
173 | |
174 | public final void testClosure() throws Exception { |
175 | assertConsistent( |
176 | "function f() {" + |
177 | " var y = 2; " + |
178 | " this.x = function() {" + |
179 | " return y;" + |
180 | " }; " + |
181 | "}" + |
182 | "var g = new f();" + |
183 | "var h = {};" + |
184 | "f.call(h);" + |
185 | "h.y = g.x;" + |
186 | "h.x() + h.y();"); |
187 | } |
188 | |
189 | public final void testNamedFunctionShadow() throws Exception { |
190 | assertConsistent("function f() { return f; } f === f();"); |
191 | assertConsistent( |
192 | "(function () { function f() { return f; } return f === f(); })();"); |
193 | } |
194 | |
195 | public final void testArray() throws Exception { |
196 | assertConsistent("[3, 2, 1].sort();"); |
197 | assertConsistent("[3, 2, 1].sort.call([4, 2, 7]);"); |
198 | } |
199 | |
200 | public final void testObject() throws Exception { |
201 | assertConsistent("({ x: 1, y: 2 });"); |
202 | } |
203 | |
204 | //TODO(erights): Fix these tests to test for the output we now expect. |
205 | @FailureIsAnOption |
206 | public final void testFunctionToStringCall() throws Exception { |
207 | rewriteAndExecute( |
208 | "function foo() {}\n" |
209 | + "assertEquals(foo.toString(),\n" |
210 | + " 'function foo() {\\n [cajoled code]\\n}');"); |
211 | rewriteAndExecute( |
212 | "function foo (a, b) { xx; }\n" |
213 | + "assertEquals(foo.toString(),\n" |
214 | + " 'function foo(a, b) {\\n [cajoled code]\\n}');"); |
215 | rewriteAndExecute( |
216 | "function foo() {}\n" |
217 | + "assertEquals(Function.prototype.toString.call(foo),\n" |
218 | + " 'function foo() {\\n [cajoled code]\\n}');"); |
219 | rewriteAndExecute( |
220 | "var foo = function (x$x, y_y) {};\n" |
221 | + "assertEquals(" |
222 | + " Function.prototype.toString.call(foo),\n" |
223 | + " 'function foo$_var(x$x, y_y) {\\n [cajoled code]\\n}');"); |
224 | } |
225 | |
226 | public final void testDate() throws Exception { |
227 | assertConsistent("(new Date(0)).getTime();"); |
228 | assertConsistent("'' + (new Date(0));"); |
229 | rewriteAndExecute( |
230 | "" |
231 | + "var time = (new Date - 1);" |
232 | + "assertFalse(isNaN(time));" |
233 | + "assertEquals('number', typeof time);"); |
234 | } |
235 | |
236 | public final void testMultiDeclaration2() throws Exception { |
237 | rewriteAndExecute("var a, b, c;"); |
238 | rewriteAndExecute( |
239 | "" |
240 | + "var a = 0, b = ++a, c = ++a;" |
241 | + "assertEquals(++a * b / c, 1.5);"); |
242 | } |
243 | |
244 | public final void testDelete() throws Exception { |
245 | assertConsistent( |
246 | "(function () { var a = { x: 1 }; delete a.x; return typeof a.x; })();" |
247 | ); |
248 | assertConsistent("var a = { x: 1 }; delete a.x; typeof a.x;"); |
249 | } |
250 | |
251 | public final void testIn2() throws Exception { |
252 | assertConsistent( |
253 | "(function () {" + |
254 | " var a = { x: 1 };\n" + |
255 | " return '' + ('x' in a) + ('y' in a);" + |
256 | "})();"); |
257 | assertConsistent( |
258 | "var a = { x: 1 };\n" + |
259 | "[('x' in a), ('y' in a)];"); |
260 | } |
261 | |
262 | /** |
263 | * Try to construct some class instances. |
264 | */ |
265 | public final void testFuncCtor() throws Exception { |
266 | rewriteAndExecute( |
267 | "function Foo(x) { this.x = x; }" + |
268 | "var foo = new Foo(2);" + |
269 | "if (!foo) { fail('Failed to construct a global object.'); }" + |
270 | "assertEquals(2, foo.x);" + |
271 | "assertEquals(Foo, foo.constructor);"); |
272 | rewriteAndExecute( |
273 | "(function () {" + |
274 | " function Foo(x) { this.x = x; }" + |
275 | " var foo = new Foo(2);" + |
276 | " if (!foo) { fail('Failed to construct a local object.'); }" + |
277 | " assertEquals(2, foo.x);" + |
278 | "})();"); |
279 | rewriteAndExecute( |
280 | "function Foo() { }" + |
281 | "var foo = new Foo();" + |
282 | "if (!foo) {" + |
283 | " fail('Failed to use a simple named function as a constructor.');" + |
284 | "}"); |
285 | } |
286 | |
287 | public final void testFuncArgs() throws Exception { |
288 | rewriteAndExecute( |
289 | "" |
290 | + "var x = 0;" |
291 | + "function f() { x = arguments[0]; }" |
292 | + "f(3);" |
293 | + "assertEquals(3, x);"); |
294 | } |
295 | |
296 | public final void testStatic() throws Exception { |
297 | assertConsistent("Array.slice([3, 4, 5, 6], 1);"); |
298 | } |
299 | |
300 | public final void testConcatArgs() throws Exception { |
301 | rewriteAndExecute("", "(function(x, y){ return [x, y]; })", |
302 | "var f = ___.getNewModuleHandler().getLastValue();" |
303 | + "function g(var_args) { return f.apply(___.USELESS, arguments); }" |
304 | + "assertEquals(g(3, 4).toString(), [3, 4].toString());"); |
305 | } |
306 | |
307 | public final void testReformedGenerics() throws Exception { |
308 | assertConsistent( |
309 | "var x = [33];" + |
310 | "x.foo = [].push;" + |
311 | "x.foo(44);" + |
312 | "x;"); |
313 | assertConsistent( |
314 | "var x = {blue:'green'};" + |
315 | "x.foo = [].push;" + |
316 | "x.foo(44);" + |
317 | "var keys = [];" + |
318 | "for (var i in x) { if (x.hasOwnProperty(i)) { keys.push(i); } }" + |
319 | "keys.sort();"); |
320 | assertConsistent( |
321 | "var x = [33];" + |
322 | "Array.prototype.push.apply(x, [3,4,5]);" + |
323 | "x;"); |
324 | assertConsistent( |
325 | "var x = {blue:'green'};" + |
326 | "Array.prototype.push.apply(x, [3,4,5]);" + |
327 | "var keys = [];" + |
328 | "for (var i in x) { if (x.hasOwnProperty(i)) { keys.push(i); } }" + |
329 | "keys.sort();"); |
330 | assertConsistent( |
331 | "var x = {blue:'green'};" + |
332 | "x.foo = [].push;" + |
333 | "x.foo.call(x, 44);" + |
334 | "var keys = [];" + |
335 | "for (var i in x) { if (x.hasOwnProperty(i)) { keys.push(i); } }" + |
336 | "keys.sort();"); |
337 | } |
338 | |
339 | public final void testMonkeyPatchPrimordialFunction() throws Exception { |
340 | assertConsistent( |
341 | "isNaN.foo = 'bar';" + |
342 | "isNaN.foo;"); |
343 | } |
344 | |
345 | public final void testInMonkeyDelete() throws Exception { |
346 | assertConsistent( |
347 | "var x = {y:1 };" + |
348 | "delete x.y;" + |
349 | "('y' in x);"); |
350 | } |
351 | |
352 | public final void testMonkeyOverride() throws Exception { |
353 | assertConsistent( |
354 | // TODO(erights): Fix when bug 953 is fixed. |
355 | "Date.prototype.propertyIsEnumerable = function(p) { return true; };" + |
356 | "(new Date()).propertyIsEnumerable('foo');"); |
357 | } |
358 | |
359 | public final void testEmbeddedcajaVM() throws Exception { |
360 | assertConsistent( |
361 | "" |
362 | + "\"use strict,cajaVM\"; \n" |
363 | + "var foo; \n" |
364 | + "(function () { \n" |
365 | + " foo = function () { return 8; }; \n" |
366 | + "})(); \n" |
367 | + "foo();" |
368 | ); |
369 | } |
370 | |
371 | /** |
372 | * Tests that Error objects are frozen |
373 | * |
374 | * See issue 1097, issue 1038, |
375 | * and {@link CommonJsRewriterTestCase#testErrorTaming()}}. |
376 | */ |
377 | public final void testErrorFreeze() throws Exception { |
378 | rewriteAndExecute( |
379 | "try {" + |
380 | " throw new Error('foo');" + |
381 | "} catch (ex) {" + |
382 | " assertTrue(Object.isFrozen(ex));" + |
383 | "}"); |
384 | } |
385 | |
386 | /** |
387 | * |
388 | */ |
389 | public final void testObjectFreeze() throws Exception { |
390 | rewriteAndExecute( |
391 | "var r = Object.freeze({});" + |
392 | "assertThrows(function(){r.foo = 8;});"); |
393 | rewriteAndExecute( |
394 | "var f = function(){};" + |
395 | "f.foo = 8;"); |
396 | rewriteAndExecute( |
397 | "var f = Object.freeze(function(){});" + |
398 | "assertThrows(function(){f.foo = 8;});"); |
399 | rewriteAndExecute( |
400 | "function Point(x,y) {" + |
401 | " this.x = x;" + |
402 | " this.y = y;" + |
403 | "}" + |
404 | "var pt = new Point(3,5);" + |
405 | "pt.x = 8;" + |
406 | "Object.freeze(pt);" + |
407 | "assertThrows(function(){pt.y = 9;});"); |
408 | // Check that deferred creation of prototype property doesn't make it |
409 | // writable. |
410 | rewriteAndExecute( |
411 | "function f(){}" + |
412 | "Object.freeze(f);" + |
413 | "assertThrows(function() { f.prototype = {}; });"); |
414 | } |
415 | |
416 | /** |
417 | * Tests that the {@code prototype}, {@code name}, and {@code length} |
418 | * properties of function instances are set properly. |
419 | */ |
420 | public final void testFunctionInstance() throws Exception { |
421 | rewriteAndExecute( |
422 | "assertTrue(!!((function(){}).prototype));"); |
423 | rewriteAndExecute( |
424 | "assertEquals((function(a,b,c){}).length, 3);"); |
425 | rewriteAndExecute( |
426 | "assertEquals((function x(a,b,c){}).name, 'x');"); |
427 | // Check frozen functions created early in es53.js |
428 | rewriteAndExecute( |
429 | "assertTrue(!!(cajaVM.USELESS.toString.prototype));"); |
430 | } |
431 | |
432 | /** |
433 | * Tests that the special handling of null on tamed exophora works. |
434 | * <p> |
435 | * The reification of tamed exophoric functions contains |
436 | * special cases for when the first argument to call, bind, or apply |
437 | * is null or undefined, in order to protect against privilege escalation. |
438 | * {@code #testNoPrivilegeEscalation()} tests that we do prevent the |
439 | * privilege escalation. Here, we test that this special case preserves |
440 | * correct functionality. |
441 | */ |
442 | public final void testTamedXo4aOkOnNull() throws Exception { |
443 | rewriteAndExecute("this.foo = 8;", |
444 | |
445 | "var x = Object.create(cajaVM.USELESS);" + |
446 | "assertFalse(({foo: 7}).hasOwnProperty.call(null, 'foo'));" + |
447 | "assertTrue(cajaVM.USELESS.isPrototypeOf(x));" + |
448 | "assertTrue(({foo: 7}).isPrototypeOf.call(null, x));", |
449 | |
450 | "assertTrue(({}).hasOwnProperty.call(null, 'foo'));" + |
451 | "assertFalse(({bar: 7}).hasOwnProperty.call(null, 'bar'));"); |
452 | rewriteAndExecute("this.foo = 8;", |
453 | |
454 | "var x = Object.create(cajaVM.USELESS);" + |
455 | "assertFalse(({foo: 7}).hasOwnProperty.apply(null, ['foo']));" + |
456 | "assertTrue(cajaVM.USELESS.isPrototypeOf(x));" + |
457 | "assertTrue(({foo: 7}).isPrototypeOf.apply(null, [x]));", |
458 | |
459 | "assertTrue(({}).hasOwnProperty.apply(null, ['foo']));" + |
460 | "assertFalse(({bar: 7}).hasOwnProperty.apply(null, ['bar']));"); |
461 | rewriteAndExecute( |
462 | "var x = Object.create(cajaVM.USELESS);" + |
463 | "assertFalse(({foo: 7}).hasOwnProperty.bind(null)('foo'));" + |
464 | "assertTrue(cajaVM.USELESS.isPrototypeOf(x));" + |
465 | "assertTrue(({foo: 7}).isPrototypeOf.bind(null)(x));"); |
466 | } |
467 | |
468 | public final void testToString() throws Exception { |
469 | assertConsistent( |
470 | "var z = { toString: function () { return 'blah'; } };" + |
471 | "try {" + |
472 | " '' + z;" + |
473 | "} catch (e) {" + |
474 | " throw new Error('PlusPlus error: ' + e);" + |
475 | "}"); |
476 | assertConsistent( |
477 | " function foo() {" |
478 | + " var x = 1;" |
479 | + " return {" |
480 | + " toString: function () {" |
481 | + " return x;" |
482 | + " }" |
483 | + " };" |
484 | + "}" |
485 | + "'' + (new foo);"); |
486 | } |
487 | |
488 | public final void testToStringToxicity() throws Exception { |
489 | rewriteAndExecute( |
490 | "", |
491 | "function objMaker(f) {return {toString:f};}", |
492 | "" |
493 | + "assertThrows(" |
494 | + " function() {testImports.objMaker(function(){return '1';});});" |
495 | ); |
496 | } |
497 | |
498 | public final void testInitializeMap() throws Exception { |
499 | assertConsistent("var zerubabel = {bobble:2, apple:1}; zerubabel.apple;"); |
500 | } |
501 | |
502 | public final void testValueOf() throws Exception { |
503 | assertConsistent("''+{valueOf:function(){return 5;}}"); |
504 | } |
505 | |
506 | public final void testAssertEqualsCajoled() throws Exception { |
507 | try { |
508 | rewriteAndExecute("assertEquals(1, 2);"); |
509 | } catch (AssertionFailedError e) { |
510 | return; |
511 | } |
512 | fail("Assertions do not work in cajoled mode"); |
513 | } |
514 | |
515 | public final void testAssertThrowsCajoledNoError() throws Exception { |
516 | rewriteAndExecute( |
517 | " assertThrows(function() { throw 'foo'; });"); |
518 | rewriteAndExecute( |
519 | " assertThrows(" |
520 | + " function() { throw 'foo'; }," |
521 | + " 'foo');"); |
522 | } |
523 | |
524 | public final void testAssertThrowsCajoledErrorNoMsg() throws Exception { |
525 | try { |
526 | rewriteAndExecute("assertThrows(function() {});"); |
527 | } catch (AssertionFailedError e) { |
528 | return; |
529 | } |
530 | fail("Assertions do not work in cajoled mode"); |
531 | } |
532 | |
533 | public final void testAssertThrowsCajoledErrorWithMsg() throws Exception { |
534 | try { |
535 | rewriteAndExecute("assertThrows(function() {}, 'foo');"); |
536 | } catch (AssertionFailedError e) { |
537 | return; |
538 | } |
539 | fail("Assertions do not work in cajoled mode"); |
540 | } |
541 | |
542 | public final void testConstructionWithFunction() throws Exception { |
543 | assertConsistent( |
544 | " function Point() {}" |
545 | + "var p = new Point();" |
546 | + "(p !== undefined);"); |
547 | assertConsistent( |
548 | " var Point = function() {};" |
549 | + "var p = new Point();" |
550 | + "(p !== undefined);"); |
551 | } |
552 | |
553 | public final void testReflectiveMethodInvocation() throws Exception { |
554 | assertConsistent( |
555 | "(function (first, second) { return 'a' + first + 'b' + second; })" |
556 | + ".call([], 8, 9);"); |
557 | assertConsistent( |
558 | "var a = []; [].push.call(a, 5, 6); a;"); |
559 | assertConsistent( |
560 | "(function (a, b) { return 'a' + a + 'b' + b; }).apply([], [8, 9]);"); |
561 | assertConsistent( |
562 | "var a = []; [].push.apply(a, [5, 6]); a;"); |
563 | assertConsistent( |
564 | "[].sort.apply([6, 5]);"); |
565 | assertConsistent( |
566 | "(function (first, second) { return 'a' + first + 'b' + second; })" |
567 | + ".bind([], 8)(9);"); |
568 | } |
569 | |
570 | /** |
571 | * Tests that <a href= |
572 | * "http://code.google.com/p/google-caja/issues/detail?id=242" |
573 | * >bug#242</a> is fixed. |
574 | * <p> |
575 | * The actual Function.bind() method used to be whitelisted and |
576 | * written to return a frozen simple-function, allowing it to be called |
577 | * from all code on all functions. As a result, if an <i>outer hull breach</i> |
578 | * occurs -- if Caja code obtains a reference to a JavaScript |
579 | * function value not marked as Caja-callable -- then |
580 | * that Caja code could call the whitelisted bind() on it, |
581 | * and then call the result, causing an <i>inner hull breach</i>, |
582 | * which threatens kernel integrity. |
583 | */ |
584 | public final void testToxicBind() throws Exception { |
585 | rewriteAndExecute( |
586 | "var confused = false;" + |
587 | "testImports.keystone = function keystone() { confused = true; };", |
588 | "assertThrows(function() {keystone.bind()();});", |
589 | "assertFalse(confused);"); |
590 | } |
591 | |
592 | /** |
593 | * Tests that <a href= |
594 | * "http://code.google.com/p/google-caja/issues/detail?id=590" |
595 | * >bug#590</a> is fixed. |
596 | * <p> |
597 | * As a client of an object, Caja code must only be able to directly delete |
598 | * <i>public</i> properties of non-frozen JSON containers. Due to this bug, |
599 | * Caja code was able to delete properties in the Caja namespace. |
600 | */ |
601 | public final void testBadDelete() throws Exception { |
602 | rewriteAndExecute( |
603 | "testImports.badContainer = {secret__: 3469};", |
604 | "assertThrows(function() {delete badContainer['secret__'];});", |
605 | "assertEquals(testImports.badContainer.secret__, 3469);"); |
606 | rewriteAndExecute( |
607 | "assertThrows(function() {delete ({})['proto___'];});"); |
608 | } |
609 | |
610 | /** |
611 | * Tests that apply works. |
612 | */ |
613 | public final void testApply() throws Exception { |
614 | rewriteAndExecute( |
615 | "", |
616 | "var x = 0;" + |
617 | "function f() { x = 1 }\n" + |
618 | "f.apply({});", |
619 | "assertEquals(testImports.x, 1);"); |
620 | // TODO(erights): Need more tests. |
621 | } |
622 | |
623 | /** |
624 | * Tests that <a href= |
625 | * "http://code.google.com/p/google-caja/issues/detail?id=347" |
626 | * >bug#347</a> is fixed. |
627 | * <p> |
628 | * The <tt>in</tt> operator should only test for properties visible to Caja. |
629 | */ |
630 | public final void testInVeil() throws Exception { |
631 | rewriteAndExecute( |
632 | "assertFalse('f___' in Object);"); |
633 | } |
634 | |
635 | //////////////////////////////////////////////////////////////////////// |
636 | // Handling of synthetic nodes |
637 | //////////////////////////////////////////////////////////////////////// |
638 | |
639 | public final void testSyntheticIsUntouched() throws Exception { |
640 | String source = "function foo() { this; arguments; }"; |
641 | ParseTreeNode input = js(fromString(source)); |
642 | syntheticTree(input); |
643 | checkSucceeds(input, js(fromString("var dis___ = IMPORTS___;" + source))); |
644 | } |
645 | |
646 | public final void testSyntheticMemberAccess() throws Exception { |
647 | ParseTreeNode input = js(fromString("({}).foo")); |
648 | syntheticTree(input); |
649 | checkSucceeds( |
650 | input, |
651 | js(fromString("var dis___ = IMPORTS___; ___.iM([]).foo;"))); |
652 | } |
653 | |
654 | public final void testSyntheticFormals() throws Exception { |
655 | FilePosition unk = FilePosition.UNKNOWN; |
656 | FunctionConstructor fc = new FunctionConstructor( |
657 | unk, |
658 | new Identifier(unk, "f"), |
659 | Arrays.asList( |
660 | new FormalParam(new Identifier(unk, "x")), |
661 | new FormalParam( |
662 | SyntheticNodes.s(new Identifier(unk, "y___")))), |
663 | new Block( |
664 | unk, |
665 | Arrays.<Statement>asList(new ReturnStmt( |
666 | unk, |
667 | Operation.createInfix( |
668 | Operator.MULTIPLICATION, |
669 | Operation.createInfix( |
670 | Operator.ADDITION, |
671 | new Reference(new Identifier(unk, "x")), |
672 | new Reference(SyntheticNodes.s( |
673 | new Identifier(unk, "y___")))), |
674 | new Reference(new Identifier(unk, "z"))))))); |
675 | checkSucceeds( |
676 | new Block( |
677 | unk, |
678 | Arrays.asList( |
679 | new FunctionDeclaration((FunctionConstructor) fc.clone()))), |
680 | js(fromString( |
681 | "" |
682 | // x and y___ are formals, but z is free to the function. |
683 | + "var dis___ = IMPORTS___;" |
684 | + "{" |
685 | + " function f(x, y___) {" |
686 | + " return (x + y___) *" |
687 | + " (IMPORTS___.z_v___ ?" |
688 | + " IMPORTS___.z :" |
689 | + " ___.ri(IMPORTS___, 'z'));" |
690 | + " }" |
691 | + " IMPORTS___.w___('f', ___.f(f, 'f'));" |
692 | + "}"))); |
693 | |
694 | SyntheticNodes.s(fc); |
695 | checkSucceeds( |
696 | new Block( |
697 | unk, |
698 | Arrays.asList( |
699 | new FunctionDeclaration((FunctionConstructor) fc.clone()))), |
700 | js(fromString( |
701 | "" |
702 | // x and y___ are formals, but z is free to the function. |
703 | + "var dis___ = IMPORTS___;" |
704 | + "function f(x, y___) {" |
705 | + " return (x + y___) *" |
706 | + " (IMPORTS___.z_v___ ?" |
707 | + " IMPORTS___.z :" |
708 | + " ___.ri(IMPORTS___, 'z'));" |
709 | + "}" |
710 | // Since the function is synthetic, it is not marked. |
711 | ))); |
712 | } |
713 | |
714 | //////////////////////////////////////////////////////////////////////// |
715 | // Specific rules |
716 | //////////////////////////////////////////////////////////////////////// |
717 | |
718 | public final void testWith() throws Exception { |
719 | checkFails("with (dreams || ambiguousScoping) anything.isPossible();", |
720 | "\"with\" blocks are not allowed"); |
721 | checkFails("with (dreams || ambiguousScoping) { anything.isPossible(); }", |
722 | "\"with\" blocks are not allowed"); |
723 | } |
724 | |
725 | public final void testTryCatch() throws Exception { |
726 | checkAddsMessage(js(fromString( |
727 | "try {" + |
728 | " throw 2;" + |
729 | "} catch (e) {" + |
730 | " var e;" + |
731 | "}")), |
732 | MessageType.MASKING_SYMBOL, |
733 | MessageLevel.ERROR); |
734 | checkAddsMessage(js(fromString( |
735 | "var e;" + |
736 | "try {" + |
737 | " throw 2;" + |
738 | "} catch (e) {" + |
739 | "}")), |
740 | MessageType.MASKING_SYMBOL, |
741 | MessageLevel.ERROR); |
742 | checkAddsMessage(js(fromString( |
743 | "try {} catch (x__) { }")), |
744 | RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE); |
745 | checkAddsMessage(js(fromString( |
746 | "var x;" + |
747 | "try {" + |
748 | " g[x + 0];" + |
749 | " g[x + 1];" + |
750 | "} catch (e) {" + |
751 | " g[x + 2];" + |
752 | " e;" + |
753 | " g[x + 3];" + |
754 | "}" + |
755 | "var e;")), |
756 | MessageType.MASKING_SYMBOL, |
757 | MessageLevel.ERROR); |
758 | rewriteAndExecute( |
759 | "var handled = false;" + |
760 | "try {" + |
761 | " throw null;" + |
762 | "} catch (ex) {" + |
763 | " assertEquals(null, ex);" + // Right value in ex. |
764 | " handled = true;" + |
765 | "}" + |
766 | "assertTrue(handled);"); // Control reached and left the catch block. |
767 | rewriteAndExecute( |
768 | "var handled = false;" + |
769 | "try {" + |
770 | " throw undefined;" + |
771 | "} catch (ex) {" + |
772 | " assertEquals(undefined, ex);" + |
773 | " handled = true;" + |
774 | "}" + |
775 | "assertTrue(handled);"); |
776 | rewriteAndExecute( |
777 | "var handled = false;" + |
778 | "try {" + |
779 | " throw true;" + |
780 | "} catch (ex) {" + |
781 | " assertEquals(true, ex);" + |
782 | " handled = true;" + |
783 | "}" + |
784 | "assertTrue(handled);"); |
785 | rewriteAndExecute( |
786 | "var handled = false;" + |
787 | "try {" + |
788 | " throw 37639105;" + |
789 | "} catch (ex) {" + |
790 | " assertEquals(37639105, ex);" + |
791 | " handled = true;" + |
792 | "}" + |
793 | "assertTrue(handled);"); |
794 | rewriteAndExecute( |
795 | "var handled = false;" + |
796 | "try {" + |
797 | " throw 'panic';" + |
798 | "} catch (ex) {" + |
799 | " assertEquals('panic', ex);" + |
800 | " handled = true;" + |
801 | "}" + |
802 | "assertTrue(handled);"); |
803 | rewriteAndExecute( |
804 | "var handled = false;" + |
805 | "try {" + |
806 | " throw new Error('hello');" + |
807 | "} catch (ex) {" + |
808 | " assertEquals('hello', ex.message);" + |
809 | " assertEquals('Error', ex.name);" + |
810 | " handled = true;" + |
811 | "}" + |
812 | "assertTrue(handled);"); |
813 | rewriteAndExecute( |
814 | "var handled = false;" + |
815 | "try {" + |
816 | " throw function foo() { throw 'should not be called'; };" + |
817 | "} catch (ex) {" + |
818 | " assertEquals('In lieu of thrown function: foo', ex());" + |
819 | " handled = true;" + |
820 | "}" + |
821 | "assertTrue(handled);"); |
822 | rewriteAndExecute( |
823 | "var handled = false;" + |
824 | "try {" + |
825 | " throw { toString: function () { return 'hiya'; }, y: 4 };" + |
826 | "} catch (ex) {" + |
827 | " assertEquals('string', typeof ex);" + |
828 | " assertEquals('hiya', ex);" + |
829 | " handled = true;" + |
830 | "}" + |
831 | "assertTrue(handled);"); |
832 | rewriteAndExecute( |
833 | "var handled = false;" + |
834 | "try {" + |
835 | " throw { toString: function () { throw new Error(); } };" + |
836 | "} catch (ex) {" + |
837 | " assertEquals('Exception during exception handling.', ex);" + |
838 | " handled = true;" + |
839 | "}" + |
840 | "assertTrue(handled);"); |
841 | } |
842 | |
843 | public final void testTryCatchFinally() throws Exception { |
844 | checkAddsMessage(js(fromString( |
845 | "try {" + |
846 | "} catch (e) {" + |
847 | " var e;" + |
848 | "} finally {" + |
849 | "}")), |
850 | MessageType.MASKING_SYMBOL, |
851 | MessageLevel.ERROR); |
852 | checkAddsMessage(js(fromString( |
853 | "var e;" + |
854 | "try {" + |
855 | "} catch (e) {" + |
856 | "} finally {" + |
857 | "}")), |
858 | MessageType.MASKING_SYMBOL, |
859 | MessageLevel.ERROR); |
860 | checkAddsMessage(js(fromString( |
861 | "try {} catch (x__) { } finally { }")), |
862 | RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE); |
863 | } |
864 | |
865 | public final void testTryFinally() throws Exception { |
866 | assertConsistent( |
867 | "var out = 0;" + |
868 | "try {" + |
869 | " try {" + |
870 | " throw 2;" + |
871 | " } finally {" + |
872 | " out = 1;" + |
873 | " }" + |
874 | " out = 2;" + |
875 | "} catch (e) {" + |
876 | "}" + |
877 | "out;"); |
878 | } |
879 | |
880 | public final void testVarBadSuffix() throws Exception { |
881 | checkFails( |
882 | "function() { foo__; };", |
883 | "Variables cannot end in \"__\""); |
884 | // Make sure *single* underscore is okay |
885 | checkSucceeds( |
886 | "function() { var foo_ = 3; };", |
887 | "var dis___ = IMPORTS___;" + |
888 | "___.f(function () {" + |
889 | " var foo_;" + |
890 | " foo_ = 3;" + |
891 | " });"); |
892 | } |
893 | |
894 | public final void testVarBadSuffixDeclaration() throws Exception { |
895 | checkFails( |
896 | "function foo__() { }", |
897 | "Variables cannot end in \"__\""); |
898 | checkFails( |
899 | "var foo__ = 3;", |
900 | "Variables cannot end in \"__\""); |
901 | checkFails( |
902 | "var foo__;", |
903 | "Variables cannot end in \"__\""); |
904 | checkFails( |
905 | "function() { function foo__() { } };", |
906 | "Variables cannot end in \"__\""); |
907 | checkFails( |
908 | "function() { var foo__ = 3; };", |
909 | "Variables cannot end in \"__\""); |
910 | checkFails( |
911 | "function() { var foo__; };", |
912 | "Variables cannot end in \"__\""); |
913 | } |
914 | |
915 | public final void testVarFuncFreeze() throws Exception { |
916 | // We can cajole and refer to a function |
917 | rewriteAndExecute( |
918 | "function foo() {}" + |
919 | "foo();"); |
920 | // We can assign a dotted property of a variable |
921 | rewriteAndExecute( |
922 | "var foo = {};" + |
923 | "foo.x = 3;" + |
924 | "assertEquals(foo.x, 3);"); |
925 | assertConsistent( |
926 | "function foo() {}" + |
927 | "var bar = foo;" + |
928 | "bar.x = 3;" + |
929 | "bar.x;"); |
930 | } |
931 | |
932 | public final void testReadBadSuffix() throws Exception { |
933 | checkFails( |
934 | "x.y__;", |
935 | "Properties cannot end in \"__\""); |
936 | } |
937 | |
938 | /** |
939 | * Tests assignment to unmaskable and maskable globals. |
940 | */ |
941 | public final void testSetBadFreeVariable() throws Exception { |
942 | // Array is in Scope.UNMASKABLE_IDENTIFIERS |
943 | checkAddsMessage( |
944 | js(fromString("Array = function () { return [] };")), |
945 | RewriterMessageType.CANNOT_MASK_IDENTIFIER); |
946 | // Throws a ReferenceError |
947 | rewriteAndExecute("assertThrows(function () { x = 1; })"); |
948 | } |
949 | |
950 | public final void testSetBadSuffix() throws Exception { |
951 | checkFails( |
952 | "x.y__ = z;", |
953 | "Properties cannot end in \"__\""); |
954 | } |
955 | |
956 | public final void testSetBadInitialize() throws Exception { |
957 | checkFails( |
958 | "var x__ = 3;", |
959 | "Variables cannot end in \"__\""); |
960 | } |
961 | |
962 | public final void testSetBadDeclare() throws Exception { |
963 | checkFails( |
964 | "var x__;", |
965 | "Variables cannot end in \"__\""); |
966 | } |
967 | |
968 | public final void testSetVar() throws Exception { |
969 | checkAddsMessage( |
970 | js(fromString("try {} catch (x__) { x__ = 3; }")), |
971 | RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE); |
972 | } |
973 | |
974 | public final void testSetReadModifyWriteLocalVar() throws Exception { |
975 | checkFails("x__ *= 2;", ""); |
976 | checkFails("x *= y__;", ""); |
977 | |
978 | assertConsistent("var x = 3; x *= 2;"); |
979 | assertConsistent("var x = 1; x += 7;"); |
980 | assertConsistent("var x = 1; x /= '2';"); |
981 | assertConsistent("var o = { x: 'a' }; o.x += 'b'; o;"); |
982 | |
983 | EnumSet<Operator> ops = EnumSet.of( |
984 | Operator.ASSIGN_MUL, |
985 | Operator.ASSIGN_DIV, |
986 | Operator.ASSIGN_MOD, |
987 | Operator.ASSIGN_SUM, |
988 | Operator.ASSIGN_SUB, |
989 | Operator.ASSIGN_LSH, |
990 | Operator.ASSIGN_RSH, |
991 | Operator.ASSIGN_USH, |
992 | Operator.ASSIGN_AND, |
993 | Operator.ASSIGN_XOR, |
994 | Operator.ASSIGN_OR |
995 | ); |
996 | for (Operator op : ops) { |
997 | assertConsistent("var x = 41, y = 0, g = [17]; x " + |
998 | op.getSymbol() + " g[y];"); |
999 | } |
1000 | } |
1001 | |
1002 | public final void testSetIncrDecr() throws Exception { |
1003 | checkFails("x__--;", ""); |
1004 | assertConsistent( |
1005 | "var x = 2;" + |
1006 | "var arr = [--x, x, x--, x, ++x, x, x++, x];" + |
1007 | "assertEquals('1,1,1,0,1,1,1,2', arr.join(','));" + |
1008 | "arr;"); |
1009 | assertConsistent( |
1010 | "var x = '2';" + |
1011 | "var arr = [--x, x, x--, x, ++x, x, x++, x];" + |
1012 | "assertEquals('1,1,1,0,1,1,1,2', arr.join(','));" + |
1013 | "arr;"); |
1014 | } |
1015 | |
1016 | public final void testSetIncrDecrOnLocals() throws Exception { |
1017 | checkFails("++x__;", ""); |
1018 | assertConsistent( |
1019 | "(function () {" + |
1020 | " var x = 2;" + |
1021 | " var arr = [--x, x, x--, x, ++x, x, x++, x];" + |
1022 | " assertEquals('1,1,1,0,1,1,1,2', arr.join(','));" + |
1023 | " return arr;" + |
1024 | "})();"); |
1025 | } |
1026 | |
1027 | public final void testSetIncrDecrOfComplexLValues() throws Exception { |
1028 | checkFails("arr[x__]--;", "Variables cannot end in \"__\""); |
1029 | checkFails("arr__[x]--;", "Variables cannot end in \"__\""); |
1030 | |
1031 | assertConsistent( |
1032 | "(function () {" + |
1033 | " var o = { x: 2 };" + |
1034 | " var arr = [--o.x, o.x, o.x--, o.x, ++o.x, o.x, o.x++, o.x];" + |
1035 | " assertEquals('1,1,1,0,1,1,1,2', arr.join(','));" + |
1036 | " return arr;" + |
1037 | "})();"); |
1038 | } |
1039 | |
1040 | public final void testSetIncrDecrOrderOfAssignment() throws Exception { |
1041 | assertConsistent( |
1042 | "(function () {" + |
1043 | " var arrs = [1, 2];" + |
1044 | " var j = 0;" + |
1045 | " arrs[++j] *= ++j;" + |
1046 | " assertEquals(2, j);" + |
1047 | " assertEquals(1, arrs[0]);" + |
1048 | " assertEquals(4, arrs[1]);" + |
1049 | " return arrs;" + |
1050 | "})();"); |
1051 | assertConsistent( |
1052 | "(function () {" + |
1053 | " var foo = (function () {" + |
1054 | " var k = 0;" + |
1055 | " return function () {" + |
1056 | " switch (k++) {" + |
1057 | " case 0: return [10, 20, 30];" + |
1058 | " case 1: return 1;" + |
1059 | " case 2: return 2;" + |
1060 | " default: throw new Error(k);" + |
1061 | " }" + |
1062 | " };" + |
1063 | " })();" + |
1064 | " return foo()[foo()] -= foo();" + |
1065 | "})();" |
1066 | ); |
1067 | } |
1068 | |
1069 | public final void testDeletePub() throws Exception { |
1070 | checkFails("delete x.foo___;", "Properties cannot end in \"__\""); |
1071 | assertConsistent( |
1072 | "(function() {" + |
1073 | " var o = { x: 3, y: 4 };" + // A JSON object. |
1074 | " function ptStr(o) { return '(' + o.x + ',' + o.y + ')'; }" + |
1075 | " var history = [ptStr(o)];" + // Record state before deletion. |
1076 | " delete o.y;" + // Delete |
1077 | " delete o.z;" + // Not present. Delete a no-op |
1078 | " history.push(ptStr(o));" + // Record state after deletion. |
1079 | " return history.toString();" + |
1080 | "})();"); |
1081 | assertConsistent( |
1082 | "var alert = 'a';" + |
1083 | "var o = { a: 1 };" + |
1084 | "delete o[alert];" + |
1085 | "assertEquals(undefined, o.a);" + |
1086 | "o;"); |
1087 | } |
1088 | |
1089 | public final void testDeleteFails() throws Exception { |
1090 | rewriteAndExecute( |
1091 | "assertThrows(function (){delete (function f(){}).name;});"); |
1092 | } |
1093 | |
1094 | public final void testDeleteNonLvalue() throws Exception { |
1095 | checkFails("delete 4;", "Invalid operand to delete"); |
1096 | } |
1097 | |
1098 | public final void testFuncAnonSimple() throws Exception { |
1099 | assertConsistent( |
1100 | "var foo = (function () {" + |
1101 | " function foo() {}" + |
1102 | " foo.x = 3;" + |
1103 | " return foo;" + |
1104 | " })();" + |
1105 | "foo();" + |
1106 | "foo.x;"); |
1107 | } |
1108 | |
1109 | public final void testFuncNamedSimpleDecl() throws Exception { |
1110 | rewriteAndExecute( |
1111 | "(function () {" + |
1112 | " function foo() {}" + |
1113 | " Object.freeze(foo);" + |
1114 | " foo();" + |
1115 | " try {" + |
1116 | " foo.x = 3;" + |
1117 | " } catch (e) { return; }" + |
1118 | " fail('mutated frozen function');" + |
1119 | "})();"); |
1120 | assertConsistent( |
1121 | "function foo() {}" + |
1122 | "foo.x = 3;" + |
1123 | "foo();" + |
1124 | "foo.x;"); |
1125 | rewriteAndExecute( |
1126 | " function f_() { return 31415; }" |
1127 | + "var x = f_();" |
1128 | + "assertEquals(x, 31415);"); |
1129 | } |
1130 | |
1131 | public final void testMapSingle() throws Exception { |
1132 | checkFails("var o = { x___: p.x, k1: p.y };", |
1133 | "Properties cannot end in \"__\""); |
1134 | } |
1135 | |
1136 | public final void testInstanceof() throws Exception { |
1137 | assertConsistent("[ (({}) instanceof Object)," + |
1138 | " ((new Date) instanceof Date)," + |
1139 | " (({}) instanceof Date)" + |
1140 | "];"); |
1141 | assertConsistent("function foo() {} (new foo) instanceof foo;"); |
1142 | assertConsistent("function foo() {} !(({}) instanceof foo);"); |
1143 | } |
1144 | |
1145 | public final void testTypeof() throws Exception { |
1146 | rewriteAndExecute("assertThrows(function () { return typeof ___; })"); |
1147 | assertConsistent("typeof true;"); |
1148 | assertConsistent("typeof 0;"); |
1149 | assertConsistent("typeof undefined;"); |
1150 | assertConsistent("typeof null;"); |
1151 | assertConsistent("typeof 'string';"); |
1152 | assertConsistent("typeof function () {};"); |
1153 | assertConsistent("typeof ({});"); |
1154 | } |
1155 | |
1156 | public final void testMaskingFunction() throws Exception { |
1157 | assertAddsMessage( |
1158 | "function Goo() { function Goo() {} }", |
1159 | MessageType.SYMBOL_REDEFINED, |
1160 | MessageLevel.ERROR ); |
1161 | assertAddsMessage( |
1162 | "function Goo() { var Goo = 1; }", |
1163 | MessageType.MASKING_SYMBOL, |
1164 | MessageLevel.LINT ); |
1165 | assertMessageNotPresent( |
1166 | "function Goo() { this.x = 1; }", |
1167 | MessageType.MASKING_SYMBOL ); |
1168 | } |
1169 | |
1170 | public final void testMapBadKeySuffix() throws Exception { |
1171 | checkAddsMessage( |
1172 | js(fromString("var o = { x__: 3 };")), |
1173 | RewriterMessageType.PROPERTIES_CANNOT_END_IN_DOUBLE_UNDERSCORE); |
1174 | } |
1175 | |
1176 | public final void testMapNonEmpty() throws Exception { |
1177 | // Ensure that calling an untamed function throws |
1178 | rewriteAndExecute( |
1179 | "testImports.f = function() {};", |
1180 | "assertThrows(function() { f(); });", |
1181 | ";"); |
1182 | // Ensure that calling a tamed function in an object literal works |
1183 | rewriteAndExecute( |
1184 | " var f = function() {};" |
1185 | + "var m = { f : f };" |
1186 | + "m.f();"); |
1187 | // Ensure that putting an untamed function into an object literal |
1188 | // causes an exception. |
1189 | rewriteAndExecute( |
1190 | "testImports.f = function() {};", |
1191 | "assertThrows(function(){({ isPrototypeOf : f });});", |
1192 | ";"); |
1193 | } |
1194 | |
1195 | public final void testLabeledStatement() throws Exception { |
1196 | checkFails("IMPORTS___: 1;", "Labels cannot end in \"__\""); |
1197 | checkFails("IMPORTS___: while (1);", "Labels cannot end in \"__\""); |
1198 | checkFails("while (1) { break x__; }", "Labels cannot end in \"__\""); |
1199 | checkFails("while (1) { continue x__; }", "Labels cannot end in \"__\""); |
1200 | assertConsistent( |
1201 | "var k = 0;" + |
1202 | "a: for (var i = 0; i < 10; ++i) {" + |
1203 | " b: for (var j = 0; j < 10; ++j) {" + |
1204 | " if (++k > 5) break a;" + |
1205 | " }" + |
1206 | "}" + |
1207 | "k;"); |
1208 | assertConsistent( |
1209 | "var k = 0;" + |
1210 | "a: for (var i = 0; i < 10; ++i) {" + |
1211 | " b: for (var j = 0; j < 10; ++j) {" + |
1212 | " if (++k > 5) break b;" + |
1213 | " }" + |
1214 | "}" + |
1215 | "k;"); |
1216 | } |
1217 | |
1218 | /** |
1219 | * Tests that the container can get access to |
1220 | * "virtual globals" defined in cajoled code. |
1221 | */ |
1222 | public final void testWrapperAccess() throws Exception { |
1223 | rewriteAndExecute( |
1224 | "", |
1225 | "var x = 'test';", |
1226 | "if (___.getNewModuleHandler().getImports().x != 'test') {" + |
1227 | "fail('Cannot see inside the wrapper');" + |
1228 | "}"); |
1229 | } |
1230 | |
1231 | /** |
1232 | * Tests that Object.prototype cannot be modified. |
1233 | */ |
1234 | public final void testFrozenObjectPrototype() throws Exception { |
1235 | rewriteAndExecute( |
1236 | "var success = false;" + |
1237 | "try {" + |
1238 | "Object.prototype.x = 'X';" + |
1239 | "} catch (e){" + |
1240 | "success = true;" + |
1241 | "}" + |
1242 | "if (!success) { fail('Object.prototype not frozen.'); }"); |
1243 | } |
1244 | |
1245 | public final void testStamp() throws Exception { |
1246 | rewriteAndExecute( |
1247 | "function Foo(){}" + |
1248 | "var foo = new Foo();" + |
1249 | "Object.freeze(foo);" + |
1250 | "var TestMark = cajaVM.Trademark('Test');" + |
1251 | "var passed = false;" + |
1252 | "try { " + |
1253 | " cajaVM.stamp([TestMark.stamp], foo);" + |
1254 | "} catch (e) {" + |
1255 | " if (e.message !== " + |
1256 | " 'Can\\'t stamp frozen objects: [object Object]') {" + |
1257 | " fail(e.message);" + |
1258 | " }" + |
1259 | " passed = true;" + |
1260 | "}" + |
1261 | "if (!passed) { fail ('Able to stamp frozen objects.'); }"); |
1262 | rewriteAndExecute( |
1263 | // Shows how privileged or uncajoled code can stamp |
1264 | // frozen objects anyway. |
1265 | "___.getNewModuleHandler()." + |
1266 | " getImports().DefineOwnProperty___('stampAnyway', {" + |
1267 | " value: ___.markFuncFreeze(function(stamp, obj) {" + |
1268 | " stamp.mark___(obj);" + |
1269 | " })," + |
1270 | " enumerable: false," + |
1271 | " writable: true," + |
1272 | " configurable: false" + |
1273 | " });", |
1274 | "function Foo(){}" + |
1275 | "var foo = new Foo();" + |
1276 | "Object.freeze(foo);" + |
1277 | "var TestMark = cajaVM.Trademark('Test');" + |
1278 | "try { " + |
1279 | " stampAnyway(TestMark.stamp, foo);" + |
1280 | "} catch (e) {" + |
1281 | " fail(e.message);" + |
1282 | "}" + |
1283 | "cajaVM.guard(TestMark.guard, foo);", |
1284 | ""); |
1285 | rewriteAndExecute( |
1286 | "var foo = {};" + |
1287 | "var TestMark = cajaVM.Trademark('Test');" + |
1288 | "cajaVM.stamp([TestMark.stamp], foo);" + |
1289 | "cajaVM.guard(TestMark.guard, foo);"); |
1290 | rewriteAndExecute( |
1291 | "var foo = {};" + |
1292 | "var TestMark = cajaVM.Trademark('Test');" + |
1293 | "cajaVM.stamp([TestMark.stamp], foo);" + |
1294 | "TestMark.guard.coerce(foo);"); |
1295 | rewriteAndExecute( |
1296 | "var foo = {};" + |
1297 | "var TestMark = cajaVM.Trademark('Test');" + |
1298 | "var passed = false;" + |
1299 | "try { " + |
1300 | " cajaVM.guard(TestMark.guard, foo);" + |
1301 | "} catch (e) {" + |
1302 | " if (e.message !== " + |
1303 | " 'Specimen does not have the \"Test\" trademark') {" + |
1304 | " fail(e.message);" + |
1305 | " }" + |
1306 | " passed = true;" + |
1307 | "}" + |
1308 | "if (!passed) { fail ('Able to forge trademarks.'); }"); |
1309 | rewriteAndExecute( |
1310 | "var foo = {};" + |
1311 | "var T1Mark = cajaVM.Trademark('T1');" + |
1312 | "var T2Mark = cajaVM.Trademark('T2');" + |
1313 | "var passed = false;" + |
1314 | "try { " + |
1315 | " cajaVM.stamp([T1Mark.stamp], foo);" + |
1316 | " cajaVM.guard(T2Mark.guard, foo);" + |
1317 | "} catch (e) {" + |
1318 | " if (e.message !== 'Specimen does not have the \"T2\" trademark') {" + |
1319 | " fail(e.message);" + |
1320 | " }" + |
1321 | " passed = true;" + |
1322 | "}" + |
1323 | "if (!passed) { fail ('Able to forge trademarks.'); }"); |
1324 | rewriteAndExecute( |
1325 | "var foo = {};" + |
1326 | "var bar = Object.create(foo);" + |
1327 | "var baz = Object.create(bar);" + |
1328 | "var TestMark = cajaVM.Trademark('Test');" + |
1329 | "cajaVM.stamp([TestMark.stamp], bar);" + |
1330 | "assertFalse(cajaVM.passesGuard(TestMark.guard, foo));" + |
1331 | "assertTrue(cajaVM.passesGuard(TestMark.guard, bar));" + |
1332 | "assertFalse(cajaVM.passesGuard(TestMark.guard, baz));"); |
1333 | } |
1334 | |
1335 | public final void testIndexOf() throws Exception { |
1336 | assertConsistent("''.indexOf('1');"); |
1337 | } |
1338 | |
1339 | public final void testCallback() throws Exception { |
1340 | assertConsistent( |
1341 | "(function(){}).apply.call(function(a, b) {return a + b;}, {}, [3, 4]);" |
1342 | ); |
1343 | assertConsistent( |
1344 | "(function(){}).call.call(function(a, b) {return a + b;}, {}, 3, 4);"); |
1345 | rewriteAndExecute( |
1346 | "var a = [], b = {x:3};\n" + |
1347 | "for (var i in b) { a.push(i, b[i]); };" + |
1348 | "assertEquals(a.toString(), 'x,3');"); |
1349 | assertConsistent( |
1350 | "Function.prototype.apply.call(" + |
1351 | " function(a, b) {" + |
1352 | " return a + b;" + |
1353 | " }, " + |
1354 | " {}, " + |
1355 | " [3, 4]);"); |
1356 | assertConsistent( |
1357 | "Function.prototype.call.call(" + |
1358 | " function(a, b) {" + |
1359 | " return a + b;" + |
1360 | " }," + |
1361 | " {}," + |
1362 | " 3," + |
1363 | " 4);"); |
1364 | assertConsistent( |
1365 | "Function.prototype.bind.call(" + |
1366 | " function(a, b) {" + |
1367 | " return a + b;" + |
1368 | " }," + |
1369 | " {}," + |
1370 | " 3)(4);"); |
1371 | } |
1372 | |
1373 | /** |
1374 | * Tests the cajaVM.newTable(opt_useKeyLifetime) abstraction. |
1375 | * <p> |
1376 | * From here, we are not in a position to test the weak-GC properties this |
1377 | * abstraction is designed to provide, nor its O(1) complexity measure. |
1378 | * However, we can test that it works as a simple lookup table. |
1379 | */ |
1380 | public final void testTable() throws Exception { |
1381 | rewriteAndExecute( |
1382 | "var t = cajaVM.newTable();" + |
1383 | "var k1 = {};" + |
1384 | "var k2 = {};" + |
1385 | "var k3 = {};" + |
1386 | "t.set(k1, 'v1');" + |
1387 | "t.set(k2, 'v2');" + |
1388 | "assertEquals(t.get(k1), 'v1');" + |
1389 | "assertEquals(t.get(k2), 'v2');" + |
1390 | "assertTrue(t.get(k3) === void 0);"); |
1391 | rewriteAndExecute( |
1392 | "var t = cajaVM.newTable(true);" + |
1393 | "var k1 = {};" + |
1394 | "var k2 = {};" + |
1395 | "var k3 = {};" + |
1396 | "t.set(k1, 'v1');" + |
1397 | "t.set(k2, 'v2');" + |
1398 | "assertEquals(t.get(k1), 'v1');" + |
1399 | "assertEquals(t.get(k2), 'v2');" + |
1400 | "assertTrue(t.get(k3) === void 0);"); |
1401 | rewriteAndExecute( |
1402 | "var t = cajaVM.newTable();" + |
1403 | "t.set('foo', 'v1');" + |
1404 | "t.set(null, 'v2');" + |
1405 | "assertEquals(t.get('foo'), 'v1');" + |
1406 | "assertEquals(t.get(null), 'v2');" + |
1407 | "assertTrue(t.get({toString: function(){return 'foo';}}) === void 0);"); |
1408 | rewriteAndExecute( |
1409 | "var t = cajaVM.newTable(true);" + |
1410 | "assertThrows(function(){t.set('foo', 'v1');});"); |
1411 | rewriteAndExecute( |
1412 | "var t = cajaVM.newTable(true);" + |
1413 | "var k1 = {};" + |
1414 | "var k2 = Object.create(k1);" + |
1415 | "var k3 = Object.create(k2);" + |
1416 | "var k4 = Object.create(k3);" + |
1417 | "t.set(k2, 'foo');" + |
1418 | "t.set(k3, 'bar');" + |
1419 | "assertEquals(t.get(k2), 'foo');\n" + |
1420 | "assertEquals(t.get(k3), 'bar');\n" + |
1421 | "assertTrue(t.get(k1) === void 0);\n" + |
1422 | "assertTrue(t.get(k4) === void 0);"); |
1423 | rewriteAndExecute( |
1424 | "var t = cajaVM.newTable();" + |
1425 | "var k1 = {};" + |
1426 | "var k2 = Object.create(k1);" + |
1427 | "var k3 = Object.create(k2);" + |
1428 | "var k4 = Object.create(k3);" + |
1429 | "t.set(k2, 'foo');" + |
1430 | "t.set(k3, 'bar');" + |
1431 | "assertEquals(t.get(k2), 'foo');\n" + |
1432 | "assertEquals(t.get(k3), 'bar');\n" + |
1433 | "assertTrue(t.get(k1) === void 0);\n" + |
1434 | "assertTrue(t.get(k4) === void 0);"); |
1435 | rewriteAndExecute( |
1436 | "var t1 = cajaVM.newTable(true);" + |
1437 | "var t2 = cajaVM.newTable(true);" + |
1438 | "var k = {};" + |
1439 | "t1.set(k, 'foo');" + |
1440 | "t2.set(k, 'bar');" + |
1441 | "assertEquals(t1.get(k), 'foo');" + |
1442 | "assertEquals(t2.get(k), 'bar');" + |
1443 | "t1.set(k, void 0);" + |
1444 | "assertTrue(t1.get(k) === void 0);" + |
1445 | "assertEquals(t2.get(k), 'bar');"); |
1446 | rewriteAndExecute( |
1447 | "var t1 = cajaVM.newTable();" + |
1448 | "var t2 = cajaVM.newTable();" + |
1449 | "var k = {};" + |
1450 | "t1.set(k, 'foo');" + |
1451 | "t2.set(k, 'bar');" + |
1452 | "assertEquals(t1.get(k), 'foo');" + |
1453 | "assertEquals(t2.get(k), 'bar');" + |
1454 | "t1.set(k, void 0);" + |
1455 | "assertTrue(t1.get(k) === void 0);" + |
1456 | "assertEquals(t2.get(k), 'bar');"); |
1457 | } |
1458 | |
1459 | /** |
1460 | * Tests that begetting works. |
1461 | */ |
1462 | public final void testInheritance() throws Exception { |
1463 | rewriteAndExecute( |
1464 | "var x = {a:8}, y = Object.create(x); assertTrue(y.a === 8);"); |
1465 | } |
1466 | |
1467 | /** |
1468 | * Tests that ES5/3 code can't cause a privilege |
1469 | * escalation by calling a tamed exophoric function with null as the |
1470 | * this-value. |
1471 | * <p> |
1472 | * The uncajoled branch of the tests below establish that a null does cause |
1473 | * a privilege escalation for normal non-strict JavaScript. |
1474 | */ |
1475 | public final void testNoPrivilegeEscalation() throws Exception { |
1476 | rewriteAndExecute("assertTrue([].valueOf.call(null) === cajaVM.USELESS);"); |
1477 | rewriteAndExecute("assertTrue([].valueOf.apply(null) === cajaVM.USELESS);"); |
1478 | rewriteAndExecute( |
1479 | "assertTrue([].valueOf.bind(null)() === cajaVM.USELESS);"); |
1480 | } |
1481 | |
1482 | /** |
1483 | * Tests that the apparent [[Class]] of the tamed JSON object is 'JSON', as |
1484 | * it should be according to ES5. Also tests parse and stringify. |
1485 | * |
1486 | * See issue 1086 |
1487 | */ |
1488 | public final void testJSONClass() throws Exception { |
1489 | rewriteAndExecute("assertTrue(''+JSON === '[object JSON]');"); |
1490 | rewriteAndExecute( |
1491 | "assertTrue(({}).toString.call(JSON) === '[object JSON]');"); |
1492 | rewriteAndExecute( |
1493 | "var x = JSON.parse('{\"a\":[{\"b\":33}]}');" + |
1494 | "assertEquals(33, x.a[0].b);"); |
1495 | rewriteAndExecute( |
1496 | "var x = JSON.stringify({a:33});" + |
1497 | "assertEquals('{\"a\":33}', x);"); |
1498 | rewriteAndExecute( |
1499 | "var pass = false;" + |
1500 | "try { var x = JSON.parse('{\"b\":1, \"a___\":33}'); }" + |
1501 | "catch (e) { " + |
1502 | " assertTrue(e.message.indexOf('a___') !== -1);" + |
1503 | " pass = true;" + |
1504 | "}" + |
1505 | "assertTrue(pass);"); |
1506 | } |
1507 | |
1508 | /** |
1509 | * Tests that an inherited <tt>*_w___</tt> flag does not enable |
1510 | * bogus writability. |
1511 | * <p> |
1512 | * See <a href="http://code.google.com/p/google-caja/issues/detail?id=1052" |
1513 | * >issue 1052</a>. |
1514 | */ |
1515 | public final void testNoCanSetInheritance() throws Exception { |
1516 | rewriteAndExecute( |
1517 | "(function() {" + |
1518 | " var a = {};" + |
1519 | " var b = Object.freeze(Object.create(a));" + |
1520 | " a.x = 8;" + |
1521 | " assertThrows(function(){b.x = 9;});" + |
1522 | " assertEquals(b.x, 8);" + |
1523 | "})();"); |
1524 | } |
1525 | |
1526 | @Override |
1527 | public void setUp() throws Exception { |
1528 | super.setUp(); |
1529 | es53Rewriter = new ES53Rewriter(TestBuildInfo.getInstance(), mq, false); |
1530 | setRewriter(es53Rewriter); |
1531 | } |
1532 | |
1533 | @Override |
1534 | protected Object executePlain(String caja) throws IOException { |
1535 | mq.getMessages().clear(); |
1536 | return RhinoTestBed.runJs( |
1537 | new Executor.Input( |
1538 | getClass(), "../../../../../js/json_sans_eval/json_sans_eval.js"), |
1539 | new Executor.Input(getClass(), "/com/google/caja/es53.js"), |
1540 | new Executor.Input( |
1541 | getClass(), "../../../../../js/jsunit/2.2/jsUnitCore.js"), |
1542 | new Executor.Input(caja, getName() + "-uncajoled")); |
1543 | } |
1544 | |
1545 | @Override |
1546 | protected Object rewriteAndExecute(String pre, String caja, String post) |
1547 | throws IOException, ParseException { |
1548 | mq.getMessages().clear(); |
1549 | |
1550 | List<Statement> children = Lists.newArrayList(); |
1551 | children.add(js(fromString(caja, is))); |
1552 | String cajoledJs = render(rewriteTopLevelNode( |
1553 | new UncajoledModule(new Block(FilePosition.UNKNOWN, children)))); |
1554 | |
1555 | assertNoErrors(); |
1556 | |
1557 | final String[] assertFunctions = new String[] { |
1558 | "fail", |
1559 | "assertEquals", |
1560 | "assertTrue", |
1561 | "assertFalse", |
1562 | "assertLessThan", |
1563 | "assertNull", |
1564 | "assertThrows", |
1565 | }; |
1566 | |
1567 | StringBuilder importsSetup = new StringBuilder(); |
1568 | importsSetup.append( |
1569 | "var testImports = ___.copy(___.whitelistAll(___.sharedImports));"); |
1570 | for (String f : assertFunctions) { |
1571 | importsSetup |
1572 | .append("testImports." + f + " = ___.markFuncFreeze(" + f + ");") |
1573 | .append("___.grantRead(testImports, '" + f + "');"); |
1574 | } |
1575 | importsSetup.append( |
1576 | "___.getNewModuleHandler().setImports(___.whitelistAll(testImports));"); |
1577 | |
1578 | Object result = RhinoTestBed.runJs( |
1579 | new Executor.Input( |
1580 | getClass(), "/com/google/caja/plugin/console-stubs.js"), |
1581 | new Executor.Input( |
1582 | getClass(), "../../../../../js/json_sans_eval/json_sans_eval.js"), |
1583 | new Executor.Input(getClass(), "/com/google/caja/es53.js"), |
1584 | new Executor.Input( |
1585 | getClass(), "../../../../../js/jsunit/2.2/jsUnitCore.js"), |
1586 | new Executor.Input( |
1587 | getClass(), "/com/google/caja/log-to-console.js"), |
1588 | new Executor.Input( |
1589 | importsSetup.toString(), |
1590 | getName() + "-test-fixture"), |
1591 | new Executor.Input(pre, getName()), |
1592 | // Load the cajoled code. |
1593 | new Executor.Input(cajoledJs, getName() + "-cajoled"), |
1594 | new Executor.Input(post, getName()), |
1595 | // Return the output field as the value of the run. |
1596 | new Executor.Input( |
1597 | "___.getNewModuleHandler().getLastValue();", getName())); |
1598 | |
1599 | assertNoErrors(); |
1600 | return result; |
1601 | } |
1602 | } |