1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sw=4 et tw=99:
3 : *
4 : * ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is JSAPI tests.
18 : *
19 : * The Initial Developer of the Original Code is
20 : * Mozilla Corporation.
21 : * Portions created by the Initial Developer are Copyright (C) 2009
22 : * the Initial Developer. All Rights Reserved.
23 : *
24 : * Contributor(s):
25 : * Jason Orendorff
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either of the GNU General Public License Version 2 or later (the "GPL"),
29 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include "mozilla/Util.h"
42 :
43 : #include "jsapi.h"
44 : #include "jsprvtd.h"
45 : #include "jsalloc.h"
46 :
47 : #include "js/Vector.h"
48 :
49 : #include <errno.h>
50 : #include <string.h>
51 : #include <stdio.h>
52 : #include <stdlib.h>
53 :
54 : class jsvalRoot
55 : {
56 : public:
57 171 : explicit jsvalRoot(JSContext *context, jsval value = JSVAL_NULL)
58 171 : : cx(context), v(value)
59 : {
60 171 : if (!JS_AddValueRoot(cx, &v)) {
61 0 : fprintf(stderr, "Out of memory in jsvalRoot constructor, aborting\n");
62 0 : abort();
63 : }
64 171 : }
65 :
66 171 : ~jsvalRoot() { JS_RemoveValueRoot(cx, &v); }
67 :
68 227 : operator jsval() const { return value(); }
69 :
70 108 : jsvalRoot & operator=(jsval value) {
71 108 : v = value;
72 108 : return *this;
73 : }
74 :
75 75 : jsval * addr() { return &v; }
76 248 : jsval value() const { return v; }
77 :
78 : private:
79 : JSContext *cx;
80 : jsval v;
81 : };
82 :
83 : /* Note: Aborts on OOM. */
84 95 : class JSAPITestString {
85 : js::Vector<char, 0, js::SystemAllocPolicy> chars;
86 : public:
87 95 : JSAPITestString() {}
88 0 : JSAPITestString(const char *s) { *this += s; }
89 0 : JSAPITestString(const JSAPITestString &s) { *this += s; }
90 :
91 0 : const char *begin() const { return chars.begin(); }
92 : const char *end() const { return chars.end(); }
93 0 : size_t length() const { return chars.length(); }
94 :
95 0 : JSAPITestString & operator +=(const char *s) {
96 0 : if (!chars.append(s, strlen(s)))
97 0 : abort();
98 0 : return *this;
99 : }
100 :
101 0 : JSAPITestString & operator +=(const JSAPITestString &s) {
102 0 : if (!chars.append(s.begin(), s.length()))
103 0 : abort();
104 0 : return *this;
105 : }
106 : };
107 :
108 0 : inline JSAPITestString operator+(JSAPITestString a, const char *b) { return a += b; }
109 0 : inline JSAPITestString operator+(JSAPITestString a, const JSAPITestString &b) { return a += b; }
110 :
111 : class JSAPITest
112 : {
113 : public:
114 : static JSAPITest *list;
115 : JSAPITest *next;
116 :
117 : JSRuntime *rt;
118 : JSContext *cx;
119 : JSObject *global;
120 : bool knownFail;
121 : JSAPITestString msgs;
122 : JSCrossCompartmentCall *call;
123 :
124 95 : JSAPITest() : rt(NULL), cx(NULL), global(NULL), knownFail(false), call(NULL) {
125 95 : next = list;
126 95 : list = this;
127 95 : }
128 :
129 95 : virtual ~JSAPITest() { uninit(); }
130 :
131 95 : virtual bool init() {
132 95 : rt = createRuntime();
133 95 : if (!rt)
134 0 : return false;
135 95 : cx = createContext();
136 95 : if (!cx)
137 0 : return false;
138 95 : JS_BeginRequest(cx);
139 95 : global = createGlobal();
140 95 : if (!global)
141 0 : return false;
142 95 : call = JS_EnterCrossCompartmentCall(cx, global);
143 95 : return call != NULL;
144 : }
145 :
146 190 : virtual void uninit() {
147 190 : if (call) {
148 95 : JS_LeaveCrossCompartmentCall(call);
149 95 : call = NULL;
150 : }
151 190 : if (cx) {
152 95 : JS_EndRequest(cx);
153 95 : JS_DestroyContext(cx);
154 95 : cx = NULL;
155 : }
156 190 : if (rt) {
157 95 : destroyRuntime();
158 95 : rt = NULL;
159 : }
160 190 : }
161 :
162 : virtual const char * name() = 0;
163 : virtual bool run() = 0;
164 :
165 : #define EXEC(s) do { if (!exec(s, __FILE__, __LINE__)) return false; } while (false)
166 :
167 27 : bool exec(const char *bytes, const char *filename, int lineno) {
168 54 : jsvalRoot v(cx);
169 27 : return JS_EvaluateScript(cx, global, bytes, strlen(bytes), filename, lineno, v.addr()) ||
170 54 : fail(bytes, filename, lineno);
171 : }
172 :
173 : #define EVAL(s, vp) do { if (!evaluate(s, __FILE__, __LINE__, vp)) return false; } while (false)
174 :
175 129 : bool evaluate(const char *bytes, const char *filename, int lineno, jsval *vp) {
176 129 : return JS_EvaluateScript(cx, global, bytes, strlen(bytes), filename, lineno, vp) ||
177 258 : fail(bytes, filename, lineno);
178 : }
179 :
180 0 : JSAPITestString jsvalToSource(jsval v) {
181 0 : JSString *str = JS_ValueToSource(cx, v);
182 0 : if (str) {
183 0 : JSAutoByteString bytes(cx, str);
184 0 : if (!!bytes)
185 0 : return JSAPITestString(bytes.ptr());
186 : }
187 0 : JS_ClearPendingException(cx);
188 0 : return JSAPITestString("<<error converting value to string>>");
189 : }
190 :
191 0 : JSAPITestString toSource(long v) {
192 : char buf[40];
193 0 : sprintf(buf, "%ld", v);
194 0 : return JSAPITestString(buf);
195 : }
196 :
197 0 : JSAPITestString toSource(unsigned long v) {
198 : char buf[40];
199 0 : sprintf(buf, "%lu", v);
200 0 : return JSAPITestString(buf);
201 : }
202 :
203 : JSAPITestString toSource(long long v) {
204 : char buf[40];
205 : sprintf(buf, "%lld", v);
206 : return JSAPITestString(buf);
207 : }
208 :
209 : JSAPITestString toSource(unsigned long long v) {
210 : char buf[40];
211 : sprintf(buf, "%llu", v);
212 : return JSAPITestString(buf);
213 : }
214 :
215 0 : JSAPITestString toSource(unsigned int v) {
216 0 : return toSource((unsigned long)v);
217 : }
218 :
219 0 : JSAPITestString toSource(int v) {
220 0 : return toSource((long)v);
221 : }
222 :
223 0 : JSAPITestString toSource(bool v) {
224 0 : return JSAPITestString(v ? "true" : "false");
225 : }
226 :
227 0 : JSAPITestString toSource(JSAtom *v) {
228 0 : return jsvalToSource(STRING_TO_JSVAL((JSString*)v));
229 : }
230 :
231 0 : JSAPITestString toSource(JSVersion v) {
232 0 : return JSAPITestString(JS_VersionToString(v));
233 : }
234 :
235 : template<typename T>
236 47 : bool checkEqual(const T &actual, const T &expected,
237 : const char *actualExpr, const char *expectedExpr,
238 : const char *filename, int lineno) {
239 : return (actual == expected) ||
240 : fail(JSAPITestString("CHECK_EQUAL failed: expected (") +
241 : expectedExpr + ") = " + toSource(expected) +
242 47 : ", got (" + actualExpr + ") = " + toSource(actual), filename, lineno);
243 : }
244 :
245 : // There are many cases where the static types of 'actual' and 'expected'
246 : // are not identical, and C++ is understandably cautious about automatic
247 : // coercions. So catch those cases and forcibly coerce, then use the
248 : // identical-type specialization. This may do bad things if the types are
249 : // actually *not* compatible.
250 : template<typename T, typename U>
251 25 : bool checkEqual(const T &actual, const U &expected,
252 : const char *actualExpr, const char *expectedExpr,
253 : const char *filename, int lineno) {
254 25 : return checkEqual(U(actual), expected, actualExpr, expectedExpr, filename, lineno);
255 : }
256 :
257 : #define CHECK_EQUAL(actual, expected) \
258 : do { \
259 : if (!checkEqual(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
260 : return false; \
261 : } while (false)
262 :
263 1531 : bool checkSame(jsval actual, jsval expected,
264 : const char *actualExpr, const char *expectedExpr,
265 : const char *filename, int lineno) {
266 : JSBool same;
267 1531 : return (JS_SameValue(cx, actual, expected, &same) && same) ||
268 0 : fail(JSAPITestString("CHECK_SAME failed: expected JS_SameValue(cx, ") +
269 0 : actualExpr + ", " + expectedExpr + "), got !JS_SameValue(cx, " +
270 10717 : jsvalToSource(actual) + ", " + jsvalToSource(expected) + ")", filename, lineno);
271 : }
272 :
273 : #define CHECK_SAME(actual, expected) \
274 : do { \
275 : if (!checkSame(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
276 : return false; \
277 : } while (false)
278 :
279 : #define CHECK(expr) \
280 : do { \
281 : if (!(expr)) \
282 : return fail("CHECK failed: " #expr, __FILE__, __LINE__); \
283 : } while (false)
284 :
285 0 : bool fail(JSAPITestString msg = JSAPITestString(), const char *filename = "-", int lineno = 0) {
286 0 : if (JS_IsExceptionPending(cx)) {
287 0 : jsvalRoot v(cx);
288 0 : JS_GetPendingException(cx, v.addr());
289 0 : JS_ClearPendingException(cx);
290 0 : JSString *s = JS_ValueToString(cx, v);
291 0 : if (s) {
292 0 : JSAutoByteString bytes(cx, s);
293 0 : if (!!bytes)
294 0 : msg += bytes.ptr();
295 : }
296 : }
297 0 : fprintf(stderr, "%s:%d:%.*s\n", filename, lineno, (int) msg.length(), msg.begin());
298 0 : msgs += msg;
299 0 : return false;
300 : }
301 :
302 0 : JSAPITestString messages() const { return msgs; }
303 :
304 105 : static JSClass * basicGlobalClass() {
305 : static JSClass c = {
306 : "global", JSCLASS_GLOBAL_FLAGS,
307 : JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
308 : JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
309 : };
310 105 : return &c;
311 : }
312 :
313 : protected:
314 : static JSBool
315 : print(JSContext *cx, unsigned argc, jsval *vp)
316 : {
317 : jsval *argv = JS_ARGV(cx, vp);
318 : for (unsigned i = 0; i < argc; i++) {
319 : JSString *str = JS_ValueToString(cx, argv[i]);
320 : if (!str)
321 : return JS_FALSE;
322 : char *bytes = JS_EncodeString(cx, str);
323 : if (!bytes)
324 : return JS_FALSE;
325 : printf("%s%s", i ? " " : "", bytes);
326 : JS_free(cx, bytes);
327 : }
328 :
329 : putchar('\n');
330 : fflush(stdout);
331 : JS_SET_RVAL(cx, vp, JSVAL_VOID);
332 : return JS_TRUE;
333 : }
334 :
335 : bool definePrint() {
336 : return JS_DefineFunction(cx, global, "print", (JSNative) print, 0, 0);
337 : }
338 :
339 93 : virtual JSRuntime * createRuntime() {
340 93 : JSRuntime *rt = JS_NewRuntime(8L * 1024 * 1024);
341 93 : if (!rt)
342 0 : return NULL;
343 :
344 : const size_t MAX_STACK_SIZE =
345 : /* Assume we can't use more than 5e5 bytes of C stack by default. */
346 : #if (defined(DEBUG) && defined(__SUNPRO_CC)) || defined(JS_CPU_SPARC)
347 : /*
348 : * Sun compiler uses a larger stack space for js::Interpret() with
349 : * debug. Use a bigger gMaxStackSize to make "make check" happy.
350 : */
351 : 5000000
352 : #else
353 93 : 500000
354 : #endif
355 : ;
356 :
357 93 : JS_SetNativeStackQuota(rt, MAX_STACK_SIZE);
358 93 : return rt;
359 : }
360 :
361 94 : virtual void destroyRuntime() {
362 94 : JS_ASSERT(!cx);
363 94 : JS_ASSERT(rt);
364 94 : JS_DestroyRuntime(rt);
365 94 : }
366 :
367 0 : static void reportError(JSContext *cx, const char *message, JSErrorReport *report) {
368 : fprintf(stderr, "%s:%u:%s\n",
369 : report->filename ? report->filename : "<no filename>",
370 : (unsigned int) report->lineno,
371 0 : message);
372 0 : }
373 :
374 95 : virtual JSContext * createContext() {
375 95 : JSContext *cx = JS_NewContext(rt, 8192);
376 95 : if (!cx)
377 0 : return NULL;
378 95 : JS_SetOptions(cx, JSOPTION_VAROBJFIX);
379 95 : JS_SetVersion(cx, JSVERSION_LATEST);
380 95 : JS_SetErrorReporter(cx, &reportError);
381 95 : return cx;
382 : }
383 :
384 105 : virtual JSClass * getGlobalClass() {
385 105 : return basicGlobalClass();
386 : }
387 :
388 99 : virtual JSObject * createGlobal(JSPrincipals *principals = NULL) {
389 : /* Create the global object. */
390 99 : JSObject *global = JS_NewCompartmentAndGlobalObject(cx, getGlobalClass(), principals);
391 99 : if (!global)
392 0 : return NULL;
393 :
394 198 : JSAutoEnterCompartment ac;
395 99 : if (!ac.enter(cx, global))
396 0 : return NULL;
397 :
398 : /* Populate the global object with the standard globals,
399 : like Object and Array. */
400 99 : if (!JS_InitStandardClasses(cx, global))
401 0 : return NULL;
402 99 : return global;
403 : }
404 : };
405 :
406 : #define BEGIN_TEST(testname) \
407 : class cls_##testname : public JSAPITest { \
408 : public: \
409 : virtual const char * name() { return #testname; } \
410 : virtual bool run()
411 :
412 : #define END_TEST(testname) \
413 : }; \
414 : static cls_##testname cls_##testname##_instance;
415 :
416 : /*
417 : * A "fixture" is a subclass of JSAPITest that holds common definitions for a
418 : * set of tests. Each test that wants to use the fixture should use
419 : * BEGIN_FIXTURE_TEST and END_FIXTURE_TEST, just as one would use BEGIN_TEST and
420 : * END_TEST, but include the fixture class as the first argument. The fixture
421 : * class's declarations are then in scope for the test bodies.
422 : */
423 :
424 : #define BEGIN_FIXTURE_TEST(fixture, testname) \
425 : class cls_##testname : public fixture { \
426 : public: \
427 : virtual const char * name() { return #testname; } \
428 : virtual bool run()
429 :
430 : #define END_FIXTURE_TEST(fixture, testname) \
431 : }; \
432 : static cls_##testname cls_##testname##_instance;
433 :
434 : /*
435 : * A class for creating and managing one temporary file.
436 : *
437 : * We could use the ISO C temporary file functions here, but those try to
438 : * create files in the root directory on Windows, which fails for users
439 : * without Administrator privileges.
440 : */
441 : class TempFile {
442 : const char *name;
443 : FILE *stream;
444 :
445 : public:
446 5 : TempFile() : name(), stream() { }
447 5 : ~TempFile() {
448 5 : if (stream)
449 3 : close();
450 5 : if (name)
451 3 : remove();
452 5 : }
453 :
454 : /*
455 : * Return a stream for a temporary file named |fileName|. Infallible.
456 : * Use only once per TempFile instance. If the file is not explicitly
457 : * closed and deleted via the member functions below, this object's
458 : * destructor will clean them up.
459 : */
460 5 : FILE *open(const char *fileName)
461 : {
462 5 : stream = fopen(fileName, "wb+");
463 5 : if (!stream) {
464 : fprintf(stderr, "error opening temporary file '%s': %s\n",
465 0 : fileName, strerror(errno));
466 0 : exit(1);
467 : }
468 5 : name = fileName;
469 5 : return stream;
470 : }
471 :
472 : /* Close the temporary file's stream. */
473 5 : void close() {
474 5 : if (fclose(stream) == EOF) {
475 : fprintf(stderr, "error closing temporary file '%s': %s\n",
476 0 : name, strerror(errno));
477 0 : exit(1);
478 : }
479 5 : stream = NULL;
480 5 : }
481 :
482 : /* Delete the temporary file. */
483 5 : void remove() {
484 5 : if (::remove(name) != 0) {
485 : fprintf(stderr, "error deleting temporary file '%s': %s\n",
486 0 : name, strerror(errno));
487 0 : exit(1);
488 : }
489 5 : name = NULL;
490 5 : }
491 : };
|