source: webkit/trunk/JavaScriptCore/kjs/object.cpp@ 21332

Last change on this file since 21332 was 21332, checked in by eseidel, 18 years ago

2007-05-09 Eric Seidel <[email protected]>

Reviewed by mjs.


http://bugs.webkit.org/show_bug.cgi?id=6985
Cyclic proto values cause WebKit to hang

  • kjs/object.cpp: (KJS::JSObject::put): do a cycle check before setting proto
  • Property svn:eol-style set to native
File size: 18.2 KB
Line 
1// -*- c-basic-offset: 2 -*-
2/*
3 * This file is part of the KDE libraries
4 * Copyright (C) 1999-2001 Harri Porten ([email protected])
5 * Copyright (C) 2001 Peter Kelly ([email protected])
6 * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, Inc.
7 * Copyright (C) 2007 Eric Seidel ([email protected])
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB. If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 *
24 */
25
26#include "config.h"
27#include "object.h"
28
29#include "error_object.h"
30#include "lookup.h"
31#include "nodes.h"
32#include "operations.h"
33#include "PropertyNameArray.h"
34#include <math.h>
35
36// maximum global call stack size. Protects against accidental or
37// malicious infinite recursions. Define to -1 if you want no limit.
38#if PLATFORM(DARWIN) || PLATFORM(WIN_OS)
39// Given OS X stack sizes we run out of stack at about 350 levels.
40// If we improve our stack usage, we can bump this number.
41#define KJS_MAX_STACK 100
42#else
43#define KJS_MAX_STACK 1000
44#endif
45
46#define JAVASCRIPT_CALL_TRACING 0
47#define JAVASCRIPT_MARK_TRACING 0
48
49#if JAVASCRIPT_CALL_TRACING
50static bool _traceJavaScript = false;
51
52extern "C" {
53 void setTraceJavaScript(bool f)
54 {
55 _traceJavaScript = f;
56 }
57
58 static bool traceJavaScript()
59 {
60 return _traceJavaScript;
61 }
62}
63#endif
64
65namespace KJS {
66
67// ------------------------------ Object ---------------------------------------
68
69JSValue *JSObject::call(ExecState *exec, JSObject *thisObj, const List &args)
70{
71 assert(implementsCall());
72
73#if KJS_MAX_STACK > 0
74 static int depth = 0; // sum of all concurrent interpreters
75
76#if JAVASCRIPT_CALL_TRACING
77 static bool tracing = false;
78 if (traceJavaScript() && !tracing) {
79 tracing = true;
80 for (int i = 0; i < depth; i++)
81 putchar (' ');
82 printf ("*** calling: %s\n", toString(exec).ascii());
83 for (int j = 0; j < args.size(); j++) {
84 for (int i = 0; i < depth; i++)
85 putchar (' ');
86 printf ("*** arg[%d] = %s\n", j, args[j]->toString(exec).ascii());
87 }
88 tracing = false;
89 }
90#endif
91
92 if (++depth > KJS_MAX_STACK) {
93 --depth;
94 return throwError(exec, RangeError, "Maximum call stack size exceeded.");
95 }
96#endif
97
98 JSValue *ret = callAsFunction(exec,thisObj,args);
99
100#if KJS_MAX_STACK > 0
101 --depth;
102#endif
103
104#if JAVASCRIPT_CALL_TRACING
105 if (traceJavaScript() && !tracing) {
106 tracing = true;
107 for (int i = 0; i < depth; i++)
108 putchar (' ');
109 printf ("*** returning: %s\n", ret->toString(exec).ascii());
110 tracing = false;
111 }
112#endif
113
114 return ret;
115}
116
117// ------------------------------ JSObject ------------------------------------
118
119void JSObject::mark()
120{
121 JSCell::mark();
122
123#if JAVASCRIPT_MARK_TRACING
124 static int markStackDepth = 0;
125 markStackDepth++;
126 for (int i = 0; i < markStackDepth; i++)
127 putchar('-');
128
129 printf("%s (%p)\n", className().UTF8String().c_str(), this);
130#endif
131
132 JSValue *proto = _proto;
133 if (!proto->marked())
134 proto->mark();
135
136 _prop.mark();
137
138#if JAVASCRIPT_MARK_TRACING
139 markStackDepth--;
140#endif
141}
142
143JSType JSObject::type() const
144{
145 return ObjectType;
146}
147
148const ClassInfo *JSObject::classInfo() const
149{
150 return 0;
151}
152
153UString JSObject::className() const
154{
155 const ClassInfo *ci = classInfo();
156 if ( ci )
157 return ci->className;
158 return "Object";
159}
160
161JSValue *JSObject::get(ExecState *exec, const Identifier &propertyName) const
162{
163 PropertySlot slot;
164
165 if (const_cast<JSObject *>(this)->getPropertySlot(exec, propertyName, slot))
166 return slot.getValue(exec, const_cast<JSObject *>(this), propertyName);
167
168 return jsUndefined();
169}
170
171JSValue *JSObject::get(ExecState *exec, unsigned propertyName) const
172{
173 PropertySlot slot;
174 if (const_cast<JSObject *>(this)->getPropertySlot(exec, propertyName, slot))
175 return slot.getValue(exec, const_cast<JSObject *>(this), propertyName);
176
177 return jsUndefined();
178}
179
180bool JSObject::getPropertySlot(ExecState *exec, unsigned propertyName, PropertySlot& slot)
181{
182 JSObject *imp = this;
183
184 while (true) {
185 if (imp->getOwnPropertySlot(exec, propertyName, slot))
186 return true;
187
188 JSValue *proto = imp->_proto;
189 if (!proto->isObject())
190 break;
191
192 imp = static_cast<JSObject *>(proto);
193 }
194
195 return false;
196}
197
198bool JSObject::getOwnPropertySlot(ExecState *exec, unsigned propertyName, PropertySlot& slot)
199{
200 return getOwnPropertySlot(exec, Identifier::from(propertyName), slot);
201}
202
203static void throwSetterError(ExecState *exec)
204{
205 throwError(exec, TypeError, "setting a property that has only a getter");
206}
207
208// ECMA 8.6.2.2
209void JSObject::put(ExecState* exec, const Identifier &propertyName, JSValue *value, int attr)
210{
211 assert(value);
212
213 // non-standard netscape extension
214 if (propertyName == exec->propertyNames().underscoreProto) {
215 JSObject* proto = value->getObject();
216 while (proto) {
217 if (proto == this)
218 throwError(exec, GeneralError, "cyclic __proto__ value");
219 proto = proto->prototype() ? proto->prototype()->getObject() : 0;
220 }
221
222 setPrototype(value);
223 return;
224 }
225
226 /* TODO: check for write permissions directly w/o this call */
227 /* Doesn't look very easy with the PropertyMap API - David */
228 // putValue() is used for JS assignemnts. It passes no attribute.
229 // Assume that a C++ implementation knows what it is doing
230 // and let it override the canPut() check.
231 if ((attr == None || attr == DontDelete) && !canPut(exec,propertyName)) {
232#ifdef KJS_VERBOSE
233 fprintf( stderr, "WARNING: canPut %s said NO\n", propertyName.ascii() );
234#endif
235 return;
236 }
237
238 // Check if there are any setters or getters in the prototype chain
239 JSObject *obj = this;
240 bool hasGettersOrSetters = false;
241 while (true) {
242 if (obj->_prop.hasGetterSetterProperties()) {
243 hasGettersOrSetters = true;
244 break;
245 }
246
247 if (!obj->_proto->isObject())
248 break;
249
250 obj = static_cast<JSObject *>(obj->_proto);
251 }
252
253 if (hasGettersOrSetters) {
254 obj = this;
255 while (true) {
256 unsigned attributes;
257 if (JSValue *gs = obj->_prop.get(propertyName, attributes)) {
258 if (attributes & GetterSetter) {
259 JSObject *setterFunc = static_cast<GetterSetterImp *>(gs)->getSetter();
260
261 if (!setterFunc) {
262 throwSetterError(exec);
263 return;
264 }
265
266 List args;
267 args.append(value);
268
269 setterFunc->call(exec, this, args);
270 return;
271 } else {
272 // If there's an existing property on the object or one of its
273 // prototype it should be replaced, so we just break here.
274 break;
275 }
276 }
277
278 if (!obj->_proto->isObject())
279 break;
280
281 obj = static_cast<JSObject *>(obj->_proto);
282 }
283 }
284
285 _prop.put(propertyName,value,attr);
286}
287
288void JSObject::put(ExecState *exec, unsigned propertyName,
289 JSValue *value, int attr)
290{
291 put(exec, Identifier::from(propertyName), value, attr);
292}
293
294// ECMA 8.6.2.3
295bool JSObject::canPut(ExecState *, const Identifier &propertyName) const
296{
297 unsigned attributes;
298
299 // Don't look in the prototype here. We can always put an override
300 // in the object, even if the prototype has a ReadOnly property.
301
302 if (!getPropertyAttributes(propertyName, attributes))
303 return true;
304 else
305 return !(attributes & ReadOnly);
306}
307
308// ECMA 8.6.2.4
309bool JSObject::hasProperty(ExecState *exec, const Identifier &propertyName) const
310{
311 PropertySlot slot;
312 return const_cast<JSObject *>(this)->getPropertySlot(exec, propertyName, slot);
313}
314
315bool JSObject::hasProperty(ExecState *exec, unsigned propertyName) const
316{
317 PropertySlot slot;
318 return const_cast<JSObject *>(this)->getPropertySlot(exec, propertyName, slot);
319}
320
321// ECMA 8.6.2.5
322bool JSObject::deleteProperty(ExecState* /*exec*/, const Identifier &propertyName)
323{
324 unsigned attributes;
325 JSValue *v = _prop.get(propertyName, attributes);
326 if (v) {
327 if ((attributes & DontDelete))
328 return false;
329 _prop.remove(propertyName);
330 if (attributes & GetterSetter)
331 _prop.setHasGetterSetterProperties(_prop.containsGettersOrSetters());
332 return true;
333 }
334
335 // Look in the static hashtable of properties
336 const HashEntry* entry = findPropertyHashEntry(propertyName);
337 if (entry && entry->attr & DontDelete)
338 return false; // this builtin property can't be deleted
339 return true;
340}
341
342bool JSObject::deleteProperty(ExecState *exec, unsigned propertyName)
343{
344 return deleteProperty(exec, Identifier::from(propertyName));
345}
346
347static ALWAYS_INLINE JSValue *tryGetAndCallProperty(ExecState *exec, const JSObject *object, const Identifier &propertyName) {
348 JSValue *v = object->get(exec, propertyName);
349 if (v->isObject()) {
350 JSObject *o = static_cast<JSObject*>(v);
351 if (o->implementsCall()) { // spec says "not primitive type" but ...
352 JSObject *thisObj = const_cast<JSObject*>(object);
353 JSValue *def = o->call(exec, thisObj, List::empty());
354 JSType defType = def->type();
355 ASSERT(defType != GetterSetterType);
356 if (defType != ObjectType)
357 return def;
358 }
359 }
360 return NULL;
361}
362
363// ECMA 8.6.2.6
364JSValue* JSObject::defaultValue(ExecState* exec, JSType hint) const
365{
366 Identifier firstPropertyName;
367 Identifier secondPropertyName;
368 /* Prefer String for Date objects */
369 if ((hint == StringType) || (hint != StringType) && (hint != NumberType) && (_proto == exec->lexicalInterpreter()->builtinDatePrototype())) {
370 firstPropertyName = exec->propertyNames().toString;
371 secondPropertyName = exec->propertyNames().valueOf;
372 } else {
373 firstPropertyName = exec->propertyNames().valueOf;
374 secondPropertyName = exec->propertyNames().toString;
375 }
376
377 JSValue *v;
378 if ((v = tryGetAndCallProperty(exec, this, firstPropertyName)))
379 return v;
380 if ((v = tryGetAndCallProperty(exec, this, secondPropertyName)))
381 return v;
382
383 if (exec->hadException())
384 return exec->exception();
385
386 return throwError(exec, TypeError, "No default value");
387}
388
389const HashEntry* JSObject::findPropertyHashEntry(const Identifier& propertyName) const
390{
391 for (const ClassInfo *info = classInfo(); info; info = info->parentClass) {
392 if (const HashTable *propHashTable = info->propHashTable) {
393 if (const HashEntry *e = Lookup::findEntry(propHashTable, propertyName))
394 return e;
395 }
396 }
397 return 0;
398}
399
400void JSObject::defineGetter(ExecState*, const Identifier& propertyName, JSObject* getterFunc)
401{
402 JSValue *o = getDirect(propertyName);
403 GetterSetterImp *gs;
404
405 if (o && o->type() == GetterSetterType) {
406 gs = static_cast<GetterSetterImp *>(o);
407 } else {
408 gs = new GetterSetterImp;
409 putDirect(propertyName, gs, GetterSetter);
410 }
411
412 _prop.setHasGetterSetterProperties(true);
413 gs->setGetter(getterFunc);
414}
415
416void JSObject::defineSetter(ExecState*, const Identifier& propertyName, JSObject* setterFunc)
417{
418 JSValue *o = getDirect(propertyName);
419 GetterSetterImp *gs;
420
421 if (o && o->type() == GetterSetterType) {
422 gs = static_cast<GetterSetterImp *>(o);
423 } else {
424 gs = new GetterSetterImp;
425 putDirect(propertyName, gs, GetterSetter);
426 }
427
428 _prop.setHasGetterSetterProperties(true);
429 gs->setSetter(setterFunc);
430}
431
432bool JSObject::implementsConstruct() const
433{
434 return false;
435}
436
437JSObject* JSObject::construct(ExecState*, const List& /*args*/)
438{
439 assert(false);
440 return NULL;
441}
442
443JSObject* JSObject::construct(ExecState* exec, const List& args, const Identifier& /*functionName*/, const UString& /*sourceURL*/, int /*lineNumber*/)
444{
445 return construct(exec, args);
446}
447
448bool JSObject::implementsCall() const
449{
450 return false;
451}
452
453JSValue *JSObject::callAsFunction(ExecState* /*exec*/, JSObject* /*thisObj*/, const List &/*args*/)
454{
455 assert(false);
456 return NULL;
457}
458
459bool JSObject::implementsHasInstance() const
460{
461 return false;
462}
463
464bool JSObject::hasInstance(ExecState* exec, JSValue* value)
465{
466 JSValue* proto = get(exec, exec->propertyNames().prototype);
467 if (!proto->isObject()) {
468 throwError(exec, TypeError, "intanceof called on an object with an invalid prototype property.");
469 return false;
470 }
471
472 if (!value->isObject())
473 return false;
474
475 JSObject* o = static_cast<JSObject*>(value);
476 while ((o = o->prototype()->getObject())) {
477 if (o == proto)
478 return true;
479 }
480 return false;
481}
482
483bool JSObject::propertyIsEnumerable(ExecState*, const Identifier& propertyName) const
484{
485 unsigned attributes;
486
487 if (!getPropertyAttributes(propertyName, attributes))
488 return false;
489 else
490 return !(attributes & DontEnum);
491}
492
493bool JSObject::getPropertyAttributes(const Identifier& propertyName, unsigned& attributes) const
494{
495 if (_prop.get(propertyName, attributes))
496 return true;
497
498 // Look in the static hashtable of properties
499 const HashEntry* e = findPropertyHashEntry(propertyName);
500 if (e) {
501 attributes = e->attr;
502 return true;
503 }
504
505 return false;
506}
507
508void JSObject::getPropertyNames(ExecState* exec, PropertyNameArray& propertyNames)
509{
510 _prop.getEnumerablePropertyNames(propertyNames);
511
512 // Add properties from the static hashtable of properties
513 const ClassInfo *info = classInfo();
514 while (info) {
515 if (info->propHashTable) {
516 int size = info->propHashTable->size;
517 const HashEntry *e = info->propHashTable->entries;
518 for (int i = 0; i < size; ++i, ++e) {
519 if (e->s && !(e->attr & DontEnum))
520 propertyNames.add(e->s);
521 }
522 }
523 info = info->parentClass;
524 }
525 if (_proto->isObject())
526 static_cast<JSObject*>(_proto)->getPropertyNames(exec, propertyNames);
527}
528
529bool JSObject::toBoolean(ExecState*) const
530{
531 return true;
532}
533
534double JSObject::toNumber(ExecState *exec) const
535{
536 JSValue *prim = toPrimitive(exec,NumberType);
537 if (exec->hadException()) // should be picked up soon in nodes.cpp
538 return 0.0;
539 return prim->toNumber(exec);
540}
541
542UString JSObject::toString(ExecState *exec) const
543{
544 JSValue *prim = toPrimitive(exec,StringType);
545 if (exec->hadException()) // should be picked up soon in nodes.cpp
546 return "";
547 return prim->toString(exec);
548}
549
550JSObject *JSObject::toObject(ExecState*) const
551{
552 return const_cast<JSObject*>(this);
553}
554
555void JSObject::putDirect(const Identifier &propertyName, JSValue *value, int attr)
556{
557 _prop.put(propertyName, value, attr);
558}
559
560void JSObject::putDirect(const Identifier &propertyName, int value, int attr)
561{
562 _prop.put(propertyName, jsNumber(value), attr);
563}
564
565void JSObject::putDirectFunction(InternalFunctionImp* func, int attr)
566{
567 putDirect(func->functionName(), func, attr);
568}
569
570void JSObject::fillGetterPropertySlot(PropertySlot& slot, JSValue **location)
571{
572 GetterSetterImp *gs = static_cast<GetterSetterImp *>(*location);
573 JSObject *getterFunc = gs->getGetter();
574 if (getterFunc)
575 slot.setGetterSlot(this, getterFunc);
576 else
577 slot.setUndefined(this);
578}
579
580// ------------------------------ Error ----------------------------------------
581
582const char * const errorNamesArr[] = {
583 I18N_NOOP("Error"), // GeneralError
584 I18N_NOOP("Evaluation error"), // EvalError
585 I18N_NOOP("Range error"), // RangeError
586 I18N_NOOP("Reference error"), // ReferenceError
587 I18N_NOOP("Syntax error"), // SyntaxError
588 I18N_NOOP("Type error"), // TypeError
589 I18N_NOOP("URI error"), // URIError
590};
591
592const char * const * const Error::errorNames = errorNamesArr;
593
594JSObject *Error::create(ExecState *exec, ErrorType errtype, const UString &message,
595 int lineno, int sourceId, const UString &sourceURL)
596{
597 JSObject *cons;
598 switch (errtype) {
599 case EvalError:
600 cons = exec->lexicalInterpreter()->builtinEvalError();
601 break;
602 case RangeError:
603 cons = exec->lexicalInterpreter()->builtinRangeError();
604 break;
605 case ReferenceError:
606 cons = exec->lexicalInterpreter()->builtinReferenceError();
607 break;
608 case SyntaxError:
609 cons = exec->lexicalInterpreter()->builtinSyntaxError();
610 break;
611 case TypeError:
612 cons = exec->lexicalInterpreter()->builtinTypeError();
613 break;
614 case URIError:
615 cons = exec->lexicalInterpreter()->builtinURIError();
616 break;
617 default:
618 cons = exec->lexicalInterpreter()->builtinError();
619 break;
620 }
621
622 List args;
623 if (message.isEmpty())
624 args.append(jsString(errorNames[errtype]));
625 else
626 args.append(jsString(message));
627 JSObject *err = static_cast<JSObject *>(cons->construct(exec,args));
628
629 if (lineno != -1)
630 err->put(exec, "line", jsNumber(lineno));
631 if (sourceId != -1)
632 err->put(exec, "sourceId", jsNumber(sourceId));
633
634 if(!sourceURL.isNull())
635 err->put(exec, "sourceURL", jsString(sourceURL));
636
637 return err;
638
639/*
640#ifndef NDEBUG
641 const char *msg = err->get(messagePropertyName)->toString().value().ascii();
642 if (l >= 0)
643 fprintf(stderr, "KJS: %s at line %d. %s\n", estr, l, msg);
644 else
645 fprintf(stderr, "KJS: %s. %s\n", estr, msg);
646#endif
647
648 return err;
649*/
650}
651
652JSObject *Error::create(ExecState *exec, ErrorType type, const char *message)
653{
654 return create(exec, type, message, -1, -1, NULL);
655}
656
657JSObject *throwError(ExecState *exec, ErrorType type)
658{
659 JSObject *error = Error::create(exec, type, UString(), -1, -1, NULL);
660 exec->setException(error);
661 return error;
662}
663
664JSObject *throwError(ExecState *exec, ErrorType type, const UString &message)
665{
666 JSObject *error = Error::create(exec, type, message, -1, -1, NULL);
667 exec->setException(error);
668 return error;
669}
670
671JSObject *throwError(ExecState *exec, ErrorType type, const char *message)
672{
673 JSObject *error = Error::create(exec, type, message, -1, -1, NULL);
674 exec->setException(error);
675 return error;
676}
677
678JSObject *throwError(ExecState *exec, ErrorType type, const UString &message, int line, int sourceId, const UString &sourceURL)
679{
680 JSObject *error = Error::create(exec, type, message, line, sourceId, sourceURL);
681 exec->setException(error);
682 return error;
683}
684
685} // namespace KJS
Note: See TracBrowser for help on using the repository browser.