/[jscoverage]/trunk/js/jsatom.cpp
ViewVC logotype

Contents of /trunk/js/jsatom.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 460 - (show annotations)
Sat Sep 26 23:15:22 2009 UTC (9 years, 9 months ago) by siliconforks
File size: 40558 byte(s)
Upgrade to SpiderMonkey from Firefox 3.5.3.

1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 *
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
10 *
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
15 *
16 * The Original Code is Mozilla Communicator client code, released
17 * March 31, 1998.
18 *
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 1998
22 * the Initial Developer. All Rights Reserved.
23 *
24 * Contributor(s):
25 *
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
37 *
38 * ***** END LICENSE BLOCK ***** */
39
40 /*
41 * JS atom table.
42 */
43 #include "jsstddef.h"
44 #include <stdlib.h>
45 #include <string.h>
46 #include "jstypes.h"
47 #include "jsutil.h" /* Added by JSIFY */
48 #include "jshash.h" /* Added by JSIFY */
49 #include "jsprf.h"
50 #include "jsapi.h"
51 #include "jsatom.h"
52 #include "jsbit.h"
53 #include "jscntxt.h"
54 #include "jsgc.h"
55 #include "jslock.h"
56 #include "jsnum.h"
57 #include "jsparse.h"
58 #include "jsscan.h"
59 #include "jsstr.h"
60 #include "jsversion.h"
61
62 /*
63 * ATOM_HASH assumes that JSHashNumber is 32-bit even on 64-bit systems.
64 */
65 JS_STATIC_ASSERT(sizeof(JSHashNumber) == 4);
66 JS_STATIC_ASSERT(sizeof(JSAtom *) == JS_BYTES_PER_WORD);
67
68 /*
69 * Start and limit offsets for atom pointers in JSAtomState must be aligned
70 * on the word boundary.
71 */
72 JS_STATIC_ASSERT(ATOM_OFFSET_START % sizeof(JSAtom *) == 0);
73 JS_STATIC_ASSERT(ATOM_OFFSET_LIMIT % sizeof(JSAtom *) == 0);
74
75 /*
76 * JS_BOOLEAN_STR and JS_TYPE_STR assume that boolean names starts from the
77 * index 1 and type name starts from the index 1+2 atoms in JSAtomState.
78 */
79 JS_STATIC_ASSERT(1 * sizeof(JSAtom *) ==
80 offsetof(JSAtomState, booleanAtoms) - ATOM_OFFSET_START);
81 JS_STATIC_ASSERT((1 + 2) * sizeof(JSAtom *) ==
82 offsetof(JSAtomState, typeAtoms) - ATOM_OFFSET_START);
83
84 const char *
85 js_AtomToPrintableString(JSContext *cx, JSAtom *atom)
86 {
87 return js_ValueToPrintableString(cx, ATOM_KEY(atom));
88 }
89
90 #define JS_PROTO(name,code,init) const char js_##name##_str[] = #name;
91 #include "jsproto.tbl"
92 #undef JS_PROTO
93
94 /*
95 * String constants for common atoms defined in JSAtomState starting from
96 * JSAtomState.emptyAtom until JSAtomState.lazy.
97 *
98 * The elements of the array after the first empty string define strings
99 * corresponding to the two boolean literals, false and true, followed by the
100 * JSType enumerators from jspubtd.h starting with "undefined" for JSTYPE_VOID
101 * (which is pseudo-boolean 2) and continuing as initialized below. The static
102 * asserts check these relations.
103 */
104 JS_STATIC_ASSERT(JSTYPE_LIMIT == 8);
105 JS_STATIC_ASSERT(JSTYPE_VOID == 0);
106
107 const char *const js_common_atom_names[] = {
108 "", /* emptyAtom */
109 js_false_str, /* booleanAtoms[0] */
110 js_true_str, /* booleanAtoms[1] */
111 js_undefined_str, /* typeAtoms[JSTYPE_VOID] */
112 js_object_str, /* typeAtoms[JSTYPE_OBJECT] */
113 js_function_str, /* typeAtoms[JSTYPE_FUNCTION] */
114 "string", /* typeAtoms[JSTYPE_STRING] */
115 "number", /* typeAtoms[JSTYPE_NUMBER] */
116 "boolean", /* typeAtoms[JSTYPE_BOOLEAN] */
117 js_null_str, /* typeAtoms[JSTYPE_NULL] */
118 "xml", /* typeAtoms[JSTYPE_XML] */
119 js_null_str, /* nullAtom */
120
121 #define JS_PROTO(name,code,init) js_##name##_str,
122 #include "jsproto.tbl"
123 #undef JS_PROTO
124
125 js_anonymous_str, /* anonymousAtom */
126 js_apply_str, /* applyAtom */
127 js_arguments_str, /* argumentsAtom */
128 js_arity_str, /* arityAtom */
129 js_call_str, /* callAtom */
130 js_callee_str, /* calleeAtom */
131 js_caller_str, /* callerAtom */
132 js_class_prototype_str, /* classPrototypeAtom */
133 js_constructor_str, /* constructorAtom */
134 js_count_str, /* countAtom */
135 js_each_str, /* eachAtom */
136 js_eval_str, /* evalAtom */
137 js_fileName_str, /* fileNameAtom */
138 js_get_str, /* getAtom */
139 js_getter_str, /* getterAtom */
140 js_index_str, /* indexAtom */
141 js_input_str, /* inputAtom */
142 js_iterator_str, /* iteratorAtom */
143 js_length_str, /* lengthAtom */
144 js_lineNumber_str, /* lineNumberAtom */
145 js_message_str, /* messageAtom */
146 js_name_str, /* nameAtom */
147 js_next_str, /* nextAtom */
148 js_noSuchMethod_str, /* noSuchMethodAtom */
149 js_parent_str, /* parentAtom */
150 js_proto_str, /* protoAtom */
151 js_set_str, /* setAtom */
152 js_setter_str, /* setterAtom */
153 js_stack_str, /* stackAtom */
154 js_toLocaleString_str, /* toLocaleStringAtom */
155 js_toSource_str, /* toSourceAtom */
156 js_toString_str, /* toStringAtom */
157 js_valueOf_str, /* valueOfAtom */
158 js_toJSON_str, /* toJSONAtom */
159 "(void 0)", /* void0Atom */
160
161 #if JS_HAS_XML_SUPPORT
162 js_etago_str, /* etagoAtom */
163 js_namespace_str, /* namespaceAtom */
164 js_ptagc_str, /* ptagcAtom */
165 js_qualifier_str, /* qualifierAtom */
166 js_space_str, /* spaceAtom */
167 js_stago_str, /* stagoAtom */
168 js_star_str, /* starAtom */
169 js_starQualifier_str, /* starQualifierAtom */
170 js_tagc_str, /* tagcAtom */
171 js_xml_str, /* xmlAtom */
172 #endif
173
174 #ifdef NARCISSUS
175 js___call___str, /* __call__Atom */
176 js___construct___str, /* __construct__Atom */
177 js___hasInstance___str, /* __hasInstance__Atom */
178 js_ExecutionContext_str, /* ExecutionContextAtom */
179 js_current_str, /* currentAtom */
180 #endif
181 };
182
183 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(js_common_atom_names) * sizeof(JSAtom *) ==
184 LAZY_ATOM_OFFSET_START - ATOM_OFFSET_START);
185
186 /*
187 * Interpreter macros called by the trace recorder assume common atom indexes
188 * fit in one byte of immediate operand.
189 */
190 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(js_common_atom_names) < 256);
191
192 const size_t js_common_atom_count = JS_ARRAY_LENGTH(js_common_atom_names);
193
194 const char js_anonymous_str[] = "anonymous";
195 const char js_apply_str[] = "apply";
196 const char js_arguments_str[] = "arguments";
197 const char js_arity_str[] = "arity";
198 const char js_call_str[] = "call";
199 const char js_callee_str[] = "callee";
200 const char js_caller_str[] = "caller";
201 const char js_class_prototype_str[] = "prototype";
202 const char js_constructor_str[] = "constructor";
203 const char js_count_str[] = "__count__";
204 const char js_each_str[] = "each";
205 const char js_eval_str[] = "eval";
206 const char js_fileName_str[] = "fileName";
207 const char js_get_str[] = "get";
208 const char js_getter_str[] = "getter";
209 const char js_index_str[] = "index";
210 const char js_input_str[] = "input";
211 const char js_iterator_str[] = "__iterator__";
212 const char js_length_str[] = "length";
213 const char js_lineNumber_str[] = "lineNumber";
214 const char js_message_str[] = "message";
215 const char js_name_str[] = "name";
216 const char js_next_str[] = "next";
217 const char js_noSuchMethod_str[] = "__noSuchMethod__";
218 const char js_object_str[] = "object";
219 const char js_parent_str[] = "__parent__";
220 const char js_proto_str[] = "__proto__";
221 const char js_setter_str[] = "setter";
222 const char js_set_str[] = "set";
223 const char js_stack_str[] = "stack";
224 const char js_toSource_str[] = "toSource";
225 const char js_toString_str[] = "toString";
226 const char js_toLocaleString_str[] = "toLocaleString";
227 const char js_undefined_str[] = "undefined";
228 const char js_valueOf_str[] = "valueOf";
229 const char js_toJSON_str[] = "toJSON";
230
231 #if JS_HAS_XML_SUPPORT
232 const char js_etago_str[] = "</";
233 const char js_namespace_str[] = "namespace";
234 const char js_ptagc_str[] = "/>";
235 const char js_qualifier_str[] = "::";
236 const char js_space_str[] = " ";
237 const char js_stago_str[] = "<";
238 const char js_star_str[] = "*";
239 const char js_starQualifier_str[] = "*::";
240 const char js_tagc_str[] = ">";
241 const char js_xml_str[] = "xml";
242 #endif
243
244 #if JS_HAS_GENERATORS
245 const char js_close_str[] = "close";
246 const char js_send_str[] = "send";
247 #endif
248
249 #ifdef NARCISSUS
250 const char js___call___str[] = "__call__";
251 const char js___construct___str[] = "__construct__";
252 const char js___hasInstance___str[] = "__hasInstance__";
253 const char js_ExecutionContext_str[] = "ExecutionContext";
254 const char js_current_str[] = "current";
255 #endif
256
257 /*
258 * JSAtomState.doubleAtoms and JSAtomState.stringAtoms hashtable entry. To
259 * support pinned and interned string atoms, we use the lowest bits of the
260 * keyAndFlags field to store ATOM_PINNED and ATOM_INTERNED flags.
261 */
262 typedef struct JSAtomHashEntry {
263 JSDHashEntryHdr hdr;
264 jsuword keyAndFlags;
265 } JSAtomHashEntry;
266
267 #define ATOM_ENTRY_FLAG_MASK (ATOM_PINNED | ATOM_INTERNED)
268
269 JS_STATIC_ASSERT(ATOM_ENTRY_FLAG_MASK < JSVAL_ALIGN);
270
271 /*
272 * Helper macros to access and modify JSAtomHashEntry.
273 */
274 #define TO_ATOM_ENTRY(hdr) ((JSAtomHashEntry *) hdr)
275 #define ATOM_ENTRY_KEY(entry) \
276 ((void *)((entry)->keyAndFlags & ~ATOM_ENTRY_FLAG_MASK))
277 #define ATOM_ENTRY_FLAGS(entry) \
278 ((uintN)((entry)->keyAndFlags & ATOM_ENTRY_FLAG_MASK))
279 #define INIT_ATOM_ENTRY(entry, key) \
280 ((void)((entry)->keyAndFlags = (jsuword)(key)))
281 #define ADD_ATOM_ENTRY_FLAGS(entry, flags) \
282 ((void)((entry)->keyAndFlags |= (jsuword)(flags)))
283 #define CLEAR_ATOM_ENTRY_FLAGS(entry, flags) \
284 ((void)((entry)->keyAndFlags &= ~(jsuword)(flags)))
285
286 static JSDHashNumber
287 HashDouble(JSDHashTable *table, const void *key);
288
289 static JSBool
290 MatchDouble(JSDHashTable *table, const JSDHashEntryHdr *hdr, const void *key);
291
292 static JSDHashNumber
293 HashString(JSDHashTable *table, const void *key);
294
295 static JSBool
296 MatchString(JSDHashTable *table, const JSDHashEntryHdr *hdr, const void *key);
297
298 static const JSDHashTableOps DoubleHashOps = {
299 JS_DHashAllocTable,
300 JS_DHashFreeTable,
301 HashDouble,
302 MatchDouble,
303 JS_DHashMoveEntryStub,
304 JS_DHashClearEntryStub,
305 JS_DHashFinalizeStub,
306 NULL
307 };
308
309 static const JSDHashTableOps StringHashOps = {
310 JS_DHashAllocTable,
311 JS_DHashFreeTable,
312 HashString,
313 MatchString,
314 JS_DHashMoveEntryStub,
315 JS_DHashClearEntryStub,
316 JS_DHashFinalizeStub,
317 NULL
318 };
319
320 #define IS_DOUBLE_TABLE(table) ((table)->ops == &DoubleHashOps)
321 #define IS_STRING_TABLE(table) ((table)->ops == &StringHashOps)
322
323 #define IS_INITIALIZED_STATE(state) IS_DOUBLE_TABLE(&(state)->doubleAtoms)
324
325 static JSDHashNumber
326 HashDouble(JSDHashTable *table, const void *key)
327 {
328 jsdouble d;
329
330 JS_ASSERT(IS_DOUBLE_TABLE(table));
331 d = *(jsdouble *)key;
332 return JSDOUBLE_HI32(d) ^ JSDOUBLE_LO32(d);
333 }
334
335 static JSDHashNumber
336 HashString(JSDHashTable *table, const void *key)
337 {
338 JS_ASSERT(IS_STRING_TABLE(table));
339 return js_HashString((JSString *)key);
340 }
341
342 static JSBool
343 MatchDouble(JSDHashTable *table, const JSDHashEntryHdr *hdr, const void *key)
344 {
345 JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr);
346 jsdouble d1, d2;
347
348 JS_ASSERT(IS_DOUBLE_TABLE(table));
349 if (entry->keyAndFlags == 0) {
350 /* See comments in MatchString. */
351 return JS_FALSE;
352 }
353
354 d1 = *(jsdouble *)ATOM_ENTRY_KEY(entry);
355 d2 = *(jsdouble *)key;
356 if (JSDOUBLE_IS_NaN(d1))
357 return JSDOUBLE_IS_NaN(d2);
358 #if defined(XP_WIN)
359 /* XXX MSVC miscompiles such that (NaN == 0) */
360 if (JSDOUBLE_IS_NaN(d2))
361 return JS_FALSE;
362 #endif
363 return d1 == d2;
364 }
365
366 static JSBool
367 MatchString(JSDHashTable *table, const JSDHashEntryHdr *hdr, const void *key)
368 {
369 JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr);
370
371 JS_ASSERT(IS_STRING_TABLE(table));
372 if (entry->keyAndFlags == 0) {
373 /*
374 * This happens when js_AtomizeString adds a new hash entry and
375 * releases the lock but before it takes the lock the second time to
376 * initialize keyAndFlags for the entry.
377 *
378 * We always return false for such entries so JS_DHashTableOperate
379 * never finds them. We clean them during GC's sweep phase.
380 *
381 * It means that with a contested lock or when GC is triggered outside
382 * the lock we may end up adding two entries, but this is a price for
383 * simpler code.
384 */
385 return JS_FALSE;
386 }
387 return js_EqualStrings((JSString *)ATOM_ENTRY_KEY(entry), (JSString *)key);
388 }
389
390 /*
391 * For a browser build from 2007-08-09 after the browser starts up there are
392 * just 55 double atoms, but over 15000 string atoms. Not to penalize more
393 * economical embeddings allocating too much memory initially we initialize
394 * atomized strings with just 1K entries.
395 */
396 #define JS_STRING_HASH_COUNT 1024
397 #define JS_DOUBLE_HASH_COUNT 64
398
399 JSBool
400 js_InitAtomState(JSRuntime *rt)
401 {
402 JSAtomState *state = &rt->atomState;
403
404 /*
405 * The caller must zero the state before calling this function.
406 */
407 JS_ASSERT(!state->stringAtoms.ops);
408 JS_ASSERT(!state->doubleAtoms.ops);
409
410 if (!JS_DHashTableInit(&state->stringAtoms, &StringHashOps,
411 NULL, sizeof(JSAtomHashEntry),
412 JS_DHASH_DEFAULT_CAPACITY(JS_STRING_HASH_COUNT))) {
413 state->stringAtoms.ops = NULL;
414 return JS_FALSE;
415 }
416 JS_ASSERT(IS_STRING_TABLE(&state->stringAtoms));
417
418 if (!JS_DHashTableInit(&state->doubleAtoms, &DoubleHashOps,
419 NULL, sizeof(JSAtomHashEntry),
420 JS_DHASH_DEFAULT_CAPACITY(JS_DOUBLE_HASH_COUNT))) {
421 state->doubleAtoms.ops = NULL;
422 JS_DHashTableFinish(&state->stringAtoms);
423 state->stringAtoms.ops = NULL;
424 return JS_FALSE;
425 }
426 JS_ASSERT(IS_DOUBLE_TABLE(&state->doubleAtoms));
427
428 #ifdef JS_THREADSAFE
429 js_InitLock(&state->lock);
430 #endif
431 JS_ASSERT(IS_INITIALIZED_STATE(state));
432 return JS_TRUE;
433 }
434
435 static JSDHashOperator
436 js_string_uninterner(JSDHashTable *table, JSDHashEntryHdr *hdr,
437 uint32 number, void *arg)
438 {
439 JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr);
440 JSRuntime *rt = (JSRuntime *)arg;
441 JSString *str;
442
443 /*
444 * Any string entry that remains at this point must be initialized, as the
445 * last GC should clean any uninitialized ones.
446 */
447 JS_ASSERT(IS_STRING_TABLE(table));
448 JS_ASSERT(entry->keyAndFlags != 0);
449 str = (JSString *)ATOM_ENTRY_KEY(entry);
450
451 /* Pass null as context. */
452 js_FinalizeStringRT(rt, str, js_GetExternalStringGCType(str), NULL);
453 return JS_DHASH_NEXT;
454 }
455
456 void
457 js_FinishAtomState(JSRuntime *rt)
458 {
459 JSAtomState *state = &rt->atomState;
460
461 if (!IS_INITIALIZED_STATE(state)) {
462 /*
463 * We are called with uninitialized state when JS_NewRuntime fails and
464 * calls JS_DestroyRuntime on a partially initialized runtime.
465 */
466 return;
467 }
468
469 JS_DHashTableEnumerate(&state->stringAtoms, js_string_uninterner, rt);
470 JS_DHashTableFinish(&state->stringAtoms);
471 JS_DHashTableFinish(&state->doubleAtoms);
472
473 #ifdef JS_THREADSAFE
474 js_FinishLock(&state->lock);
475 #endif
476 #ifdef DEBUG
477 memset(state, JS_FREE_PATTERN, sizeof *state);
478 #endif
479 }
480
481 JSBool
482 js_InitCommonAtoms(JSContext *cx)
483 {
484 JSAtomState *state = &cx->runtime->atomState;
485 uintN i;
486 JSAtom **atoms;
487
488 atoms = COMMON_ATOMS_START(state);
489 for (i = 0; i < JS_ARRAY_LENGTH(js_common_atom_names); i++, atoms++) {
490 *atoms = js_Atomize(cx, js_common_atom_names[i],
491 strlen(js_common_atom_names[i]), ATOM_PINNED);
492 if (!*atoms)
493 return JS_FALSE;
494 }
495 JS_ASSERT((uint8 *)atoms - (uint8 *)state == LAZY_ATOM_OFFSET_START);
496 memset(atoms, 0, ATOM_OFFSET_LIMIT - LAZY_ATOM_OFFSET_START);
497
498 return JS_TRUE;
499 }
500
501 static JSDHashOperator
502 js_atom_unpinner(JSDHashTable *table, JSDHashEntryHdr *hdr,
503 uint32 number, void *arg)
504 {
505 JS_ASSERT(IS_STRING_TABLE(table));
506 CLEAR_ATOM_ENTRY_FLAGS(TO_ATOM_ENTRY(hdr), ATOM_PINNED);
507 return JS_DHASH_NEXT;
508 }
509
510 void
511 js_FinishCommonAtoms(JSContext *cx)
512 {
513 JSAtomState *state = &cx->runtime->atomState;
514
515 JS_DHashTableEnumerate(&state->stringAtoms, js_atom_unpinner, NULL);
516 #ifdef DEBUG
517 memset(COMMON_ATOMS_START(state), JS_FREE_PATTERN,
518 ATOM_OFFSET_LIMIT - ATOM_OFFSET_START);
519 #endif
520 }
521
522 static JSDHashOperator
523 js_locked_atom_tracer(JSDHashTable *table, JSDHashEntryHdr *hdr,
524 uint32 number, void *arg)
525 {
526 JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr);
527 JSTracer *trc = (JSTracer *)arg;
528
529 if (entry->keyAndFlags == 0) {
530 /* Ignore uninitialized entries during tracing. */
531 return JS_DHASH_NEXT;
532 }
533 JS_SET_TRACING_INDEX(trc, "locked_atom", (size_t)number);
534 JS_CallTracer(trc, ATOM_ENTRY_KEY(entry),
535 IS_STRING_TABLE(table) ? JSTRACE_STRING : JSTRACE_DOUBLE);
536 return JS_DHASH_NEXT;
537 }
538
539 static JSDHashOperator
540 js_pinned_atom_tracer(JSDHashTable *table, JSDHashEntryHdr *hdr,
541 uint32 number, void *arg)
542 {
543 JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr);
544 JSTracer *trc = (JSTracer *)arg;
545 uintN flags = ATOM_ENTRY_FLAGS(entry);
546
547 JS_ASSERT(IS_STRING_TABLE(table));
548 if (flags & (ATOM_PINNED | ATOM_INTERNED)) {
549 JS_SET_TRACING_INDEX(trc,
550 flags & ATOM_PINNED
551 ? "pinned_atom"
552 : "interned_atom",
553 (size_t)number);
554 JS_CallTracer(trc, ATOM_ENTRY_KEY(entry), JSTRACE_STRING);
555 }
556 return JS_DHASH_NEXT;
557 }
558
559 void
560 js_TraceAtomState(JSTracer *trc, JSBool allAtoms)
561 {
562 JSAtomState *state;
563
564 state = &trc->context->runtime->atomState;
565 if (allAtoms) {
566 JS_DHashTableEnumerate(&state->doubleAtoms, js_locked_atom_tracer, trc);
567 JS_DHashTableEnumerate(&state->stringAtoms, js_locked_atom_tracer, trc);
568 } else {
569 JS_DHashTableEnumerate(&state->stringAtoms, js_pinned_atom_tracer, trc);
570 }
571 }
572
573 static JSDHashOperator
574 js_atom_sweeper(JSDHashTable *table, JSDHashEntryHdr *hdr,
575 uint32 number, void *arg)
576 {
577 JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr);
578 JSContext *cx = (JSContext *)arg;
579
580 /* Remove uninitialized entries. */
581 if (entry->keyAndFlags == 0)
582 return JS_DHASH_REMOVE;
583
584 if (ATOM_ENTRY_FLAGS(entry) & (ATOM_PINNED | ATOM_INTERNED)) {
585 /* Pinned or interned key cannot be finalized. */
586 JS_ASSERT(!js_IsAboutToBeFinalized(cx, ATOM_ENTRY_KEY(entry)));
587 } else if (js_IsAboutToBeFinalized(cx, ATOM_ENTRY_KEY(entry))) {
588 /* Remove entries with things about to be GC'ed. */
589 return JS_DHASH_REMOVE;
590 }
591 return JS_DHASH_NEXT;
592 }
593
594 void
595 js_SweepAtomState(JSContext *cx)
596 {
597 JSAtomState *state = &cx->runtime->atomState;
598
599 JS_DHashTableEnumerate(&state->doubleAtoms, js_atom_sweeper, cx);
600 JS_DHashTableEnumerate(&state->stringAtoms, js_atom_sweeper, cx);
601
602 /*
603 * Optimize for simplicity and mutate table generation numbers even if the
604 * sweeper has not removed any entries.
605 */
606 state->doubleAtoms.generation++;
607 state->stringAtoms.generation++;
608 }
609
610 JSAtom *
611 js_AtomizeDouble(JSContext *cx, jsdouble d)
612 {
613 JSAtomState *state;
614 JSDHashTable *table;
615 JSAtomHashEntry *entry;
616 uint32 gen;
617 jsdouble *key;
618 jsval v;
619
620 state = &cx->runtime->atomState;
621 table = &state->doubleAtoms;
622
623 JS_LOCK(cx, &state->lock);
624 entry = TO_ATOM_ENTRY(JS_DHashTableOperate(table, &d, JS_DHASH_ADD));
625 if (!entry)
626 goto failed_hash_add;
627 if (entry->keyAndFlags == 0) {
628 gen = ++table->generation;
629 JS_UNLOCK(cx, &state->lock);
630
631 key = js_NewWeaklyRootedDouble(cx, d);
632 if (!key)
633 return NULL;
634
635 JS_LOCK(cx, &state->lock);
636 if (table->generation == gen) {
637 JS_ASSERT(entry->keyAndFlags == 0);
638 } else {
639 entry = TO_ATOM_ENTRY(JS_DHashTableOperate(table, key,
640 JS_DHASH_ADD));
641 if (!entry)
642 goto failed_hash_add;
643 if (entry->keyAndFlags != 0)
644 goto finish;
645 ++table->generation;
646 }
647 INIT_ATOM_ENTRY(entry, key);
648 }
649
650 finish:
651 v = DOUBLE_TO_JSVAL((jsdouble *)ATOM_ENTRY_KEY(entry));
652 cx->weakRoots.lastAtom = v;
653 JS_UNLOCK(cx, &state->lock);
654
655 return (JSAtom *)v;
656
657 failed_hash_add:
658 JS_UNLOCK(cx, &state->lock);
659 JS_ReportOutOfMemory(cx);
660 return NULL;
661 }
662
663 JSAtom *
664 js_AtomizeString(JSContext *cx, JSString *str, uintN flags)
665 {
666 jsval v;
667 JSAtomState *state;
668 JSDHashTable *table;
669 JSAtomHashEntry *entry;
670 JSString *key;
671 uint32 gen;
672
673 JS_ASSERT(!(flags & ~(ATOM_PINNED|ATOM_INTERNED|ATOM_TMPSTR|ATOM_NOCOPY)));
674 JS_ASSERT_IF(flags & ATOM_NOCOPY, flags & ATOM_TMPSTR);
675
676 state = &cx->runtime->atomState;
677 table = &state->stringAtoms;
678
679 JS_LOCK(cx, &state->lock);
680 entry = TO_ATOM_ENTRY(JS_DHashTableOperate(table, str, JS_DHASH_ADD));
681 if (!entry)
682 goto failed_hash_add;
683 if (entry->keyAndFlags != 0) {
684 key = (JSString *)ATOM_ENTRY_KEY(entry);
685 } else {
686 /*
687 * We created a new hashtable entry. Unless str is already allocated
688 * from the GC heap and flat, we have to release state->lock as
689 * string construction is a complex operation. For example, it can
690 * trigger GC which may rehash the table and make the entry invalid.
691 */
692 ++table->generation;
693 if (!(flags & ATOM_TMPSTR) && JSSTRING_IS_FLAT(str)) {
694 JSFLATSTR_CLEAR_MUTABLE(str);
695 key = str;
696 } else {
697 gen = table->generation;
698 JS_UNLOCK(cx, &state->lock);
699
700 if (flags & ATOM_TMPSTR) {
701 if (flags & ATOM_NOCOPY) {
702 key = js_NewString(cx, JSFLATSTR_CHARS(str),
703 JSFLATSTR_LENGTH(str));
704 if (!key)
705 return NULL;
706
707 /* Finish handing off chars to the GC'ed key string. */
708 str->u.chars = NULL;
709 } else {
710 key = js_NewStringCopyN(cx, JSFLATSTR_CHARS(str),
711 JSFLATSTR_LENGTH(str));
712 if (!key)
713 return NULL;
714 }
715 } else {
716 JS_ASSERT(JSSTRING_IS_DEPENDENT(str));
717 if (!js_UndependString(cx, str))
718 return NULL;
719 key = str;
720 }
721
722 JS_LOCK(cx, &state->lock);
723 if (table->generation == gen) {
724 JS_ASSERT(entry->keyAndFlags == 0);
725 } else {
726 entry = TO_ATOM_ENTRY(JS_DHashTableOperate(table, key,
727 JS_DHASH_ADD));
728 if (!entry)
729 goto failed_hash_add;
730 if (entry->keyAndFlags != 0) {
731 key = (JSString *)ATOM_ENTRY_KEY(entry);
732 goto finish;
733 }
734 ++table->generation;
735 }
736 }
737 INIT_ATOM_ENTRY(entry, key);
738 JSFLATSTR_SET_ATOMIZED(key);
739 }
740
741 finish:
742 ADD_ATOM_ENTRY_FLAGS(entry, flags & (ATOM_PINNED | ATOM_INTERNED));
743 JS_ASSERT(JSSTRING_IS_ATOMIZED(key));
744 v = STRING_TO_JSVAL(key);
745 cx->weakRoots.lastAtom = v;
746 JS_UNLOCK(cx, &state->lock);
747 return (JSAtom *)v;
748
749 failed_hash_add:
750 JS_UNLOCK(cx, &state->lock);
751 JS_ReportOutOfMemory(cx);
752 return NULL;
753 }
754
755 JSAtom *
756 js_Atomize(JSContext *cx, const char *bytes, size_t length, uintN flags)
757 {
758 jschar *chars;
759 JSString str;
760 JSAtom *atom;
761
762 /*
763 * Avoiding the malloc in js_InflateString on shorter strings saves us
764 * over 20,000 malloc calls on mozilla browser startup. This compares to
765 * only 131 calls where the string is longer than a 31 char (net) buffer.
766 * The vast majority of atomized strings are already in the hashtable. So
767 * js_AtomizeString rarely has to copy the temp string we make.
768 */
769 #define ATOMIZE_BUF_MAX 32
770 jschar inflated[ATOMIZE_BUF_MAX];
771 size_t inflatedLength = ATOMIZE_BUF_MAX - 1;
772
773 if (length < ATOMIZE_BUF_MAX) {
774 js_InflateStringToBuffer(cx, bytes, length, inflated, &inflatedLength);
775 inflated[inflatedLength] = 0;
776 chars = inflated;
777 } else {
778 inflatedLength = length;
779 chars = js_InflateString(cx, bytes, &inflatedLength);
780 if (!chars)
781 return NULL;
782 flags |= ATOM_NOCOPY;
783 }
784
785 JSFLATSTR_INIT(&str, (jschar *)chars, inflatedLength);
786 atom = js_AtomizeString(cx, &str, ATOM_TMPSTR | flags);
787 if (chars != inflated && str.u.chars)
788 JS_free(cx, chars);
789 return atom;
790 }
791
792 JSAtom *
793 js_AtomizeChars(JSContext *cx, const jschar *chars, size_t length, uintN flags)
794 {
795 JSString str;
796
797 JSFLATSTR_INIT(&str, (jschar *)chars, length);
798 return js_AtomizeString(cx, &str, ATOM_TMPSTR | flags);
799 }
800
801 JSAtom *
802 js_GetExistingStringAtom(JSContext *cx, const jschar *chars, size_t length)
803 {
804 JSString str, *str2;
805 JSAtomState *state;
806 JSDHashEntryHdr *hdr;
807
808 JSFLATSTR_INIT(&str, (jschar *)chars, length);
809 state = &cx->runtime->atomState;
810
811 JS_LOCK(cx, &state->lock);
812 hdr = JS_DHashTableOperate(&state->stringAtoms, &str, JS_DHASH_LOOKUP);
813 str2 = JS_DHASH_ENTRY_IS_BUSY(hdr)
814 ? (JSString *)ATOM_ENTRY_KEY(TO_ATOM_ENTRY(hdr))
815 : NULL;
816 JS_UNLOCK(cx, &state->lock);
817
818 return str2 ? (JSAtom *)STRING_TO_JSVAL(str2) : NULL;
819 }
820
821 JSBool
822 js_AtomizePrimitiveValue(JSContext *cx, jsval v, JSAtom **atomp)
823 {
824 JSAtom *atom;
825
826 if (JSVAL_IS_STRING(v)) {
827 atom = js_AtomizeString(cx, JSVAL_TO_STRING(v), 0);
828 if (!atom)
829 return JS_FALSE;
830 } else if (JSVAL_IS_DOUBLE(v)) {
831 atom = js_AtomizeDouble(cx, *JSVAL_TO_DOUBLE(v));
832 if (!atom)
833 return JS_FALSE;
834 } else {
835 JS_ASSERT(JSVAL_IS_INT(v) || JSVAL_IS_BOOLEAN(v) ||
836 JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v));
837 atom = (JSAtom *)v;
838 }
839 *atomp = atom;
840 return JS_TRUE;
841 }
842
843 JSBool
844 js_ValueToStringId(JSContext *cx, jsval v, jsid *idp)
845 {
846 JSString *str;
847 JSAtom *atom;
848
849 /*
850 * Optimize for the common case where v is an already-atomized string. The
851 * comment in jsstr.h before the JSSTRING_SET_ATOMIZED macro's definition
852 * explains why this is thread-safe. The extra rooting via lastAtom (which
853 * would otherwise be done in js_js_AtomizeString) ensures the caller that
854 * the resulting id at is least weakly rooted.
855 */
856 if (JSVAL_IS_STRING(v)) {
857 str = JSVAL_TO_STRING(v);
858 if (JSSTRING_IS_ATOMIZED(str)) {
859 cx->weakRoots.lastAtom = v;
860 *idp = ATOM_TO_JSID((JSAtom *) v);
861 return JS_TRUE;
862 }
863 } else {
864 str = js_ValueToString(cx, v);
865 if (!str)
866 return JS_FALSE;
867 }
868 atom = js_AtomizeString(cx, str, 0);
869 if (!atom)
870 return JS_FALSE;
871 *idp = ATOM_TO_JSID(atom);
872 return JS_TRUE;
873 }
874
875 #ifdef DEBUG
876
877 static JSDHashOperator
878 atom_dumper(JSDHashTable *table, JSDHashEntryHdr *hdr,
879 uint32 number, void *arg)
880 {
881 JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr);
882 FILE *fp = (FILE *)arg;
883 void *key;
884 uintN flags;
885
886 fprintf(fp, "%3u %08x ", number, (uintN)entry->hdr.keyHash);
887 if (entry->keyAndFlags == 0) {
888 fputs("<uninitialized>", fp);
889 } else {
890 key = ATOM_ENTRY_KEY(entry);
891 if (IS_DOUBLE_TABLE(table)) {
892 fprintf(fp, "%.16g", *(jsdouble *)key);
893 } else {
894 JS_ASSERT(IS_STRING_TABLE(table));
895 js_FileEscapedString(fp, (JSString *)key, '"');
896 }
897 flags = ATOM_ENTRY_FLAGS(entry);
898 if (flags != 0) {
899 fputs((flags & (ATOM_PINNED | ATOM_INTERNED))
900 ? " pinned | interned"
901 : (flags & ATOM_PINNED) ? " pinned" : " interned",
902 fp);
903 }
904 }
905 putc('\n', fp);
906 return JS_DHASH_NEXT;
907 }
908
909 JS_FRIEND_API(void)
910 js_DumpAtoms(JSContext *cx, FILE *fp)
911 {
912 JSAtomState *state = &cx->runtime->atomState;
913
914 fprintf(fp, "stringAtoms table contents:\n");
915 JS_DHashTableEnumerate(&state->stringAtoms, atom_dumper, fp);
916 #ifdef JS_DHASHMETER
917 JS_DHashTableDumpMeter(&state->stringAtoms, atom_dumper, fp);
918 #endif
919 putc('\n', fp);
920
921 fprintf(fp, "doubleAtoms table contents:\n");
922 JS_DHashTableEnumerate(&state->doubleAtoms, atom_dumper, fp);
923 #ifdef JS_DHASHMETER
924 JS_DHashTableDumpMeter(&state->doubleAtoms, atom_dumper, fp);
925 #endif
926 putc('\n', fp);
927 }
928
929 #endif
930
931 static JSHashNumber
932 js_hash_atom_ptr(const void *key)
933 {
934 const JSAtom *atom = (const JSAtom *) key;
935 return ATOM_HASH(atom);
936 }
937
938 #if JS_BITS_PER_WORD == 32
939 # define TEMP_SIZE_START_LOG2 5
940 #else
941 # define TEMP_SIZE_START_LOG2 6
942 #endif
943 #define TEMP_SIZE_LIMIT_LOG2 (TEMP_SIZE_START_LOG2 + NUM_TEMP_FREELISTS)
944
945 #define TEMP_SIZE_START JS_BIT(TEMP_SIZE_START_LOG2)
946 #define TEMP_SIZE_LIMIT JS_BIT(TEMP_SIZE_LIMIT_LOG2)
947
948 JS_STATIC_ASSERT(TEMP_SIZE_START >= sizeof(JSHashTable));
949
950 static void *
951 js_alloc_temp_space(void *priv, size_t size)
952 {
953 JSCompiler *jsc = (JSCompiler *) priv;
954
955 void *space;
956 if (size < TEMP_SIZE_LIMIT) {
957 int bin = JS_CeilingLog2(size) - TEMP_SIZE_START_LOG2;
958 JS_ASSERT(unsigned(bin) < NUM_TEMP_FREELISTS);
959
960 space = jsc->tempFreeList[bin];
961 if (space) {
962 jsc->tempFreeList[bin] = *(void **)space;
963 return space;
964 }
965 }
966
967 JS_ARENA_ALLOCATE(space, &jsc->context->tempPool, size);
968 if (!space)
969 js_ReportOutOfScriptQuota(jsc->context);
970 return space;
971 }
972
973 static void
974 js_free_temp_space(void *priv, void *item, size_t size)
975 {
976 if (size >= TEMP_SIZE_LIMIT)
977 return;
978
979 JSCompiler *jsc = (JSCompiler *) priv;
980 int bin = JS_CeilingLog2(size) - TEMP_SIZE_START_LOG2;
981 JS_ASSERT(unsigned(bin) < NUM_TEMP_FREELISTS);
982
983 *(void **)item = jsc->tempFreeList[bin];
984 jsc->tempFreeList[bin] = item;
985 }
986
987 static JSHashEntry *
988 js_alloc_temp_entry(void *priv, const void *key)
989 {
990 JSCompiler *jsc = (JSCompiler *) priv;
991 JSAtomListElement *ale;
992
993 ale = jsc->aleFreeList;
994 if (ale) {
995 jsc->aleFreeList = ALE_NEXT(ale);
996 return &ale->entry;
997 }
998
999 JS_ARENA_ALLOCATE_TYPE(ale, JSAtomListElement, &jsc->context->tempPool);
1000 if (!ale) {
1001 js_ReportOutOfScriptQuota(jsc->context);
1002 return NULL;
1003 }
1004 return &ale->entry;
1005 }
1006
1007 static void
1008 js_free_temp_entry(void *priv, JSHashEntry *he, uintN flag)
1009 {
1010 JSCompiler *jsc = (JSCompiler *) priv;
1011 JSAtomListElement *ale = (JSAtomListElement *) he;
1012
1013 ALE_SET_NEXT(ale, jsc->aleFreeList);
1014 jsc->aleFreeList = ale;
1015 }
1016
1017 static JSHashAllocOps temp_alloc_ops = {
1018 js_alloc_temp_space, js_free_temp_space,
1019 js_alloc_temp_entry, js_free_temp_entry
1020 };
1021
1022 JSAtomListElement *
1023 JSAtomList::rawLookup(JSAtom *atom, JSHashEntry **&hep)
1024 {
1025 JSAtomListElement *ale;
1026
1027 if (table) {
1028 hep = JS_HashTableRawLookup(table, ATOM_HASH(atom), atom);
1029 ale = *hep ? (JSAtomListElement *) *hep : NULL;
1030 } else {
1031 JSHashEntry **alep = &list;
1032 hep = NULL;
1033 while ((ale = (JSAtomListElement *)*alep) != NULL) {
1034 if (ALE_ATOM(ale) == atom) {
1035 /* Hit, move atom's element to the front of the list. */
1036 *alep = ale->entry.next;
1037 ale->entry.next = list;
1038 list = &ale->entry;
1039 break;
1040 }
1041 alep = &ale->entry.next;
1042 }
1043 }
1044 return ale;
1045 }
1046
1047 #define ATOM_LIST_HASH_THRESHOLD 12
1048
1049 JSAtomListElement *
1050 JSAtomList::add(JSCompiler *jsc, JSAtom *atom, AddHow how)
1051 {
1052 JS_ASSERT(!set);
1053
1054 JSAtomListElement *ale, *ale2, *next;
1055 JSHashEntry **hep;
1056
1057 ale = rawLookup(atom, hep);
1058 if (!ale || how != UNIQUE) {
1059 if (count < ATOM_LIST_HASH_THRESHOLD && !table) {
1060 /* Few enough for linear search and no hash table yet needed. */
1061 ale = (JSAtomListElement *)js_alloc_temp_entry(jsc, atom);
1062 if (!ale)
1063 return NULL;
1064 ALE_SET_ATOM(ale, atom);
1065
1066 if (how == HOIST) {
1067 ale->entry.next = NULL;
1068 hep = (JSHashEntry **) &list;
1069 while (*hep)
1070 hep = &(*hep)->next;
1071 *hep = &ale->entry;
1072 } else {
1073 ale->entry.next = list;
1074 list = &ale->entry;
1075 }
1076 } else {
1077 /*
1078 * We should hash, or else we already are hashing, but count was
1079 * reduced by JSAtomList::rawRemove below ATOM_LIST_HASH_THRESHOLD.
1080 * Check whether we should create the table.
1081 */
1082 if (!table) {
1083 /* No hash table yet, so hep had better be null! */
1084 JS_ASSERT(!hep);
1085 table = JS_NewHashTable(count + 1, js_hash_atom_ptr,
1086 JS_CompareValues, JS_CompareValues,
1087 &temp_alloc_ops, jsc);
1088 if (!table)
1089 return NULL;
1090
1091 /*
1092 * Set ht->nentries explicitly, because we are moving entries
1093 * from list to ht, not calling JS_HashTable(Raw|)Add.
1094 */
1095 table->nentries = count;
1096
1097 /*
1098 * Insert each ale on list into the new hash table. Append to
1099 * the hash chain rather than inserting at the bucket head, to
1100 * preserve order among entries with the same key.
1101 */
1102 for (ale2 = (JSAtomListElement *)list; ale2; ale2 = next) {
1103 next = ALE_NEXT(ale2);
1104 ale2->entry.keyHash = ATOM_HASH(ALE_ATOM(ale2));
1105 hep = JS_HashTableRawLookup(table, ale2->entry.keyHash,
1106 ale2->entry.key);
1107 while (*hep)
1108 hep = &(*hep)->next;
1109 *hep = &ale2->entry;
1110 ale2->entry.next = NULL;
1111 }
1112 list = NULL;
1113
1114 /* Set hep for insertion of atom's ale, immediately below. */
1115 hep = JS_HashTableRawLookup(table, ATOM_HASH(atom), atom);
1116 }
1117
1118 /* Finally, add an entry for atom into the hash bucket at hep. */
1119 ale = (JSAtomListElement *)
1120 JS_HashTableRawAdd(table, hep, ATOM_HASH(atom), atom, NULL);
1121 if (!ale)
1122 return NULL;
1123
1124 /*
1125 * If hoisting, move ale to the end of its chain after we called
1126 * JS_HashTableRawAdd, since RawAdd may have grown the table and
1127 * then recomputed hep to refer to the pointer to the first entry
1128 * with the given key.
1129 */
1130 if (how == HOIST && ale->entry.next) {
1131 *hep = ale->entry.next;
1132 ale->entry.next = NULL;
1133 do {
1134 hep = &(*hep)->next;
1135 } while (*hep);
1136 *hep = &ale->entry;
1137 }
1138 }
1139
1140 ALE_SET_INDEX(ale, count++);
1141 }
1142 return ale;
1143 }
1144
1145 void
1146 JSAtomList::rawRemove(JSCompiler *jsc, JSAtomListElement *ale, JSHashEntry **hep)
1147 {
1148 JS_ASSERT(!set);
1149 JS_ASSERT(count != 0);
1150
1151 if (table) {
1152 JS_ASSERT(hep);
1153 JS_HashTableRawRemove(table, hep, &ale->entry);
1154 } else {
1155 JS_ASSERT(!hep);
1156 hep = &list;
1157 while (*hep != &ale->entry) {
1158 JS_ASSERT(*hep);
1159 hep = &(*hep)->next;
1160 }
1161 *hep = ale->entry.next;
1162 js_free_temp_entry(jsc, &ale->entry, HT_FREE_ENTRY);
1163 }
1164
1165 --count;
1166 }
1167
1168 JSAtomListElement *
1169 JSAtomListIterator::operator ()()
1170 {
1171 JSAtomListElement *ale;
1172 JSHashTable *ht;
1173
1174 if (index == uint32(-1))
1175 return NULL;
1176
1177 ale = next;
1178 if (!ale) {
1179 ht = list->table;
1180 if (!ht)
1181 goto done;
1182 do {
1183 if (index == JS_BIT(JS_HASH_BITS - ht->shift))
1184 goto done;
1185 next = (JSAtomListElement *) ht->buckets[index++];
1186 } while (!next);
1187 ale = next;
1188 }
1189
1190 next = ALE_NEXT(ale);
1191 return ale;
1192
1193 done:
1194 index = uint32(-1);
1195 return NULL;
1196 }
1197
1198 static intN
1199 js_map_atom(JSHashEntry *he, intN i, void *arg)
1200 {
1201 JSAtomListElement *ale = (JSAtomListElement *)he;
1202 JSAtom **vector = (JSAtom **) arg;
1203
1204 vector[ALE_INDEX(ale)] = ALE_ATOM(ale);
1205 return HT_ENUMERATE_NEXT;
1206 }
1207
1208 #ifdef DEBUG
1209 static jsrefcount js_atom_map_count;
1210 static jsrefcount js_atom_map_hash_table_count;
1211 #endif
1212
1213 void
1214 js_InitAtomMap(JSContext *cx, JSAtomMap *map, JSAtomList *al)
1215 {
1216 JSAtom **vector;
1217 JSAtomListElement *ale;
1218 uint32 count;
1219
1220 /* Map length must already be initialized. */
1221 JS_ASSERT(al->count == map->length);
1222 #ifdef DEBUG
1223 JS_ATOMIC_INCREMENT(&js_atom_map_count);
1224 #endif
1225 ale = (JSAtomListElement *)al->list;
1226 if (!ale && !al->table) {
1227 JS_ASSERT(!map->vector);
1228 return;
1229 }
1230
1231 count = al->count;
1232 vector = map->vector;
1233 if (al->table) {
1234 #ifdef DEBUG
1235 JS_ATOMIC_INCREMENT(&js_atom_map_hash_table_count);
1236 #endif
1237 JS_HashTableEnumerateEntries(al->table, js_map_atom, vector);
1238 } else {
1239 do {
1240 vector[ALE_INDEX(ale)] = ALE_ATOM(ale);
1241 } while ((ale = ALE_NEXT(ale)) != NULL);
1242 }
1243 al->clear();
1244 }

  ViewVC Help
Powered by ViewVC 1.1.24