1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "jsapi.h"
7 : #include "jsbool.h"
8 : #include "jscntxt.h"
9 : #include "jscompartment.h"
10 : #include "jsfriendapi.h"
11 : #include "jsgc.h"
12 : #include "jsobj.h"
13 : #include "jsprf.h"
14 : #include "jswrapper.h"
15 :
16 : #include "methodjit/MethodJIT.h"
17 :
18 : using namespace js;
19 : using namespace JS;
20 :
21 : static JSBool
22 8618 : GC(JSContext *cx, unsigned argc, jsval *vp)
23 : {
24 8618 : JSCompartment *comp = NULL;
25 8618 : if (argc == 1) {
26 108 : Value arg = vp[2];
27 108 : if (arg.isObject())
28 108 : comp = UnwrapObject(&arg.toObject())->compartment();
29 : }
30 :
31 : #ifndef JS_MORE_DETERMINISTIC
32 8618 : size_t preBytes = cx->runtime->gcBytes;
33 : #endif
34 :
35 8618 : JS_CompartmentGC(cx, comp);
36 :
37 8618 : char buf[256] = { '\0' };
38 : #ifndef JS_MORE_DETERMINISTIC
39 : JS_snprintf(buf, sizeof(buf), "before %lu, after %lu\n",
40 8618 : (unsigned long)preBytes, (unsigned long)cx->runtime->gcBytes);
41 : #endif
42 8618 : JSString *str = JS_NewStringCopyZ(cx, buf);
43 8618 : if (!str)
44 0 : return false;
45 8618 : *vp = STRING_TO_JSVAL(str);
46 8618 : return true;
47 : }
48 :
49 : static const struct ParamPair {
50 : const char *name;
51 : JSGCParamKey param;
52 : } paramMap[] = {
53 : {"maxBytes", JSGC_MAX_BYTES },
54 : {"maxMallocBytes", JSGC_MAX_MALLOC_BYTES},
55 : {"gcBytes", JSGC_BYTES},
56 : {"gcNumber", JSGC_NUMBER},
57 : {"sliceTimeBudget", JSGC_SLICE_TIME_BUDGET}
58 : };
59 :
60 : static JSBool
61 18 : GCParameter(JSContext *cx, unsigned argc, jsval *vp)
62 : {
63 : JSString *str;
64 18 : if (argc == 0) {
65 0 : str = JS_ValueToString(cx, JSVAL_VOID);
66 0 : JS_ASSERT(str);
67 : } else {
68 18 : str = JS_ValueToString(cx, vp[2]);
69 18 : if (!str)
70 0 : return JS_FALSE;
71 18 : vp[2] = STRING_TO_JSVAL(str);
72 : }
73 :
74 18 : JSFlatString *flatStr = JS_FlattenString(cx, str);
75 18 : if (!flatStr)
76 0 : return false;
77 :
78 18 : size_t paramIndex = 0;
79 18 : for (;; paramIndex++) {
80 36 : if (paramIndex == ArrayLength(paramMap)) {
81 : JS_ReportError(cx,
82 : "the first argument argument must be maxBytes, "
83 : "maxMallocBytes, gcStackpoolLifespan, gcBytes or "
84 0 : "gcNumber");
85 0 : return false;
86 : }
87 36 : if (JS_FlatStringEqualsAscii(flatStr, paramMap[paramIndex].name))
88 : break;
89 : }
90 18 : JSGCParamKey param = paramMap[paramIndex].param;
91 :
92 18 : if (argc == 1) {
93 9 : uint32_t value = JS_GetGCParameter(cx->runtime, param);
94 9 : return JS_NewNumberValue(cx, value, &vp[0]);
95 : }
96 :
97 9 : if (param == JSGC_NUMBER ||
98 : param == JSGC_BYTES) {
99 : JS_ReportError(cx, "Attempt to change read-only parameter %s",
100 0 : paramMap[paramIndex].name);
101 0 : return false;
102 : }
103 :
104 : uint32_t value;
105 9 : if (!JS_ValueToECMAUint32(cx, vp[3], &value)) {
106 : JS_ReportError(cx,
107 : "the second argument must be convertable to uint32_t "
108 0 : "with non-zero value");
109 0 : return false;
110 : }
111 :
112 9 : if (param == JSGC_MAX_BYTES) {
113 9 : uint32_t gcBytes = JS_GetGCParameter(cx->runtime, JSGC_BYTES);
114 9 : if (value < gcBytes) {
115 : JS_ReportError(cx,
116 : "attempt to set maxBytes to the value less than the current "
117 : "gcBytes (%u)",
118 0 : gcBytes);
119 0 : return false;
120 : }
121 : }
122 :
123 9 : JS_SetGCParameter(cx->runtime, param, value);
124 9 : *vp = JSVAL_VOID;
125 9 : return true;
126 : }
127 :
128 : static JSBool
129 9 : InternalConst(JSContext *cx, unsigned argc, jsval *vp)
130 : {
131 9 : if (argc != 1) {
132 0 : JS_ReportError(cx, "the function takes exactly one argument");
133 0 : return false;
134 : }
135 :
136 9 : JSString *str = JS_ValueToString(cx, vp[2]);
137 9 : if (!str)
138 0 : return false;
139 9 : JSFlatString *flat = JS_FlattenString(cx, str);
140 9 : if (!flat)
141 0 : return false;
142 :
143 9 : if (JS_FlatStringEqualsAscii(flat, "MARK_STACK_LENGTH")) {
144 9 : vp[0] = UINT_TO_JSVAL(js::MARK_STACK_LENGTH);
145 : } else {
146 0 : JS_ReportError(cx, "unknown const name");
147 0 : return false;
148 : }
149 9 : return true;
150 : }
151 :
152 : #ifdef JS_GC_ZEAL
153 : static JSBool
154 1629 : GCZeal(JSContext *cx, unsigned argc, jsval *vp)
155 : {
156 1629 : uint32_t zeal, frequency = JS_DEFAULT_ZEAL_FREQ;
157 1629 : JSBool compartment = JS_FALSE;
158 :
159 1629 : if (argc > 3) {
160 0 : ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Too many arguments");
161 0 : return JS_FALSE;
162 : }
163 1629 : if (!JS_ValueToECMAUint32(cx, argc < 1 ? JSVAL_VOID : vp[2], &zeal))
164 0 : return JS_FALSE;
165 1629 : if (argc >= 2)
166 45 : if (!JS_ValueToECMAUint32(cx, vp[3], &frequency))
167 0 : return JS_FALSE;
168 1629 : if (argc >= 3)
169 0 : compartment = js_ValueToBoolean(vp[3]);
170 :
171 1629 : JS_SetGCZeal(cx, (uint8_t)zeal, frequency, compartment);
172 1629 : *vp = JSVAL_VOID;
173 1629 : return JS_TRUE;
174 : }
175 :
176 : static JSBool
177 27 : ScheduleGC(JSContext *cx, unsigned argc, jsval *vp)
178 : {
179 : uint32_t count;
180 27 : bool compartment = false;
181 :
182 27 : if (argc != 1 && argc != 2) {
183 0 : ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Wrong number of arguments");
184 0 : return JS_FALSE;
185 : }
186 27 : if (!JS_ValueToECMAUint32(cx, vp[2], &count))
187 0 : return JS_FALSE;
188 27 : if (argc == 2)
189 0 : compartment = js_ValueToBoolean(vp[3]);
190 :
191 27 : JS_ScheduleGC(cx, count, compartment);
192 27 : *vp = JSVAL_VOID;
193 27 : return JS_TRUE;
194 : }
195 :
196 : static JSBool
197 18 : VerifyBarriers(JSContext *cx, unsigned argc, jsval *vp)
198 : {
199 18 : if (argc) {
200 0 : ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Too many arguments");
201 0 : return JS_FALSE;
202 : }
203 18 : gc::VerifyBarriers(cx);
204 18 : *vp = JSVAL_VOID;
205 18 : return JS_TRUE;
206 : }
207 :
208 : static JSBool
209 0 : GCSlice(JSContext *cx, unsigned argc, jsval *vp)
210 : {
211 : uint32_t budget;
212 :
213 0 : if (argc != 1) {
214 0 : ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Wrong number of arguments");
215 0 : return JS_FALSE;
216 : }
217 :
218 0 : if (!JS_ValueToECMAUint32(cx, vp[2], &budget))
219 0 : return JS_FALSE;
220 :
221 0 : GCDebugSlice(cx, budget);
222 0 : *vp = JSVAL_VOID;
223 0 : return JS_TRUE;
224 : }
225 :
226 : static JSBool
227 0 : DeterministicGC(JSContext *cx, unsigned argc, jsval *vp)
228 : {
229 0 : if (argc != 1) {
230 0 : ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Wrong number of arguments");
231 0 : return JS_FALSE;
232 : }
233 :
234 0 : gc::SetDeterministicGC(cx, js_ValueToBoolean(vp[2]));
235 0 : *vp = JSVAL_VOID;
236 0 : return JS_TRUE;
237 : }
238 : #endif /* JS_GC_ZEAL */
239 :
240 : typedef struct JSCountHeapNode JSCountHeapNode;
241 :
242 : struct JSCountHeapNode {
243 : void *thing;
244 : JSGCTraceKind kind;
245 : JSCountHeapNode *next;
246 : };
247 :
248 : typedef struct JSCountHeapTracer {
249 : JSTracer base;
250 : JSDHashTable visited;
251 : bool ok;
252 : JSCountHeapNode *traceList;
253 : JSCountHeapNode *recycleList;
254 : } JSCountHeapTracer;
255 :
256 : static void
257 228049 : CountHeapNotify(JSTracer *trc, void **thingp, JSGCTraceKind kind)
258 : {
259 : JSCountHeapTracer *countTracer;
260 : JSDHashEntryStub *entry;
261 : JSCountHeapNode *node;
262 228049 : void *thing = *thingp;
263 :
264 228049 : JS_ASSERT(trc->callback == CountHeapNotify);
265 228049 : countTracer = (JSCountHeapTracer *)trc;
266 228049 : if (!countTracer->ok)
267 0 : return;
268 :
269 : entry = (JSDHashEntryStub *)
270 228049 : JS_DHashTableOperate(&countTracer->visited, thing, JS_DHASH_ADD);
271 228049 : if (!entry) {
272 0 : countTracer->ok = false;
273 0 : return;
274 : }
275 228049 : if (entry->key)
276 58285 : return;
277 169764 : entry->key = thing;
278 :
279 169764 : node = countTracer->recycleList;
280 169764 : if (node) {
281 43533 : countTracer->recycleList = node->next;
282 : } else {
283 126231 : node = (JSCountHeapNode *) js_malloc(sizeof *node);
284 126231 : if (!node) {
285 0 : countTracer->ok = false;
286 0 : return;
287 : }
288 : }
289 169764 : node->thing = thing;
290 169764 : node->kind = kind;
291 169764 : node->next = countTracer->traceList;
292 169764 : countTracer->traceList = node;
293 : }
294 :
295 : static const struct TraceKindPair {
296 : const char *name;
297 : int32_t kind;
298 : } traceKindNames[] = {
299 : { "all", -1 },
300 : { "object", JSTRACE_OBJECT },
301 : { "string", JSTRACE_STRING },
302 : #if JS_HAS_XML_SUPPORT
303 : { "xml", JSTRACE_XML },
304 : #endif
305 : };
306 :
307 : static JSBool
308 27 : CountHeap(JSContext *cx, unsigned argc, jsval *vp)
309 : {
310 : void* startThing;
311 : JSGCTraceKind startTraceKind;
312 : jsval v;
313 : int32_t traceKind;
314 : JSString *str;
315 : JSCountHeapTracer countTracer;
316 : JSCountHeapNode *node;
317 : size_t counter;
318 :
319 27 : startThing = NULL;
320 27 : startTraceKind = JSTRACE_OBJECT;
321 27 : if (argc > 0) {
322 0 : v = JS_ARGV(cx, vp)[0];
323 0 : if (JSVAL_IS_TRACEABLE(v)) {
324 0 : startThing = JSVAL_TO_TRACEABLE(v);
325 0 : startTraceKind = JSVAL_TRACE_KIND(v);
326 0 : } else if (!JSVAL_IS_NULL(v)) {
327 : JS_ReportError(cx,
328 : "the first argument is not null or a heap-allocated "
329 0 : "thing");
330 0 : return JS_FALSE;
331 : }
332 : }
333 :
334 27 : traceKind = -1;
335 27 : if (argc > 1) {
336 0 : str = JS_ValueToString(cx, JS_ARGV(cx, vp)[1]);
337 0 : if (!str)
338 0 : return JS_FALSE;
339 0 : JSFlatString *flatStr = JS_FlattenString(cx, str);
340 0 : if (!flatStr)
341 0 : return JS_FALSE;
342 0 : for (size_t i = 0; ;) {
343 0 : if (JS_FlatStringEqualsAscii(flatStr, traceKindNames[i].name)) {
344 0 : traceKind = traceKindNames[i].kind;
345 : break;
346 : }
347 0 : if (++i == ArrayLength(traceKindNames)) {
348 0 : JSAutoByteString bytes(cx, str);
349 0 : if (!!bytes)
350 0 : JS_ReportError(cx, "trace kind name '%s' is unknown", bytes.ptr());
351 0 : return JS_FALSE;
352 : }
353 : }
354 : }
355 :
356 27 : JS_TracerInit(&countTracer.base, JS_GetRuntime(cx), CountHeapNotify);
357 27 : if (!JS_DHashTableInit(&countTracer.visited, JS_DHashGetStubOps(),
358 : NULL, sizeof(JSDHashEntryStub),
359 27 : JS_DHASH_DEFAULT_CAPACITY(100))) {
360 0 : JS_ReportOutOfMemory(cx);
361 0 : return JS_FALSE;
362 : }
363 27 : countTracer.ok = true;
364 27 : countTracer.traceList = NULL;
365 27 : countTracer.recycleList = NULL;
366 :
367 27 : if (!startThing) {
368 27 : JS_TraceRuntime(&countTracer.base);
369 : } else {
370 0 : JS_SET_TRACING_NAME(&countTracer.base, "root");
371 0 : JS_CallTracer(&countTracer.base, startThing, startTraceKind);
372 : }
373 :
374 27 : counter = 0;
375 169818 : while ((node = countTracer.traceList) != NULL) {
376 169764 : if (traceKind == -1 || node->kind == traceKind)
377 169764 : counter++;
378 169764 : countTracer.traceList = node->next;
379 169764 : node->next = countTracer.recycleList;
380 169764 : countTracer.recycleList = node;
381 169764 : JS_TraceChildren(&countTracer.base, node->thing, node->kind);
382 : }
383 126285 : while ((node = countTracer.recycleList) != NULL) {
384 126231 : countTracer.recycleList = node->next;
385 126231 : js_free(node);
386 : }
387 27 : JS_DHashTableFinish(&countTracer.visited);
388 27 : if (!countTracer.ok) {
389 0 : JS_ReportOutOfMemory(cx);
390 0 : return false;
391 : }
392 :
393 27 : return JS_NewNumberValue(cx, (double) counter, vp);
394 : }
395 :
396 : static unsigned finalizeCount = 0;
397 :
398 : static void
399 90 : finalize_counter_finalize(JSContext *cx, JSObject *obj)
400 : {
401 90 : JS_ATOMIC_INCREMENT(&finalizeCount);
402 90 : }
403 :
404 : static JSClass FinalizeCounterClass = {
405 : "FinalizeCounter", JSCLASS_IS_ANONYMOUS,
406 : JS_PropertyStub, /* addProperty */
407 : JS_PropertyStub, /* delProperty */
408 : JS_PropertyStub, /* getProperty */
409 : JS_StrictPropertyStub, /* setProperty */
410 : JS_EnumerateStub,
411 : JS_ResolveStub,
412 : JS_ConvertStub,
413 : finalize_counter_finalize
414 : };
415 :
416 : static JSBool
417 90 : MakeFinalizeObserver(JSContext *cx, unsigned argc, jsval *vp)
418 : {
419 : JSObject *obj = JS_NewObjectWithGivenProto(cx, &FinalizeCounterClass, NULL,
420 90 : JS_GetGlobalObject(cx));
421 90 : if (!obj)
422 0 : return false;
423 90 : *vp = OBJECT_TO_JSVAL(obj);
424 90 : return true;
425 : }
426 :
427 : static JSBool
428 27 : FinalizeCount(JSContext *cx, unsigned argc, jsval *vp)
429 : {
430 27 : *vp = INT_TO_JSVAL(finalizeCount);
431 27 : return true;
432 : }
433 :
434 : JSBool
435 0 : MJitCodeStats(JSContext *cx, unsigned argc, jsval *vp)
436 : {
437 : #ifdef JS_METHODJIT
438 0 : JSRuntime *rt = cx->runtime;
439 0 : AutoLockGC lock(rt);
440 0 : size_t n = 0;
441 0 : for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) {
442 0 : n += (*c)->sizeOfMjitCode();
443 : }
444 0 : JS_SET_RVAL(cx, vp, INT_TO_JSVAL(n));
445 : #else
446 : JS_SET_RVAL(cx, vp, JSVAL_VOID);
447 : #endif
448 0 : return true;
449 : }
450 :
451 : JSBool
452 27 : MJitChunkLimit(JSContext *cx, unsigned argc, jsval *vp)
453 : {
454 27 : if (argc != 1) {
455 0 : ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Wrong number of arguments");
456 0 : return JS_FALSE;
457 : }
458 :
459 : double t;
460 27 : if (!JS_ValueToNumber(cx, JS_ARGV(cx, vp)[0], &t))
461 0 : return JS_FALSE;
462 :
463 : #ifdef JS_METHODJIT
464 27 : mjit::SetChunkLimit((uint32_t) t);
465 : #endif
466 :
467 : // Clear out analysis information which might refer to code compiled with
468 : // the previous chunk limit.
469 27 : JS_GC(cx);
470 :
471 27 : vp->setUndefined();
472 27 : return true;
473 : }
474 :
475 : static JSBool
476 27 : Terminate(JSContext *cx, unsigned arg, jsval *vp)
477 : {
478 27 : JS_ClearPendingException(cx);
479 27 : return JS_FALSE;
480 : }
481 :
482 : static JSFunctionSpecWithHelp TestingFunctions[] = {
483 : JS_FN_HELP("gc", ::GC, 0, 0,
484 : "gc([obj])",
485 : " Run the garbage collector. When obj is given, GC only its compartment."),
486 :
487 : JS_FN_HELP("gcparam", GCParameter, 2, 0,
488 : "gcparam(name [, value])",
489 : " Wrapper for JS_[GS]etGCParameter. The name is either maxBytes,\n"
490 : " maxMallocBytes, gcBytes, gcNumber, or sliceTimeBudget."),
491 :
492 : JS_FN_HELP("countHeap", CountHeap, 0, 0,
493 : "countHeap([start[, kind]])",
494 : " Count the number of live GC things in the heap or things reachable from\n"
495 : " start when it is given and is not null. kind is either 'all' (default) to\n"
496 : " count all things or one of 'object', 'double', 'string', 'function',\n"
497 : " 'qname', 'namespace', 'xml' to count only things of that kind."),
498 :
499 : JS_FN_HELP("makeFinalizeObserver", MakeFinalizeObserver, 0, 0,
500 : "makeFinalizeObserver()",
501 : " Get a special object whose finalization increases the counter returned\n"
502 : " by the finalizeCount function."),
503 :
504 : JS_FN_HELP("finalizeCount", FinalizeCount, 0, 0,
505 : "finalizeCount()",
506 : " Return the current value of the finalization counter that is incremented\n"
507 : " each time an object returned by the makeFinalizeObserver is finalized."),
508 :
509 : #ifdef JS_GC_ZEAL
510 : JS_FN_HELP("gczeal", GCZeal, 2, 0,
511 : "gczeal(level, [period], [compartmentGC?])",
512 : " Specifies how zealous the garbage collector should be. Values for level:\n"
513 : " 0: Normal amount of collection\n"
514 : " 1: Collect when roots are added or removed\n"
515 : " 2: Collect when memory is allocated\n"
516 : " 3: Collect when the window paints (browser only)\n"
517 : " 4: Verify write barriers between instructions\n"
518 : " 5: Verify write barriers between paints\n"
519 : " Period specifies that collection happens every n allocations.\n"
520 : " If compartmentGC is true, the collections will be compartmental."),
521 :
522 : JS_FN_HELP("schedulegc", ScheduleGC, 1, 0,
523 : "schedulegc(num, [compartmentGC?])",
524 : " Schedule a GC to happen after num allocations."),
525 :
526 : JS_FN_HELP("verifybarriers", VerifyBarriers, 0, 0,
527 : "verifybarriers()",
528 : " Start or end a run of the write barrier verifier."),
529 :
530 : JS_FN_HELP("gcslice", GCSlice, 1, 0,
531 : "gcslice(n)",
532 : " Run an incremental GC slice that marks about n objects."),
533 :
534 : JS_FN_HELP("deterministicgc", DeterministicGC, 1, 0,
535 : "deterministicgc(true|false)",
536 : " If true, only allow determinstic GCs to run."),
537 : #endif
538 :
539 : JS_FN_HELP("internalConst", InternalConst, 1, 0,
540 : "internalConst(name)",
541 : " Query an internal constant for the engine. See InternalConst source for\n"
542 : " the list of constant names."),
543 :
544 : #ifdef JS_METHODJIT
545 : JS_FN_HELP("mjitcodestats", MJitCodeStats, 0, 0,
546 : "mjitcodestats()",
547 : "Return stats on mjit code memory usage."),
548 : #endif
549 :
550 : JS_FN_HELP("mjitChunkLimit", MJitChunkLimit, 1, 0,
551 : "mjitChunkLimit(N)",
552 : " Specify limit on compiled chunk size during mjit compilation."),
553 :
554 : JS_FN_HELP("terminate", Terminate, 0, 0,
555 : "terminate()",
556 : " Terminate JavaScript execution, as if we had run out of\n"
557 : " memory or been terminated by the slow script dialog."),
558 :
559 : JS_FS_END
560 : };
561 :
562 : namespace js {
563 :
564 : bool
565 22824 : DefineTestingFunctions(JSContext *cx, JSObject *obj)
566 : {
567 22824 : return JS_DefineFunctionsWithHelp(cx, obj, TestingFunctions);
568 : }
569 :
570 : } /* namespace js */
|