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 "jscntxt.h" |
53 |
#include "jsversion.h" |
54 |
#include "jsgc.h" |
55 |
#include "jslock.h" |
56 |
#include "jsnum.h" |
57 |
#include "jsscan.h" |
58 |
#include "jsstr.h" |
59 |
|
60 |
const char * |
61 |
js_AtomToPrintableString(JSContext *cx, JSAtom *atom) |
62 |
{ |
63 |
return js_ValueToPrintableString(cx, ATOM_KEY(atom)); |
64 |
} |
65 |
|
66 |
#define JS_PROTO(name,code,init) const char js_##name##_str[] = #name; |
67 |
#include "jsproto.tbl" |
68 |
#undef JS_PROTO |
69 |
|
70 |
/* |
71 |
* Names for common atoms defined in JSAtomState starting from |
72 |
* JSAtomState.emptyAtom until JSAtomState.lazy. |
73 |
* |
74 |
* The elements of the array after the first empty string define strings |
75 |
* corresponding to JSType enumerators from jspubtd.h and to two boolean |
76 |
* literals, false and true. The following assert insists that JSType defines |
77 |
* exactly 8 types. |
78 |
*/ |
79 |
JS_STATIC_ASSERT(JSTYPE_LIMIT == 8); |
80 |
const char *const js_common_atom_names[] = { |
81 |
"", /* emptyAtom */ |
82 |
js_undefined_str, /* typeAtoms[JSTYPE_VOID] */ |
83 |
js_object_str, /* typeAtoms[JSTYPE_OBJECT] */ |
84 |
js_function_str, /* typeAtoms[JSTYPE_FUNCTION] */ |
85 |
"string", /* typeAtoms[JSTYPE_STRING] */ |
86 |
"number", /* typeAtoms[JSTYPE_NUMBER] */ |
87 |
"boolean", /* typeAtoms[JSTYPE_BOOLEAN] */ |
88 |
js_null_str, /* typeAtoms[JSTYPE_NULL] */ |
89 |
"xml", /* typeAtoms[JSTYPE_XML] */ |
90 |
js_false_str, /* booleanAtoms[0] */ |
91 |
js_true_str, /* booleanAtoms[1] */ |
92 |
js_null_str, /* nullAtom */ |
93 |
|
94 |
#define JS_PROTO(name,code,init) js_##name##_str, |
95 |
#include "jsproto.tbl" |
96 |
#undef JS_PROTO |
97 |
|
98 |
js_anonymous_str, /* anonymousAtom */ |
99 |
js_arguments_str, /* argumentsAtom */ |
100 |
js_arity_str, /* arityAtom */ |
101 |
js_callee_str, /* calleeAtom */ |
102 |
js_caller_str, /* callerAtom */ |
103 |
js_class_prototype_str, /* classPrototypeAtom */ |
104 |
js_constructor_str, /* constructorAtom */ |
105 |
js_count_str, /* countAtom */ |
106 |
js_each_str, /* eachAtom */ |
107 |
js_eval_str, /* evalAtom */ |
108 |
js_fileName_str, /* fileNameAtom */ |
109 |
js_get_str, /* getAtom */ |
110 |
js_getter_str, /* getterAtom */ |
111 |
js_index_str, /* indexAtom */ |
112 |
js_input_str, /* inputAtom */ |
113 |
js_iterator_str, /* iteratorAtom */ |
114 |
js_length_str, /* lengthAtom */ |
115 |
js_lineNumber_str, /* lineNumberAtom */ |
116 |
js_message_str, /* messageAtom */ |
117 |
js_name_str, /* nameAtom */ |
118 |
js_next_str, /* nextAtom */ |
119 |
js_noSuchMethod_str, /* noSuchMethodAtom */ |
120 |
js_parent_str, /* parentAtom */ |
121 |
js_proto_str, /* protoAtom */ |
122 |
js_set_str, /* setAtom */ |
123 |
js_setter_str, /* setterAtom */ |
124 |
js_stack_str, /* stackAtom */ |
125 |
js_toLocaleString_str, /* toLocaleStringAtom */ |
126 |
js_toSource_str, /* toSourceAtom */ |
127 |
js_toString_str, /* toStringAtom */ |
128 |
js_valueOf_str, /* valueOfAtom */ |
129 |
js_toJSON_str, /* toJSONAtom */ |
130 |
"(void 0)", /* void0Atom */ |
131 |
|
132 |
#if JS_HAS_XML_SUPPORT |
133 |
js_etago_str, /* etagoAtom */ |
134 |
js_namespace_str, /* namespaceAtom */ |
135 |
js_ptagc_str, /* ptagcAtom */ |
136 |
js_qualifier_str, /* qualifierAtom */ |
137 |
js_space_str, /* spaceAtom */ |
138 |
js_stago_str, /* stagoAtom */ |
139 |
js_star_str, /* starAtom */ |
140 |
js_starQualifier_str, /* starQualifierAtom */ |
141 |
js_tagc_str, /* tagcAtom */ |
142 |
js_xml_str, /* xmlAtom */ |
143 |
#endif |
144 |
|
145 |
#ifdef NARCISSUS |
146 |
js_call_str, /* callAtom */ |
147 |
js_construct_str, /* constructAtom */ |
148 |
js_hasInstance_str, /* hasInstanceAtom */ |
149 |
js_ExecutionContext_str, /* ExecutionContextAtom */ |
150 |
js_current_str, /* currentAtom */ |
151 |
#endif |
152 |
}; |
153 |
JS_STATIC_ASSERT(JS_ARRAY_LENGTH(js_common_atom_names) * sizeof(JSAtom *) == |
154 |
LAZY_ATOM_OFFSET_START - ATOM_OFFSET_START); |
155 |
|
156 |
const char js_anonymous_str[] = "anonymous"; |
157 |
const char js_arguments_str[] = "arguments"; |
158 |
const char js_arity_str[] = "arity"; |
159 |
const char js_callee_str[] = "callee"; |
160 |
const char js_caller_str[] = "caller"; |
161 |
const char js_class_prototype_str[] = "prototype"; |
162 |
const char js_constructor_str[] = "constructor"; |
163 |
const char js_count_str[] = "__count__"; |
164 |
const char js_each_str[] = "each"; |
165 |
const char js_eval_str[] = "eval"; |
166 |
const char js_fileName_str[] = "fileName"; |
167 |
const char js_get_str[] = "get"; |
168 |
const char js_getter_str[] = "getter"; |
169 |
const char js_index_str[] = "index"; |
170 |
const char js_input_str[] = "input"; |
171 |
const char js_iterator_str[] = "__iterator__"; |
172 |
const char js_length_str[] = "length"; |
173 |
const char js_lineNumber_str[] = "lineNumber"; |
174 |
const char js_message_str[] = "message"; |
175 |
const char js_name_str[] = "name"; |
176 |
const char js_next_str[] = "next"; |
177 |
const char js_noSuchMethod_str[] = "__noSuchMethod__"; |
178 |
const char js_object_str[] = "object"; |
179 |
const char js_parent_str[] = "__parent__"; |
180 |
const char js_proto_str[] = "__proto__"; |
181 |
const char js_setter_str[] = "setter"; |
182 |
const char js_set_str[] = "set"; |
183 |
const char js_stack_str[] = "stack"; |
184 |
const char js_toSource_str[] = "toSource"; |
185 |
const char js_toString_str[] = "toString"; |
186 |
const char js_toLocaleString_str[] = "toLocaleString"; |
187 |
const char js_undefined_str[] = "undefined"; |
188 |
const char js_valueOf_str[] = "valueOf"; |
189 |
const char js_toJSON_str[] = "toJSON"; |
190 |
|
191 |
#if JS_HAS_XML_SUPPORT |
192 |
const char js_etago_str[] = "</"; |
193 |
const char js_namespace_str[] = "namespace"; |
194 |
const char js_ptagc_str[] = "/>"; |
195 |
const char js_qualifier_str[] = "::"; |
196 |
const char js_space_str[] = " "; |
197 |
const char js_stago_str[] = "<"; |
198 |
const char js_star_str[] = "*"; |
199 |
const char js_starQualifier_str[] = "*::"; |
200 |
const char js_tagc_str[] = ">"; |
201 |
const char js_xml_str[] = "xml"; |
202 |
#endif |
203 |
|
204 |
#if JS_HAS_GENERATORS |
205 |
const char js_close_str[] = "close"; |
206 |
const char js_send_str[] = "send"; |
207 |
#endif |
208 |
|
209 |
#ifdef NARCISSUS |
210 |
const char js_call_str[] = "__call__"; |
211 |
const char js_construct_str[] = "__construct__"; |
212 |
const char js_hasInstance_str[] = "__hasInstance__"; |
213 |
const char js_ExecutionContext_str[] = "ExecutionContext"; |
214 |
const char js_current_str[] = "current"; |
215 |
#endif |
216 |
|
217 |
/* |
218 |
* JSAtomState.doubleAtoms and JSAtomState.stringAtoms hashtable entry. To |
219 |
* support pinned and interned string atoms, we use the lowest bits of the |
220 |
* keyAndFlags field to store ATOM_PINNED and ATOM_INTERNED flags. |
221 |
*/ |
222 |
typedef struct JSAtomHashEntry { |
223 |
JSDHashEntryHdr hdr; |
224 |
jsuword keyAndFlags; |
225 |
} JSAtomHashEntry; |
226 |
|
227 |
#define ATOM_ENTRY_FLAG_MASK (ATOM_PINNED | ATOM_INTERNED) |
228 |
|
229 |
JS_STATIC_ASSERT(ATOM_ENTRY_FLAG_MASK < JSVAL_ALIGN); |
230 |
|
231 |
/* |
232 |
* Helper macros to access and modify JSAtomHashEntry. |
233 |
*/ |
234 |
#define TO_ATOM_ENTRY(hdr) ((JSAtomHashEntry *) hdr) |
235 |
#define ATOM_ENTRY_KEY(entry) \ |
236 |
((void *)((entry)->keyAndFlags & ~ATOM_ENTRY_FLAG_MASK)) |
237 |
#define ATOM_ENTRY_FLAGS(entry) \ |
238 |
((uintN)((entry)->keyAndFlags & ATOM_ENTRY_FLAG_MASK)) |
239 |
#define INIT_ATOM_ENTRY(entry, key) \ |
240 |
((void)((entry)->keyAndFlags = (jsuword)(key))) |
241 |
#define ADD_ATOM_ENTRY_FLAGS(entry, flags) \ |
242 |
((void)((entry)->keyAndFlags |= (jsuword)(flags))) |
243 |
#define CLEAR_ATOM_ENTRY_FLAGS(entry, flags) \ |
244 |
((void)((entry)->keyAndFlags &= ~(jsuword)(flags))) |
245 |
|
246 |
static JSDHashNumber |
247 |
HashDouble(JSDHashTable *table, const void *key); |
248 |
|
249 |
static JSBool |
250 |
MatchDouble(JSDHashTable *table, const JSDHashEntryHdr *hdr, const void *key); |
251 |
|
252 |
static JSDHashNumber |
253 |
HashString(JSDHashTable *table, const void *key); |
254 |
|
255 |
static JSBool |
256 |
MatchString(JSDHashTable *table, const JSDHashEntryHdr *hdr, const void *key); |
257 |
|
258 |
static const JSDHashTableOps DoubleHashOps = { |
259 |
JS_DHashAllocTable, |
260 |
JS_DHashFreeTable, |
261 |
HashDouble, |
262 |
MatchDouble, |
263 |
JS_DHashMoveEntryStub, |
264 |
JS_DHashClearEntryStub, |
265 |
JS_DHashFinalizeStub, |
266 |
NULL |
267 |
}; |
268 |
|
269 |
static const JSDHashTableOps StringHashOps = { |
270 |
JS_DHashAllocTable, |
271 |
JS_DHashFreeTable, |
272 |
HashString, |
273 |
MatchString, |
274 |
JS_DHashMoveEntryStub, |
275 |
JS_DHashClearEntryStub, |
276 |
JS_DHashFinalizeStub, |
277 |
NULL |
278 |
}; |
279 |
|
280 |
#define IS_DOUBLE_TABLE(table) ((table)->ops == &DoubleHashOps) |
281 |
#define IS_STRING_TABLE(table) ((table)->ops == &StringHashOps) |
282 |
|
283 |
#define IS_INITIALIZED_STATE(state) IS_DOUBLE_TABLE(&(state)->doubleAtoms) |
284 |
|
285 |
static JSDHashNumber |
286 |
HashDouble(JSDHashTable *table, const void *key) |
287 |
{ |
288 |
jsdouble d; |
289 |
|
290 |
JS_ASSERT(IS_DOUBLE_TABLE(table)); |
291 |
d = *(jsdouble *)key; |
292 |
return JSDOUBLE_HI32(d) ^ JSDOUBLE_LO32(d); |
293 |
} |
294 |
|
295 |
static JSDHashNumber |
296 |
HashString(JSDHashTable *table, const void *key) |
297 |
{ |
298 |
JS_ASSERT(IS_STRING_TABLE(table)); |
299 |
return js_HashString((JSString *)key); |
300 |
} |
301 |
|
302 |
static JSBool |
303 |
MatchDouble(JSDHashTable *table, const JSDHashEntryHdr *hdr, const void *key) |
304 |
{ |
305 |
JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr); |
306 |
jsdouble d1, d2; |
307 |
|
308 |
JS_ASSERT(IS_DOUBLE_TABLE(table)); |
309 |
if (entry->keyAndFlags == 0) { |
310 |
/* See comments in MatchString. */ |
311 |
return JS_FALSE; |
312 |
} |
313 |
|
314 |
d1 = *(jsdouble *)ATOM_ENTRY_KEY(entry); |
315 |
d2 = *(jsdouble *)key; |
316 |
if (JSDOUBLE_IS_NaN(d1)) |
317 |
return JSDOUBLE_IS_NaN(d2); |
318 |
#if defined(XP_WIN) |
319 |
/* XXX MSVC miscompiles such that (NaN == 0) */ |
320 |
if (JSDOUBLE_IS_NaN(d2)) |
321 |
return JS_FALSE; |
322 |
#endif |
323 |
return d1 == d2; |
324 |
} |
325 |
|
326 |
static JSBool |
327 |
MatchString(JSDHashTable *table, const JSDHashEntryHdr *hdr, const void *key) |
328 |
{ |
329 |
JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr); |
330 |
|
331 |
JS_ASSERT(IS_STRING_TABLE(table)); |
332 |
if (entry->keyAndFlags == 0) { |
333 |
/* |
334 |
* This happens when js_AtomizeString adds a new hash entry and |
335 |
* releases the lock but before it takes the lock the second time to |
336 |
* initialize keyAndFlags for the entry. |
337 |
* |
338 |
* We always return false for such entries so JS_DHashTableOperate |
339 |
* never finds them. We clean them during GC's sweep phase. |
340 |
* |
341 |
* It means that with a contested lock or when GC is triggered outside |
342 |
* the lock we may end up adding two entries, but this is a price for |
343 |
* simpler code. |
344 |
*/ |
345 |
return JS_FALSE; |
346 |
} |
347 |
return js_EqualStrings((JSString *)ATOM_ENTRY_KEY(entry), (JSString *)key); |
348 |
} |
349 |
|
350 |
/* |
351 |
* For a browser build from 2007-08-09 after the browser starts up there are |
352 |
* just 55 double atoms, but over 15000 string atoms. Not to penalize more |
353 |
* economical embeddings allocating too much memory initially we initialize |
354 |
* atomized strings with just 1K entries. |
355 |
*/ |
356 |
#define JS_STRING_HASH_COUNT 1024 |
357 |
#define JS_DOUBLE_HASH_COUNT 64 |
358 |
|
359 |
JSBool |
360 |
js_InitAtomState(JSRuntime *rt) |
361 |
{ |
362 |
JSAtomState *state = &rt->atomState; |
363 |
|
364 |
/* |
365 |
* The caller must zero the state before calling this function. |
366 |
*/ |
367 |
JS_ASSERT(!state->stringAtoms.ops); |
368 |
JS_ASSERT(!state->doubleAtoms.ops); |
369 |
|
370 |
if (!JS_DHashTableInit(&state->stringAtoms, &StringHashOps, |
371 |
NULL, sizeof(JSAtomHashEntry), |
372 |
JS_DHASH_DEFAULT_CAPACITY(JS_STRING_HASH_COUNT))) { |
373 |
state->stringAtoms.ops = NULL; |
374 |
return JS_FALSE; |
375 |
} |
376 |
JS_ASSERT(IS_STRING_TABLE(&state->stringAtoms)); |
377 |
|
378 |
if (!JS_DHashTableInit(&state->doubleAtoms, &DoubleHashOps, |
379 |
NULL, sizeof(JSAtomHashEntry), |
380 |
JS_DHASH_DEFAULT_CAPACITY(JS_DOUBLE_HASH_COUNT))) { |
381 |
state->doubleAtoms.ops = NULL; |
382 |
JS_DHashTableFinish(&state->stringAtoms); |
383 |
state->stringAtoms.ops = NULL; |
384 |
return JS_FALSE; |
385 |
} |
386 |
JS_ASSERT(IS_DOUBLE_TABLE(&state->doubleAtoms)); |
387 |
|
388 |
#ifdef JS_THREADSAFE |
389 |
js_InitLock(&state->lock); |
390 |
#endif |
391 |
JS_ASSERT(IS_INITIALIZED_STATE(state)); |
392 |
return JS_TRUE; |
393 |
} |
394 |
|
395 |
static JSDHashOperator |
396 |
js_string_uninterner(JSDHashTable *table, JSDHashEntryHdr *hdr, |
397 |
uint32 number, void *arg) |
398 |
{ |
399 |
JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr); |
400 |
JSRuntime *rt = (JSRuntime *)arg; |
401 |
JSString *str; |
402 |
|
403 |
/* |
404 |
* Any string entry that remains at this point must be initialized, as the |
405 |
* last GC should clean any uninitialized ones. |
406 |
*/ |
407 |
JS_ASSERT(IS_STRING_TABLE(table)); |
408 |
JS_ASSERT(entry->keyAndFlags != 0); |
409 |
str = (JSString *)ATOM_ENTRY_KEY(entry); |
410 |
|
411 |
/* Pass null as context. */ |
412 |
js_FinalizeStringRT(rt, str, js_GetExternalStringGCType(str), NULL); |
413 |
return JS_DHASH_NEXT; |
414 |
} |
415 |
|
416 |
void |
417 |
js_FinishAtomState(JSRuntime *rt) |
418 |
{ |
419 |
JSAtomState *state = &rt->atomState; |
420 |
|
421 |
if (!IS_INITIALIZED_STATE(state)) { |
422 |
/* |
423 |
* We are called with uninitialized state when JS_NewRuntime fails and |
424 |
* calls JS_DestroyRuntime on a partially initialized runtime. |
425 |
*/ |
426 |
return; |
427 |
} |
428 |
|
429 |
JS_DHashTableEnumerate(&state->stringAtoms, js_string_uninterner, rt); |
430 |
JS_DHashTableFinish(&state->stringAtoms); |
431 |
JS_DHashTableFinish(&state->doubleAtoms); |
432 |
|
433 |
#ifdef JS_THREADSAFE |
434 |
js_FinishLock(&state->lock); |
435 |
#endif |
436 |
#ifdef DEBUG |
437 |
memset(state, JS_FREE_PATTERN, sizeof *state); |
438 |
#endif |
439 |
} |
440 |
|
441 |
JSBool |
442 |
js_InitCommonAtoms(JSContext *cx) |
443 |
{ |
444 |
JSAtomState *state = &cx->runtime->atomState; |
445 |
uintN i; |
446 |
JSAtom **atoms; |
447 |
|
448 |
atoms = COMMON_ATOMS_START(state); |
449 |
for (i = 0; i < JS_ARRAY_LENGTH(js_common_atom_names); i++, atoms++) { |
450 |
*atoms = js_Atomize(cx, js_common_atom_names[i], |
451 |
strlen(js_common_atom_names[i]), ATOM_PINNED); |
452 |
if (!*atoms) |
453 |
return JS_FALSE; |
454 |
} |
455 |
JS_ASSERT((uint8 *)atoms - (uint8 *)state == LAZY_ATOM_OFFSET_START); |
456 |
memset(atoms, 0, ATOM_OFFSET_LIMIT - LAZY_ATOM_OFFSET_START); |
457 |
|
458 |
return JS_TRUE; |
459 |
} |
460 |
|
461 |
static JSDHashOperator |
462 |
js_atom_unpinner(JSDHashTable *table, JSDHashEntryHdr *hdr, |
463 |
uint32 number, void *arg) |
464 |
{ |
465 |
JS_ASSERT(IS_STRING_TABLE(table)); |
466 |
CLEAR_ATOM_ENTRY_FLAGS(TO_ATOM_ENTRY(hdr), ATOM_PINNED); |
467 |
return JS_DHASH_NEXT; |
468 |
} |
469 |
|
470 |
void |
471 |
js_FinishCommonAtoms(JSContext *cx) |
472 |
{ |
473 |
JSAtomState *state = &cx->runtime->atomState; |
474 |
|
475 |
JS_DHashTableEnumerate(&state->stringAtoms, js_atom_unpinner, NULL); |
476 |
#ifdef DEBUG |
477 |
memset(COMMON_ATOMS_START(state), JS_FREE_PATTERN, |
478 |
ATOM_OFFSET_LIMIT - ATOM_OFFSET_START); |
479 |
#endif |
480 |
} |
481 |
|
482 |
static JSDHashOperator |
483 |
js_locked_atom_tracer(JSDHashTable *table, JSDHashEntryHdr *hdr, |
484 |
uint32 number, void *arg) |
485 |
{ |
486 |
JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr); |
487 |
JSTracer *trc = (JSTracer *)arg; |
488 |
|
489 |
if (entry->keyAndFlags == 0) { |
490 |
/* Ignore uninitialized entries during tracing. */ |
491 |
return JS_DHASH_NEXT; |
492 |
} |
493 |
JS_SET_TRACING_INDEX(trc, "locked_atom", (size_t)number); |
494 |
JS_CallTracer(trc, ATOM_ENTRY_KEY(entry), |
495 |
IS_STRING_TABLE(table) ? JSTRACE_STRING : JSTRACE_DOUBLE); |
496 |
return JS_DHASH_NEXT; |
497 |
} |
498 |
|
499 |
static JSDHashOperator |
500 |
js_pinned_atom_tracer(JSDHashTable *table, JSDHashEntryHdr *hdr, |
501 |
uint32 number, void *arg) |
502 |
{ |
503 |
JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr); |
504 |
JSTracer *trc = (JSTracer *)arg; |
505 |
uintN flags = ATOM_ENTRY_FLAGS(entry); |
506 |
|
507 |
JS_ASSERT(IS_STRING_TABLE(table)); |
508 |
if (flags & (ATOM_PINNED | ATOM_INTERNED)) { |
509 |
JS_SET_TRACING_INDEX(trc, |
510 |
flags & ATOM_PINNED |
511 |
? "pinned_atom" |
512 |
: "interned_atom", |
513 |
(size_t)number); |
514 |
JS_CallTracer(trc, ATOM_ENTRY_KEY(entry), JSTRACE_STRING); |
515 |
} |
516 |
return JS_DHASH_NEXT; |
517 |
} |
518 |
|
519 |
void |
520 |
js_TraceAtomState(JSTracer *trc, JSBool allAtoms) |
521 |
{ |
522 |
JSAtomState *state; |
523 |
|
524 |
state = &trc->context->runtime->atomState; |
525 |
if (allAtoms) { |
526 |
JS_DHashTableEnumerate(&state->doubleAtoms, js_locked_atom_tracer, trc); |
527 |
JS_DHashTableEnumerate(&state->stringAtoms, js_locked_atom_tracer, trc); |
528 |
} else { |
529 |
JS_DHashTableEnumerate(&state->stringAtoms, js_pinned_atom_tracer, trc); |
530 |
} |
531 |
} |
532 |
|
533 |
static JSDHashOperator |
534 |
js_atom_sweeper(JSDHashTable *table, JSDHashEntryHdr *hdr, |
535 |
uint32 number, void *arg) |
536 |
{ |
537 |
JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr); |
538 |
JSContext *cx = (JSContext *)arg; |
539 |
|
540 |
/* Remove uninitialized entries. */ |
541 |
if (entry->keyAndFlags == 0) |
542 |
return JS_DHASH_REMOVE; |
543 |
|
544 |
if (ATOM_ENTRY_FLAGS(entry) & (ATOM_PINNED | ATOM_INTERNED)) { |
545 |
/* Pinned or interned key cannot be finalized. */ |
546 |
JS_ASSERT(!js_IsAboutToBeFinalized(cx, ATOM_ENTRY_KEY(entry))); |
547 |
} else if (js_IsAboutToBeFinalized(cx, ATOM_ENTRY_KEY(entry))) { |
548 |
/* Remove entries with things about to be GC'ed. */ |
549 |
return JS_DHASH_REMOVE; |
550 |
} |
551 |
return JS_DHASH_NEXT; |
552 |
} |
553 |
|
554 |
void |
555 |
js_SweepAtomState(JSContext *cx) |
556 |
{ |
557 |
JSAtomState *state = &cx->runtime->atomState; |
558 |
|
559 |
JS_DHashTableEnumerate(&state->doubleAtoms, js_atom_sweeper, cx); |
560 |
JS_DHashTableEnumerate(&state->stringAtoms, js_atom_sweeper, cx); |
561 |
|
562 |
/* |
563 |
* Optimize for simplicity and mutate table generation numbers even if the |
564 |
* sweeper has not removed any entries. |
565 |
*/ |
566 |
state->doubleAtoms.generation++; |
567 |
state->stringAtoms.generation++; |
568 |
} |
569 |
|
570 |
JSAtom * |
571 |
js_AtomizeDouble(JSContext *cx, jsdouble d) |
572 |
{ |
573 |
JSAtomState *state; |
574 |
JSDHashTable *table; |
575 |
JSAtomHashEntry *entry; |
576 |
uint32 gen; |
577 |
jsdouble *key; |
578 |
jsval v; |
579 |
|
580 |
state = &cx->runtime->atomState; |
581 |
table = &state->doubleAtoms; |
582 |
|
583 |
JS_LOCK(cx, &state->lock); |
584 |
entry = TO_ATOM_ENTRY(JS_DHashTableOperate(table, &d, JS_DHASH_ADD)); |
585 |
if (!entry) |
586 |
goto failed_hash_add; |
587 |
if (entry->keyAndFlags == 0) { |
588 |
gen = ++table->generation; |
589 |
JS_UNLOCK(cx, &state->lock); |
590 |
|
591 |
key = js_NewWeaklyRootedDouble(cx, d); |
592 |
if (!key) |
593 |
return NULL; |
594 |
|
595 |
JS_LOCK(cx, &state->lock); |
596 |
if (table->generation == gen) { |
597 |
JS_ASSERT(entry->keyAndFlags == 0); |
598 |
} else { |
599 |
entry = TO_ATOM_ENTRY(JS_DHashTableOperate(table, key, |
600 |
JS_DHASH_ADD)); |
601 |
if (!entry) |
602 |
goto failed_hash_add; |
603 |
if (entry->keyAndFlags != 0) |
604 |
goto finish; |
605 |
++table->generation; |
606 |
} |
607 |
INIT_ATOM_ENTRY(entry, key); |
608 |
} |
609 |
|
610 |
finish: |
611 |
v = DOUBLE_TO_JSVAL((jsdouble *)ATOM_ENTRY_KEY(entry)); |
612 |
cx->weakRoots.lastAtom = v; |
613 |
JS_UNLOCK(cx, &state->lock); |
614 |
|
615 |
return (JSAtom *)v; |
616 |
|
617 |
failed_hash_add: |
618 |
JS_UNLOCK(cx, &state->lock); |
619 |
JS_ReportOutOfMemory(cx); |
620 |
return NULL; |
621 |
} |
622 |
|
623 |
JSAtom * |
624 |
js_AtomizeString(JSContext *cx, JSString *str, uintN flags) |
625 |
{ |
626 |
jsval v; |
627 |
JSAtomState *state; |
628 |
JSDHashTable *table; |
629 |
JSAtomHashEntry *entry; |
630 |
JSString *key; |
631 |
uint32 gen; |
632 |
|
633 |
JS_ASSERT(!(flags & ~(ATOM_PINNED|ATOM_INTERNED|ATOM_TMPSTR|ATOM_NOCOPY))); |
634 |
JS_ASSERT_IF(flags & ATOM_NOCOPY, flags & ATOM_TMPSTR); |
635 |
|
636 |
state = &cx->runtime->atomState; |
637 |
table = &state->stringAtoms; |
638 |
|
639 |
JS_LOCK(cx, &state->lock); |
640 |
entry = TO_ATOM_ENTRY(JS_DHashTableOperate(table, str, JS_DHASH_ADD)); |
641 |
if (!entry) |
642 |
goto failed_hash_add; |
643 |
if (entry->keyAndFlags != 0) { |
644 |
key = (JSString *)ATOM_ENTRY_KEY(entry); |
645 |
} else { |
646 |
/* |
647 |
* We created a new hashtable entry. Unless str is already allocated |
648 |
* from the GC heap and flat, we have to release state->lock as |
649 |
* string construction is a complex operation. For example, it can |
650 |
* trigger GC which may rehash the table and make the entry invalid. |
651 |
*/ |
652 |
++table->generation; |
653 |
if (!(flags & ATOM_TMPSTR) && JSSTRING_IS_FLAT(str)) { |
654 |
JSFLATSTR_CLEAR_MUTABLE(str); |
655 |
key = str; |
656 |
} else { |
657 |
gen = table->generation; |
658 |
JS_UNLOCK(cx, &state->lock); |
659 |
|
660 |
if (flags & ATOM_TMPSTR) { |
661 |
if (flags & ATOM_NOCOPY) { |
662 |
key = js_NewString(cx, JSFLATSTR_CHARS(str), |
663 |
JSFLATSTR_LENGTH(str)); |
664 |
if (!key) |
665 |
return NULL; |
666 |
|
667 |
/* Finish handing off chars to the GC'ed key string. */ |
668 |
str->u.chars = NULL; |
669 |
} else { |
670 |
key = js_NewStringCopyN(cx, JSFLATSTR_CHARS(str), |
671 |
JSFLATSTR_LENGTH(str)); |
672 |
if (!key) |
673 |
return NULL; |
674 |
} |
675 |
} else { |
676 |
JS_ASSERT(JSSTRING_IS_DEPENDENT(str)); |
677 |
if (!js_UndependString(cx, str)) |
678 |
return NULL; |
679 |
key = str; |
680 |
} |
681 |
|
682 |
JS_LOCK(cx, &state->lock); |
683 |
if (table->generation == gen) { |
684 |
JS_ASSERT(entry->keyAndFlags == 0); |
685 |
} else { |
686 |
entry = TO_ATOM_ENTRY(JS_DHashTableOperate(table, key, |
687 |
JS_DHASH_ADD)); |
688 |
if (!entry) |
689 |
goto failed_hash_add; |
690 |
if (entry->keyAndFlags != 0) { |
691 |
key = (JSString *)ATOM_ENTRY_KEY(entry); |
692 |
goto finish; |
693 |
} |
694 |
++table->generation; |
695 |
} |
696 |
} |
697 |
INIT_ATOM_ENTRY(entry, key); |
698 |
JSFLATSTR_SET_ATOMIZED(key); |
699 |
} |
700 |
|
701 |
finish: |
702 |
ADD_ATOM_ENTRY_FLAGS(entry, flags & (ATOM_PINNED | ATOM_INTERNED)); |
703 |
JS_ASSERT(JSSTRING_IS_ATOMIZED(key)); |
704 |
v = STRING_TO_JSVAL(key); |
705 |
cx->weakRoots.lastAtom = v; |
706 |
JS_UNLOCK(cx, &state->lock); |
707 |
return (JSAtom *)v; |
708 |
|
709 |
failed_hash_add: |
710 |
JS_UNLOCK(cx, &state->lock); |
711 |
JS_ReportOutOfMemory(cx); |
712 |
return NULL; |
713 |
} |
714 |
|
715 |
JSAtom * |
716 |
js_Atomize(JSContext *cx, const char *bytes, size_t length, uintN flags) |
717 |
{ |
718 |
jschar *chars; |
719 |
JSString str; |
720 |
JSAtom *atom; |
721 |
|
722 |
/* |
723 |
* Avoiding the malloc in js_InflateString on shorter strings saves us |
724 |
* over 20,000 malloc calls on mozilla browser startup. This compares to |
725 |
* only 131 calls where the string is longer than a 31 char (net) buffer. |
726 |
* The vast majority of atomized strings are already in the hashtable. So |
727 |
* js_AtomizeString rarely has to copy the temp string we make. |
728 |
*/ |
729 |
#define ATOMIZE_BUF_MAX 32 |
730 |
jschar inflated[ATOMIZE_BUF_MAX]; |
731 |
size_t inflatedLength = ATOMIZE_BUF_MAX - 1; |
732 |
|
733 |
if (length < ATOMIZE_BUF_MAX) { |
734 |
js_InflateStringToBuffer(cx, bytes, length, inflated, &inflatedLength); |
735 |
inflated[inflatedLength] = 0; |
736 |
chars = inflated; |
737 |
} else { |
738 |
inflatedLength = length; |
739 |
chars = js_InflateString(cx, bytes, &inflatedLength); |
740 |
if (!chars) |
741 |
return NULL; |
742 |
flags |= ATOM_NOCOPY; |
743 |
} |
744 |
|
745 |
JSFLATSTR_INIT(&str, (jschar *)chars, inflatedLength); |
746 |
atom = js_AtomizeString(cx, &str, ATOM_TMPSTR | flags); |
747 |
if (chars != inflated && str.u.chars) |
748 |
JS_free(cx, chars); |
749 |
return atom; |
750 |
} |
751 |
|
752 |
JSAtom * |
753 |
js_AtomizeChars(JSContext *cx, const jschar *chars, size_t length, uintN flags) |
754 |
{ |
755 |
JSString str; |
756 |
|
757 |
JSFLATSTR_INIT(&str, (jschar *)chars, length); |
758 |
return js_AtomizeString(cx, &str, ATOM_TMPSTR | flags); |
759 |
} |
760 |
|
761 |
JSAtom * |
762 |
js_GetExistingStringAtom(JSContext *cx, const jschar *chars, size_t length) |
763 |
{ |
764 |
JSString str, *str2; |
765 |
JSAtomState *state; |
766 |
JSDHashEntryHdr *hdr; |
767 |
|
768 |
JSFLATSTR_INIT(&str, (jschar *)chars, length); |
769 |
state = &cx->runtime->atomState; |
770 |
|
771 |
JS_LOCK(cx, &state->lock); |
772 |
hdr = JS_DHashTableOperate(&state->stringAtoms, &str, JS_DHASH_LOOKUP); |
773 |
str2 = JS_DHASH_ENTRY_IS_BUSY(hdr) |
774 |
? (JSString *)ATOM_ENTRY_KEY(TO_ATOM_ENTRY(hdr)) |
775 |
: NULL; |
776 |
JS_UNLOCK(cx, &state->lock); |
777 |
|
778 |
return str2 ? (JSAtom *)STRING_TO_JSVAL(str2) : NULL; |
779 |
} |
780 |
|
781 |
JSBool |
782 |
js_AtomizePrimitiveValue(JSContext *cx, jsval v, JSAtom **atomp) |
783 |
{ |
784 |
JSAtom *atom; |
785 |
|
786 |
if (JSVAL_IS_STRING(v)) { |
787 |
atom = js_AtomizeString(cx, JSVAL_TO_STRING(v), 0); |
788 |
if (!atom) |
789 |
return JS_FALSE; |
790 |
} else if (JSVAL_IS_DOUBLE(v)) { |
791 |
atom = js_AtomizeDouble(cx, *JSVAL_TO_DOUBLE(v)); |
792 |
if (!atom) |
793 |
return JS_FALSE; |
794 |
} else { |
795 |
JS_ASSERT(JSVAL_IS_INT(v) || JSVAL_IS_BOOLEAN(v) || |
796 |
JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)); |
797 |
atom = (JSAtom *)v; |
798 |
} |
799 |
*atomp = atom; |
800 |
return JS_TRUE; |
801 |
} |
802 |
|
803 |
JSBool |
804 |
js_ValueToStringId(JSContext *cx, jsval v, jsid *idp) |
805 |
{ |
806 |
JSString *str; |
807 |
JSAtom *atom; |
808 |
|
809 |
/* |
810 |
* Optimize for the common case where v is an already-atomized string. The |
811 |
* comment in jsstr.h before the JSSTRING_SET_ATOMIZED macro's definition |
812 |
* explains why this is thread-safe. The extra rooting via lastAtom (which |
813 |
* would otherwise be done in js_js_AtomizeString) ensures the caller that |
814 |
* the resulting id at is least weakly rooted. |
815 |
*/ |
816 |
if (JSVAL_IS_STRING(v)) { |
817 |
str = JSVAL_TO_STRING(v); |
818 |
if (JSSTRING_IS_ATOMIZED(str)) { |
819 |
cx->weakRoots.lastAtom = v; |
820 |
*idp = ATOM_TO_JSID((JSAtom *) v); |
821 |
return JS_TRUE; |
822 |
} |
823 |
} else { |
824 |
str = js_ValueToString(cx, v); |
825 |
if (!str) |
826 |
return JS_FALSE; |
827 |
} |
828 |
atom = js_AtomizeString(cx, str, 0); |
829 |
if (!atom) |
830 |
return JS_FALSE; |
831 |
*idp = ATOM_TO_JSID(atom); |
832 |
return JS_TRUE; |
833 |
} |
834 |
|
835 |
#ifdef DEBUG |
836 |
|
837 |
static JSDHashOperator |
838 |
atom_dumper(JSDHashTable *table, JSDHashEntryHdr *hdr, |
839 |
uint32 number, void *arg) |
840 |
{ |
841 |
JSAtomHashEntry *entry = TO_ATOM_ENTRY(hdr); |
842 |
FILE *fp = (FILE *)arg; |
843 |
void *key; |
844 |
uintN flags; |
845 |
|
846 |
fprintf(fp, "%3u %08x ", number, (uintN)entry->hdr.keyHash); |
847 |
if (entry->keyAndFlags == 0) { |
848 |
fputs("<uninitialized>", fp); |
849 |
} else { |
850 |
key = ATOM_ENTRY_KEY(entry); |
851 |
if (IS_DOUBLE_TABLE(table)) { |
852 |
fprintf(fp, "%.16g", *(jsdouble *)key); |
853 |
} else { |
854 |
JS_ASSERT(IS_STRING_TABLE(table)); |
855 |
js_FileEscapedString(fp, (JSString *)key, '"'); |
856 |
} |
857 |
flags = ATOM_ENTRY_FLAGS(entry); |
858 |
if (flags != 0) { |
859 |
fputs((flags & (ATOM_PINNED | ATOM_INTERNED)) |
860 |
? " pinned | interned" |
861 |
: (flags & ATOM_PINNED) ? " pinned" : " interned", |
862 |
fp); |
863 |
} |
864 |
} |
865 |
putc('\n', fp); |
866 |
return JS_DHASH_NEXT; |
867 |
} |
868 |
|
869 |
JS_FRIEND_API(void) |
870 |
js_DumpAtoms(JSContext *cx, FILE *fp) |
871 |
{ |
872 |
JSAtomState *state = &cx->runtime->atomState; |
873 |
|
874 |
fprintf(fp, "stringAtoms table contents:\n"); |
875 |
JS_DHashTableEnumerate(&state->stringAtoms, atom_dumper, fp); |
876 |
#ifdef JS_DHASHMETER |
877 |
JS_DHashTableDumpMeter(&state->stringAtoms, atom_dumper, fp); |
878 |
#endif |
879 |
putc('\n', fp); |
880 |
|
881 |
fprintf(fp, "doubleAtoms table contents:\n"); |
882 |
JS_DHashTableEnumerate(&state->doubleAtoms, atom_dumper, fp); |
883 |
#ifdef JS_DHASHMETER |
884 |
JS_DHashTableDumpMeter(&state->doubleAtoms, atom_dumper, fp); |
885 |
#endif |
886 |
putc('\n', fp); |
887 |
} |
888 |
|
889 |
#endif |
890 |
|
891 |
static JSHashNumber |
892 |
js_hash_atom_ptr(const void *key) |
893 |
{ |
894 |
const JSAtom *atom = (const JSAtom *) key; |
895 |
return ATOM_HASH(atom); |
896 |
} |
897 |
|
898 |
static void * |
899 |
js_alloc_temp_space(void *priv, size_t size) |
900 |
{ |
901 |
JSContext *cx = (JSContext *) priv; |
902 |
void *space; |
903 |
|
904 |
JS_ARENA_ALLOCATE(space, &cx->tempPool, size); |
905 |
if (!space) |
906 |
js_ReportOutOfScriptQuota(cx); |
907 |
return space; |
908 |
} |
909 |
|
910 |
static void |
911 |
js_free_temp_space(void *priv, void *item) |
912 |
{ |
913 |
} |
914 |
|
915 |
static JSHashEntry * |
916 |
js_alloc_temp_entry(void *priv, const void *key) |
917 |
{ |
918 |
JSContext *cx = (JSContext *) priv; |
919 |
JSAtomListElement *ale; |
920 |
|
921 |
JS_ARENA_ALLOCATE_TYPE(ale, JSAtomListElement, &cx->tempPool); |
922 |
if (!ale) { |
923 |
js_ReportOutOfScriptQuota(cx); |
924 |
return NULL; |
925 |
} |
926 |
return &ale->entry; |
927 |
} |
928 |
|
929 |
static void |
930 |
js_free_temp_entry(void *priv, JSHashEntry *he, uintN flag) |
931 |
{ |
932 |
} |
933 |
|
934 |
static JSHashAllocOps temp_alloc_ops = { |
935 |
js_alloc_temp_space, js_free_temp_space, |
936 |
js_alloc_temp_entry, js_free_temp_entry |
937 |
}; |
938 |
|
939 |
JSAtomListElement * |
940 |
js_IndexAtom(JSContext *cx, JSAtom *atom, JSAtomList *al) |
941 |
{ |
942 |
JSAtomListElement *ale, *ale2, *next; |
943 |
JSHashEntry **hep; |
944 |
|
945 |
ATOM_LIST_LOOKUP(ale, hep, al, atom); |
946 |
if (!ale) { |
947 |
if (al->count < 10) { |
948 |
/* Few enough for linear search, no hash table needed. */ |
949 |
JS_ASSERT(!al->table); |
950 |
ale = (JSAtomListElement *)js_alloc_temp_entry(cx, atom); |
951 |
if (!ale) |
952 |
return NULL; |
953 |
ALE_SET_ATOM(ale, atom); |
954 |
ale->entry.next = al->list; |
955 |
al->list = &ale->entry; |
956 |
} else { |
957 |
/* We want to hash. Have we already made a hash table? */ |
958 |
if (!al->table) { |
959 |
/* No hash table yet, so hep had better be null! */ |
960 |
JS_ASSERT(!hep); |
961 |
al->table = JS_NewHashTable(al->count + 1, js_hash_atom_ptr, |
962 |
JS_CompareValues, JS_CompareValues, |
963 |
&temp_alloc_ops, cx); |
964 |
if (!al->table) |
965 |
return NULL; |
966 |
|
967 |
/* |
968 |
* Set ht->nentries explicitly, because we are moving entries |
969 |
* from al to ht, not calling JS_HashTable(Raw|)Add. |
970 |
*/ |
971 |
al->table->nentries = al->count; |
972 |
|
973 |
/* Insert each ale on al->list into the new hash table. */ |
974 |
for (ale2 = (JSAtomListElement *)al->list; ale2; ale2 = next) { |
975 |
next = ALE_NEXT(ale2); |
976 |
ale2->entry.keyHash = ATOM_HASH(ALE_ATOM(ale2)); |
977 |
hep = JS_HashTableRawLookup(al->table, ale2->entry.keyHash, |
978 |
ale2->entry.key); |
979 |
ale2->entry.next = *hep; |
980 |
*hep = &ale2->entry; |
981 |
} |
982 |
al->list = NULL; |
983 |
|
984 |
/* Set hep for insertion of atom's ale, immediately below. */ |
985 |
hep = JS_HashTableRawLookup(al->table, ATOM_HASH(atom), atom); |
986 |
} |
987 |
|
988 |
/* Finally, add an entry for atom into the hash bucket at hep. */ |
989 |
ale = (JSAtomListElement *) |
990 |
JS_HashTableRawAdd(al->table, hep, ATOM_HASH(atom), atom, |
991 |
NULL); |
992 |
if (!ale) |
993 |
return NULL; |
994 |
} |
995 |
|
996 |
ALE_SET_INDEX(ale, al->count++); |
997 |
} |
998 |
return ale; |
999 |
} |
1000 |
|
1001 |
static intN |
1002 |
js_map_atom(JSHashEntry *he, intN i, void *arg) |
1003 |
{ |
1004 |
JSAtomListElement *ale = (JSAtomListElement *)he; |
1005 |
JSAtom **vector = (JSAtom **) arg; |
1006 |
|
1007 |
vector[ALE_INDEX(ale)] = ALE_ATOM(ale); |
1008 |
return HT_ENUMERATE_NEXT; |
1009 |
} |
1010 |
|
1011 |
#ifdef DEBUG |
1012 |
static jsrefcount js_atom_map_count; |
1013 |
static jsrefcount js_atom_map_hash_table_count; |
1014 |
#endif |
1015 |
|
1016 |
void |
1017 |
js_InitAtomMap(JSContext *cx, JSAtomMap *map, JSAtomList *al) |
1018 |
{ |
1019 |
JSAtom **vector; |
1020 |
JSAtomListElement *ale; |
1021 |
uint32 count; |
1022 |
|
1023 |
/* Map length must already be initialized. */ |
1024 |
JS_ASSERT(al->count == map->length); |
1025 |
#ifdef DEBUG |
1026 |
JS_ATOMIC_INCREMENT(&js_atom_map_count); |
1027 |
#endif |
1028 |
ale = (JSAtomListElement *)al->list; |
1029 |
if (!ale && !al->table) { |
1030 |
JS_ASSERT(!map->vector); |
1031 |
return; |
1032 |
} |
1033 |
|
1034 |
count = al->count; |
1035 |
vector = map->vector; |
1036 |
if (al->table) { |
1037 |
#ifdef DEBUG |
1038 |
JS_ATOMIC_INCREMENT(&js_atom_map_hash_table_count); |
1039 |
#endif |
1040 |
JS_HashTableEnumerateEntries(al->table, js_map_atom, vector); |
1041 |
} else { |
1042 |
do { |
1043 |
vector[ALE_INDEX(ale)] = ALE_ATOM(ale); |
1044 |
} while ((ale = ALE_NEXT(ale)) != NULL); |
1045 |
} |
1046 |
ATOM_LIST_INIT(al); |
1047 |
} |