Fix major JavaScript memory leak. run-plt says cvs-base improved
[WebKit-https.git] / JavaScriptCore / kjs / array_object.cpp
1 // -*- c-basic-offset: 2 -*-
2 /*
3  *  This file is part of the KDE libraries
4  *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21
22 #include "value.h"
23 #include "object.h"
24 #include "types.h"
25 #include "interpreter.h"
26 #include "operations.h"
27 #include "array_object.h"
28 #include "internal.h"
29 #include "error_object.h"
30
31 #include "array_object.lut.h"
32
33 #include <stdio.h>
34 #include <assert.h>
35
36 using namespace KJS;
37
38 // ------------------------------ ArrayInstanceImp -----------------------------
39
40 const ClassInfo ArrayInstanceImp::info = {"Array", 0, 0, 0};
41
42 ArrayInstanceImp::ArrayInstanceImp(const Object &proto, unsigned initialLength)
43   : ObjectImp(proto)
44   , length(initialLength)
45   , capacity(length)
46   , storage(length ? new (ValueImp *)[length] : 0)
47 {
48   unsigned l = length;
49   for (unsigned i = 0; i < l; ++i) {
50     storage[i] = Undefined().imp();
51   }
52 }
53
54 ArrayInstanceImp::ArrayInstanceImp(const Object &proto, const List &list)
55   : ObjectImp(proto)
56   , length(list.size())
57   , capacity(length)
58   , storage(length ? new (ValueImp *)[length] : 0)
59 {
60   ListIterator it = list.begin();
61   const unsigned l = length;
62   for (unsigned i = 0; i < l; ++i) {
63     storage[i] = (it++).imp();
64   }
65 }
66
67 ArrayInstanceImp::~ArrayInstanceImp()
68 {
69   delete [] storage;
70 }
71
72 Value ArrayInstanceImp::get(ExecState *exec, const UString &propertyName) const
73 {
74   if (propertyName == lengthPropertyName)
75     return Number(length);
76
77   bool ok;
78   unsigned index = propertyName.toULong(&ok);
79   if (ok) {
80     if (index >= length)
81       return Undefined();
82     return Value(storage[index]);
83   }
84
85   return ObjectImp::get(exec, propertyName);
86 }
87
88 Value ArrayInstanceImp::get(ExecState *exec, unsigned index) const
89 {
90   if (index >= length)
91     return Undefined();
92   return Value(storage[index]);
93 }
94
95 // Special implementation of [[Put]] - see ECMA 15.4.5.1
96 void ArrayInstanceImp::put(ExecState *exec, const UString &propertyName, const Value &value, int attr)
97 {
98   if (propertyName == lengthPropertyName) {
99     setLength(value.toUInt32(exec));
100     return;
101   }
102   
103   bool ok;
104   unsigned index = propertyName.toULong(&ok);
105   if (ok) {
106     setLength(index + 1);
107     storage[index] = value.imp();
108     return;
109   }
110   
111   ObjectImp::put(exec, propertyName, value, attr);
112 }
113
114 void ArrayInstanceImp::put(ExecState *exec, unsigned index, const Value &value, int attr)
115 {
116   setLength(index + 1);
117   storage[index] = value.imp();
118 }
119
120 bool ArrayInstanceImp::hasProperty(ExecState *exec, const UString &propertyName) const
121 {
122   if (propertyName == lengthPropertyName)
123     return true;
124   
125   bool ok;
126   unsigned index = propertyName.toULong(&ok);
127   if (ok) {
128     if (index >= length)
129       return false;
130     return storage[index]->type() != UndefinedType;
131   }
132   
133   return ObjectImp::hasProperty(exec, propertyName);
134 }
135
136 bool ArrayInstanceImp::hasProperty(ExecState *exec, unsigned index) const
137 {
138   if (index >= length)
139     return false;
140   return storage[index]->type() != UndefinedType;
141 }
142
143 bool ArrayInstanceImp::deleteProperty(ExecState *exec, const UString &propertyName)
144 {
145   if (propertyName == lengthPropertyName)
146     return false;
147   
148   bool ok;
149   unsigned index = propertyName.toULong(&ok);
150   if (ok) {
151     if (index >= length)
152       return true;
153     storage[index] = Undefined().imp();
154     return true;
155   }
156   
157   return ObjectImp::deleteProperty(exec, propertyName);
158 }
159
160 bool ArrayInstanceImp::deleteProperty(ExecState *exec, unsigned index)
161 {
162   if (index >= length)
163     return true;
164   storage[index] = Undefined().imp();
165   return true;
166 }
167
168 void ArrayInstanceImp::setLength(unsigned newLength)
169 {
170   if (newLength < length) {
171     const unsigned l = length;
172     for (unsigned i = newLength; i < l; ++i)
173       storage[i] = Undefined().imp();
174   }
175   if (newLength > capacity) {
176     unsigned newCapacity = (newLength * 3 + 1) / 2;
177     ValueImp **newStorage = new (ValueImp *)[newCapacity];
178     const unsigned l = length;
179     for (unsigned i = 0; i < l; ++i)
180       newStorage[i] = storage[i];
181     for (unsigned i = l; i < newLength; i++)
182       newStorage[i] = Undefined().imp();
183     delete [] storage;
184     storage = newStorage;
185     capacity = newCapacity;
186   }
187   length = newLength;
188 }
189
190 void ArrayInstanceImp::mark()
191 {
192   ObjectImp::mark();
193   const unsigned l = length;
194   for (unsigned i = 0; i < l; ++i) {
195     ValueImp *imp = storage[i];
196     if (!imp->marked())
197       imp->mark();
198   }
199 }
200
201 // ------------------------------ ArrayPrototypeImp ----------------------------
202
203 const ClassInfo ArrayPrototypeImp::info = {"Array", &ArrayInstanceImp::info, &arrayTable, 0};
204
205 /* Source for array_object.lut.h
206 @begin arrayTable 13
207   toString       ArrayProtoFuncImp::ToString       DontEnum|Function 0
208   toLocaleString ArrayProtoFuncImp::ToLocaleString DontEnum|Function 0
209   concat         ArrayProtoFuncImp::Concat         DontEnum|Function 1
210   join           ArrayProtoFuncImp::Join           DontEnum|Function 1
211   pop            ArrayProtoFuncImp::Pop            DontEnum|Function 0
212   push           ArrayProtoFuncImp::Push           DontEnum|Function 1
213   reverse        ArrayProtoFuncImp::Reverse        DontEnum|Function 0
214   shift          ArrayProtoFuncImp::Shift          DontEnum|Function 0
215   slice          ArrayProtoFuncImp::Slice          DontEnum|Function 2
216   sort           ArrayProtoFuncImp::Sort           DontEnum|Function 1
217   splice         ArrayProtoFuncImp::Splice         DontEnum|Function 2
218   unshift        ArrayProtoFuncImp::UnShift        DontEnum|Function 1
219 @end
220 */
221
222 // ECMA 15.4.4
223 ArrayPrototypeImp::ArrayPrototypeImp(ExecState *exec,
224                                      ObjectPrototypeImp *objProto)
225   : ArrayInstanceImp(Object(objProto), 0)
226 {
227   Value protect(this);
228   setInternalValue(Null());
229 }
230
231 Value ArrayPrototypeImp::get(ExecState *exec, const UString &propertyName) const
232 {
233   //fprintf( stderr, "ArrayPrototypeImp::get(%s)\n", propertyName.ascii() );
234   return lookupGetFunction<ArrayProtoFuncImp, ArrayInstanceImp>( exec, propertyName, &arrayTable, this );
235 }
236
237 // ------------------------------ ArrayProtoFuncImp ----------------------------
238
239 ArrayProtoFuncImp::ArrayProtoFuncImp(ExecState *exec, int i, int len)
240   : InternalFunctionImp(
241     static_cast<FunctionPrototypeImp*>(exec->interpreter()->builtinFunctionPrototype().imp())
242     ), id(i)
243 {
244   Value protect(this);
245   put(exec,lengthPropertyName,Number(len),DontDelete|ReadOnly|DontEnum);
246 }
247
248 bool ArrayProtoFuncImp::implementsCall() const
249 {
250   return true;
251 }
252
253 // ECMA 15.4.4
254 Value ArrayProtoFuncImp::call(ExecState *exec, Object &thisObj, const List &args)
255 {
256   unsigned int length = thisObj.get(exec,lengthPropertyName).toUInt32(exec);
257
258   Value result;
259   switch (id) {
260   case ToLocaleString:
261     // TODO  - see 15.4.4.3
262     // fall through
263   case ToString:
264
265     if (!thisObj.inherits(&ArrayInstanceImp::info)) {
266       Object err = Error::create(exec,TypeError);
267       exec->setException(err);
268       return err;
269     }
270
271     // fall through
272
273   case Join: {
274     UString separator = ",";
275     UString str = "";
276
277     if (args.size() > 0)
278       separator = args[0].toString(exec);
279     for (unsigned int k = 0; k < length; k++) {
280       if (k >= 1)
281         str += separator;
282       Value element = thisObj.get(exec,k);
283       if (element.type() != UndefinedType && element.type() != NullType)
284         str += element.toString(exec);
285     }
286     result = String(str);
287     break;
288   }
289   case Concat: {
290     Object arr = Object::dynamicCast(exec->interpreter()->builtinArray().construct(exec,List::empty()));
291     int n = 0;
292     Value curArg = thisObj;
293     Object curObj = Object::dynamicCast(thisObj);
294     ListIterator it = args.begin();
295     for (;;) {
296       if (curArg.type() == ObjectType &&
297           curObj.inherits(&ArrayInstanceImp::info)) {
298         unsigned int k = 0;
299         if (n > 0)
300           length = curObj.get(exec,lengthPropertyName).toUInt32(exec);
301         while (k < length) {
302           if (curObj.hasProperty(exec,k))
303             arr.put(exec, n, curObj.get(exec, k));
304           n++;
305           k++;
306         }
307       } else {
308         arr.put(exec, n, curArg);
309         n++;
310       }
311       if (it == args.end())
312         break;
313       curArg = *it;
314       curObj = Object::dynamicCast(it++); // may be 0
315     }
316     arr.put(exec,lengthPropertyName, Number(n), DontEnum | DontDelete);
317
318     result = arr;
319     break;
320   }
321   case Pop:{
322     if (length == 0) {
323       thisObj.put(exec, lengthPropertyName, Number(length), DontEnum | DontDelete);
324       result = Undefined();
325     } else {
326       result = thisObj.get(exec, length - 1);
327       thisObj.put(exec, lengthPropertyName, Number(length - 1), DontEnum | DontDelete);
328     }
329     break;
330   }
331   case Push: {
332     for (int n = 0; n < args.size(); n++)
333       thisObj.put(exec, length + n, args[n]);
334     length += args.size();
335     thisObj.put(exec,lengthPropertyName, Number(length), DontEnum | DontDelete);
336     result = Number(length);
337     break;
338   }
339   case Reverse: {
340
341     unsigned int middle = length / 2;
342
343     for (unsigned int k = 0; k < middle; k++) {
344       unsigned lk1 = length - k - 1;
345       Value obj = thisObj.get(exec,k);
346       Value obj2 = thisObj.get(exec,lk1);
347       if (thisObj.hasProperty(exec,lk1)) {
348         if (thisObj.hasProperty(exec,k)) {
349           thisObj.put(exec, k, obj2);
350           thisObj.put(exec, lk1, obj);
351         } else {
352           thisObj.put(exec, k, obj2);
353           thisObj.deleteProperty(exec, lk1);
354         }
355       } else {
356         if (thisObj.hasProperty(exec, k)) {
357           thisObj.deleteProperty(exec, k);
358           thisObj.put(exec, lk1, obj);
359         } else {
360           // why delete something that's not there ? Strange.
361           thisObj.deleteProperty(exec, k);
362           thisObj.deleteProperty(exec, lk1);
363         }
364       }
365     }
366     result = thisObj;
367     break;
368   }
369   case Shift: {
370     if (length == 0) {
371       thisObj.put(exec, lengthPropertyName, Number(length), DontEnum | DontDelete);
372       result = Undefined();
373     } else {
374       result = thisObj.get(exec, 0);
375       for(unsigned int k = 1; k < length; k++) {
376         if (thisObj.hasProperty(exec, k)) {
377           Value obj = thisObj.get(exec, k);
378           thisObj.put(exec, k-1, obj);
379         } else
380           thisObj.deleteProperty(exec, k-1);
381       }
382       thisObj.deleteProperty(exec, length - 1);
383       thisObj.put(exec, lengthPropertyName, Number(length - 1), DontEnum | DontDelete);
384     }
385     break;
386   }
387   case Slice: {
388     // http://developer.netscape.com/docs/manuals/js/client/jsref/array.htm#1193713 or 15.4.4.10
389
390     // We return a new array
391     Object resObj = Object::dynamicCast(exec->interpreter()->builtinArray().construct(exec,List::empty()));
392     result = resObj;
393     int begin = args[0].toUInt32(exec);
394     if ( begin < 0 )
395       begin = maxInt( begin + length, 0 );
396     else
397       begin = minInt( begin, length );
398     int end = length;
399     if (args[1].type() != UndefinedType)
400     {
401       end = args[1].toUInt32(exec);
402       if ( end < 0 )
403         end = maxInt( end + length, 0 );
404       else
405         end = minInt( end, length );
406     }
407
408     //printf( "Slicing from %d to %d \n", begin, end );
409     for(unsigned int k = 0; k < (unsigned int) end-begin; k++) {
410       if (thisObj.hasProperty(exec,k+begin)) {
411         Value obj = thisObj.get(exec, k+begin);
412         resObj.put(exec, k, obj);
413       }
414     }
415     resObj.put(exec, lengthPropertyName, Number(end - begin), DontEnum | DontDelete);
416     break;
417   }
418   case Sort:{
419 #if 0
420     printf("KJS Array::Sort length=%d\n", length);
421     for ( unsigned int i = 0 ; i<length ; ++i )
422       printf("KJS Array::Sort: %d: %s\n", i, thisObj.get(i).toString().value().ascii() );
423 #endif
424     Object sortFunction;
425     bool useSortFunction = (args[0].type() != UndefinedType);
426     if (useSortFunction)
427       {
428         sortFunction = args[0].toObject(exec);
429         if (!sortFunction.implementsCall())
430           useSortFunction = false;
431       }
432
433     if (length == 0) {
434       thisObj.put(exec, lengthPropertyName, Number(0), DontEnum | DontDelete);
435       result = Undefined();
436       break;
437     }
438
439     // "Min" sort. Not the fastest, but definitely less code than heapsort
440     // or quicksort, and much less swapping than bubblesort/insertionsort.
441     for ( unsigned int i = 0 ; i<length-1 ; ++i )
442       {
443         Value iObj = thisObj.get(exec,i);
444         unsigned int themin = i;
445         Value minObj = iObj;
446         for ( unsigned int j = i+1 ; j<length ; ++j )
447           {
448             Value jObj = thisObj.get(exec,j);
449             int cmp;
450             if (jObj.type() == UndefinedType) {
451               cmp = 1;
452             } else if (minObj.type() == UndefinedType) {
453               cmp = -1;
454             } else if (useSortFunction) {
455                 List l;
456                 l.append(jObj);
457                 l.append(minObj);
458                 Object thisObj = exec->interpreter()->globalObject();
459                 cmp = sortFunction.call(exec,thisObj, l ).toInt32(exec);
460             } else {
461               cmp = (jObj.toString(exec) < minObj.toString(exec)) ? -1 : 1;
462             }
463             if ( cmp < 0 )
464               {
465                 themin = j;
466                 minObj = jObj;
467               }
468           }
469         // Swap themin and i
470         if ( themin > i )
471           {
472             //printf("KJS Array::Sort: swapping %d and %d\n", i, themin );
473             thisObj.put( exec, i, minObj );
474             thisObj.put( exec, themin, iObj );
475           }
476       }
477 #if 0
478     printf("KJS Array::Sort -- Resulting array:\n");
479     for ( unsigned int i = 0 ; i<length ; ++i )
480       printf("KJS Array::Sort: %d: %s\n", i, thisObj.get(i).toString().value().ascii() );
481 #endif
482     result = thisObj;
483     break;
484   }
485   case Splice: {
486     // 15.4.4.12 - oh boy this is huge
487     Object resObj = Object::dynamicCast(exec->interpreter()->builtinArray().construct(exec,List::empty()));
488     result = resObj;
489     int begin = args[0].toUInt32(exec);
490     if ( begin < 0 )
491       begin = maxInt( begin + length, 0 );
492     else
493       begin = minInt( begin, length );
494     unsigned int deleteCount = minInt( maxInt( args[1].toUInt32(exec), 0 ), length - begin );
495
496     //printf( "Splicing from %d, deleteCount=%d \n", begin, deleteCount );
497     for(unsigned int k = 0; k < deleteCount; k++) {
498       if (thisObj.hasProperty(exec,k+begin)) {
499         Value obj = thisObj.get(exec, k+begin);
500         resObj.put(exec, k, obj);
501       }
502     }
503     resObj.put(exec, lengthPropertyName, Number(deleteCount), DontEnum | DontDelete);
504
505     unsigned int additionalArgs = maxInt( args.size() - 2, 0 );
506     if ( additionalArgs != deleteCount )
507     {
508       if ( additionalArgs < deleteCount )
509       {
510         for ( unsigned int k = begin; k < length - deleteCount; ++k )
511         {
512           if (thisObj.hasProperty(exec,k+deleteCount)) {
513             Value obj = thisObj.get(exec, k+deleteCount);
514             thisObj.put(exec, k+additionalArgs, obj);
515           }
516           else
517             thisObj.deleteProperty(exec, k+additionalArgs);
518         }
519         for ( unsigned int k = length ; k > length - deleteCount + additionalArgs; --k )
520           thisObj.deleteProperty(exec, k-1);
521       }
522       else
523       {
524         for ( unsigned int k = length - deleteCount; (int)k > begin; --k )
525         {
526           if (thisObj.hasProperty(exec,k+deleteCount-1)) {
527             Value obj = thisObj.get(exec, k+deleteCount-1);
528             thisObj.put(exec, k+additionalArgs-1, obj);
529           }
530           else
531             thisObj.deleteProperty(exec, k+additionalArgs-1);
532         }
533       }
534     }
535     for ( unsigned int k = 0; k < additionalArgs; ++k )
536     {
537       thisObj.put(exec, k+begin, args[k+2]);
538     }
539     thisObj.put(exec, lengthPropertyName, Number(length - deleteCount + additionalArgs), DontEnum | DontDelete);
540     break;
541   }
542   case UnShift: { // 15.4.4.13
543     unsigned int nrArgs = args.size();
544     for ( unsigned int k = length; k > 0; --k )
545     {
546       if (thisObj.hasProperty(exec,k-1)) {
547         Value obj = thisObj.get(exec, k-1);
548         thisObj.put(exec, k+nrArgs-1, obj);
549       } else {
550         thisObj.deleteProperty(exec, k+nrArgs-1);
551       }
552     }
553     for ( unsigned int k = 0; k < nrArgs; ++k )
554       thisObj.put(exec, k, args[k]);
555     result = Number(length + nrArgs);
556     thisObj.put(exec, lengthPropertyName, result, DontEnum | DontDelete);
557     break;
558   }
559   default:
560     assert(0);
561     break;
562   }
563   return result;
564 }
565
566 // ------------------------------ ArrayObjectImp -------------------------------
567
568 ArrayObjectImp::ArrayObjectImp(ExecState *exec,
569                                FunctionPrototypeImp *funcProto,
570                                ArrayPrototypeImp *arrayProto)
571   : InternalFunctionImp(funcProto)
572 {
573   Value protect(this);
574   // ECMA 15.4.3.1 Array.prototype
575   put(exec,prototypePropertyName, Object(arrayProto), DontEnum|DontDelete|ReadOnly);
576
577   // no. of arguments for constructor
578   put(exec,lengthPropertyName, Number(1), ReadOnly|DontDelete|DontEnum);
579 }
580
581 bool ArrayObjectImp::implementsConstruct() const
582 {
583   return true;
584 }
585
586 // ECMA 15.4.2
587 Object ArrayObjectImp::construct(ExecState *exec, const List &args)
588 {
589   // a single numeric argument denotes the array size (!)
590   if (args.size() == 1 && args[0].type() == NumberType)
591     return Object(new ArrayInstanceImp(exec->interpreter()->builtinArrayPrototype(), args[0].toUInt32(exec)));
592
593   // otherwise the array is constructed with the arguments in it
594   return Object(new ArrayInstanceImp(exec->interpreter()->builtinArrayPrototype(), args));
595 }
596
597 bool ArrayObjectImp::implementsCall() const
598 {
599   return true;
600 }
601
602 // ECMA 15.6.1
603 Value ArrayObjectImp::call(ExecState *exec, Object &/*thisObj*/, const List &args)
604 {
605   // equivalent to 'new Array(....)'
606   return construct(exec,args);
607 }
608