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