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