xref: /reactos/dll/win32/jscript/object.c (revision c81af08f)
1 /*
2  * Copyright 2008 Jacek Caban for CodeWeavers
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include <assert.h>
20 
21 #include "jscript.h"
22 
23 #include "wine/debug.h"
24 
25 WINE_DEFAULT_DEBUG_CHANNEL(jscript);
26 
27 static const WCHAR toStringW[] = {'t','o','S','t','r','i','n','g',0};
28 static const WCHAR toLocaleStringW[] = {'t','o','L','o','c','a','l','e','S','t','r','i','n','g',0};
29 static const WCHAR valueOfW[] = {'v','a','l','u','e','O','f',0};
30 static const WCHAR hasOwnPropertyW[] = {'h','a','s','O','w','n','P','r','o','p','e','r','t','y',0};
31 static const WCHAR propertyIsEnumerableW[] =
32     {'p','r','o','p','e','r','t','y','I','s','E','n','u','m','e','r','a','b','l','e',0};
33 static const WCHAR isPrototypeOfW[] = {'i','s','P','r','o','t','o','t','y','p','e','O','f',0};
34 
35 static const WCHAR getOwnPropertyDescriptorW[] =
36     {'g','e','t','O','w','n','P','r','o','p','e','r','t','y','D','e','s','c','r','i','p','t','o','r',0};
37 static const WCHAR definePropertyW[] = {'d','e','f','i','n','e','P','r','o','p','e','r','t','y',0};
38 
39 static const WCHAR definePropertiesW[] = {'d','e','f','i','n','e','P','r','o','p','e','r','t','i','e','s',0};
40 
41 static const WCHAR default_valueW[] = {'[','o','b','j','e','c','t',' ','O','b','j','e','c','t',']',0};
42 
43 static const WCHAR configurableW[] = {'c','o','n','f','i','g','u','r','a','b','l','e',0};
44 static const WCHAR enumerableW[] = {'e','n','u','m','e','r','a','b','l','e',0};
45 static const WCHAR valueW[] = {'v','a','l','u','e',0};
46 static const WCHAR writableW[] = {'w','r','i','t','a','b','l','e',0};
47 static const WCHAR getW[] = {'g','e','t',0};
48 static const WCHAR setW[] = {'s','e','t',0};
49 
50 static HRESULT Object_toString(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
51         jsval_t *r)
52 {
53     jsdisp_t *jsdisp;
54     const WCHAR *str;
55 
56     static const WCHAR formatW[] = {'[','o','b','j','e','c','t',' ','%','s',']',0};
57 
58     static const WCHAR arrayW[] = {'A','r','r','a','y',0};
59     static const WCHAR booleanW[] = {'B','o','o','l','e','a','n',0};
60     static const WCHAR dateW[] = {'D','a','t','e',0};
61     static const WCHAR errorW[] = {'E','r','r','o','r',0};
62     static const WCHAR functionW[] = {'F','u','n','c','t','i','o','n',0};
63     static const WCHAR mathW[] = {'M','a','t','h',0};
64     static const WCHAR numberW[] = {'N','u','m','b','e','r',0};
65     static const WCHAR objectW[] = {'O','b','j','e','c','t',0};
66     static const WCHAR regexpW[] = {'R','e','g','E','x','p',0};
67     static const WCHAR stringW[] = {'S','t','r','i','n','g',0};
68     /* Keep in sync with jsclass_t enum */
69     static const WCHAR *names[] = {NULL, arrayW, booleanW, dateW, objectW, errorW,
70         functionW, NULL, mathW, numberW, objectW, regexpW, stringW, objectW, objectW, objectW};
71 
72     TRACE("\n");
73 
74     jsdisp = get_jsdisp(jsthis);
75     if(!jsdisp) {
76         str = objectW;
77     }else if(names[jsdisp->builtin_info->class]) {
78         str = names[jsdisp->builtin_info->class];
79     }else {
80         assert(jsdisp->builtin_info->class != JSCLASS_NONE);
81         FIXME("jdisp->builtin_info->class = %d\n", jsdisp->builtin_info->class);
82         return E_FAIL;
83     }
84 
85     if(r) {
86         jsstr_t *ret;
87         WCHAR *ptr;
88 
89         ret = jsstr_alloc_buf(9+strlenW(str), &ptr);
90         if(!ret)
91             return E_OUTOFMEMORY;
92 
93         sprintfW(ptr, formatW, str);
94         *r = jsval_string(ret);
95     }
96 
97     return S_OK;
98 }
99 
100 static HRESULT Object_toLocaleString(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
101         jsval_t *r)
102 {
103     TRACE("\n");
104 
105     if(!is_jsdisp(jsthis)) {
106         FIXME("Host object this\n");
107         return E_FAIL;
108     }
109 
110     return jsdisp_call_name(jsthis->u.jsdisp, toStringW, DISPATCH_METHOD, 0, NULL, r);
111 }
112 
113 static HRESULT Object_valueOf(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
114         jsval_t *r)
115 {
116     TRACE("\n");
117 
118     if(r) {
119         IDispatch_AddRef(jsthis->u.disp);
120         *r = jsval_disp(jsthis->u.disp);
121     }
122     return S_OK;
123 }
124 
125 static HRESULT Object_hasOwnProperty(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
126         jsval_t *r)
127 {
128     jsstr_t *name;
129     DISPID id;
130     BSTR bstr;
131     HRESULT hres;
132 
133     TRACE("\n");
134 
135     if(!argc) {
136         if(r)
137             *r = jsval_bool(FALSE);
138         return S_OK;
139     }
140 
141     hres = to_string(ctx, argv[0], &name);
142     if(FAILED(hres))
143         return hres;
144 
145     if(is_jsdisp(jsthis)) {
146         property_desc_t prop_desc;
147         const WCHAR *name_str;
148 
149         name_str = jsstr_flatten(name);
150         if(!name_str) {
151             jsstr_release(name);
152             return E_OUTOFMEMORY;
153         }
154 
155         hres = jsdisp_get_own_property(jsthis->u.jsdisp, name_str, TRUE, &prop_desc);
156         jsstr_release(name);
157         if(FAILED(hres) && hres != DISP_E_UNKNOWNNAME)
158             return hres;
159 
160         if(r) *r = jsval_bool(hres == S_OK);
161         return S_OK;
162     }
163 
164 
165     bstr = SysAllocStringLen(NULL, jsstr_length(name));
166     if(bstr)
167         jsstr_flush(name, bstr);
168     jsstr_release(name);
169     if(!bstr)
170         return E_OUTOFMEMORY;
171 
172     if(is_dispex(jsthis))
173         hres = IDispatchEx_GetDispID(jsthis->u.dispex, bstr, make_grfdex(ctx, fdexNameCaseSensitive), &id);
174     else
175         hres = IDispatch_GetIDsOfNames(jsthis->u.disp, &IID_NULL, &bstr, 1, ctx->lcid, &id);
176 
177     SysFreeString(bstr);
178     if(r)
179         *r = jsval_bool(SUCCEEDED(hres));
180     return S_OK;
181 }
182 
183 static HRESULT Object_propertyIsEnumerable(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
184         jsval_t *r)
185 {
186     property_desc_t prop_desc;
187     const WCHAR *name;
188     jsstr_t *name_str;
189     HRESULT hres;
190 
191     TRACE("\n");
192 
193     if(argc != 1) {
194         FIXME("argc %d not supported\n", argc);
195         return E_NOTIMPL;
196     }
197 
198     if(!is_jsdisp(jsthis)) {
199         FIXME("Host object this\n");
200         return E_FAIL;
201     }
202 
203     hres = to_flat_string(ctx, argv[0], &name_str, &name);
204     if(FAILED(hres))
205         return hres;
206 
207     hres = jsdisp_get_own_property(jsthis->u.jsdisp, name, TRUE, &prop_desc);
208     jsstr_release(name_str);
209     if(FAILED(hres) && hres != DISP_E_UNKNOWNNAME)
210         return hres;
211 
212     if(r)
213         *r = jsval_bool(hres == S_OK && (prop_desc.flags & PROPF_ENUMERABLE) != 0);
214     return S_OK;
215 }
216 
217 static HRESULT Object_isPrototypeOf(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
218         jsval_t *r)
219 {
220     FIXME("\n");
221     return E_NOTIMPL;
222 }
223 
224 static HRESULT Object_get_value(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r)
225 {
226     jsstr_t *ret;
227 
228     TRACE("\n");
229 
230     ret = jsstr_alloc(default_valueW);
231     if(!ret)
232         return E_OUTOFMEMORY;
233 
234     *r = jsval_string(ret);
235     return S_OK;
236 }
237 
238 static void Object_destructor(jsdisp_t *dispex)
239 {
240     heap_free(dispex);
241 }
242 
243 static const builtin_prop_t Object_props[] = {
244     {hasOwnPropertyW,        Object_hasOwnProperty,        PROPF_METHOD|1},
245     {isPrototypeOfW,         Object_isPrototypeOf,         PROPF_METHOD|1},
246     {propertyIsEnumerableW,  Object_propertyIsEnumerable,  PROPF_METHOD|1},
247     {toLocaleStringW,        Object_toLocaleString,        PROPF_METHOD},
248     {toStringW,              Object_toString,              PROPF_METHOD},
249     {valueOfW,               Object_valueOf,               PROPF_METHOD}
250 };
251 
252 static const builtin_info_t Object_info = {
253     JSCLASS_OBJECT,
254     {NULL, NULL,0, Object_get_value},
255     ARRAY_SIZE(Object_props),
256     Object_props,
257     Object_destructor,
258     NULL
259 };
260 
261 static const builtin_info_t ObjectInst_info = {
262     JSCLASS_OBJECT,
263     {NULL, NULL,0, Object_get_value},
264     0, NULL,
265     Object_destructor,
266     NULL
267 };
268 
269 static void release_property_descriptor(property_desc_t *desc)
270 {
271     if(desc->explicit_value)
272         jsval_release(desc->value);
273     if(desc->getter)
274         jsdisp_release(desc->getter);
275     if(desc->setter)
276         jsdisp_release(desc->setter);
277 }
278 
279 static HRESULT to_property_descriptor(script_ctx_t *ctx, jsdisp_t *attr_obj, property_desc_t *desc)
280 {
281     DISPID id;
282     jsval_t v;
283     BOOL b;
284     HRESULT hres;
285 
286     memset(desc, 0, sizeof(*desc));
287     desc->value = jsval_undefined();
288 
289     hres = jsdisp_get_id(attr_obj, enumerableW, 0, &id);
290     if(SUCCEEDED(hres)) {
291         desc->mask |= PROPF_ENUMERABLE;
292         hres = jsdisp_propget(attr_obj, id, &v);
293         if(FAILED(hres))
294             return hres;
295         hres = to_boolean(v, &b);
296         jsval_release(v);
297         if(FAILED(hres))
298             return hres;
299         if(b)
300             desc->flags |= PROPF_ENUMERABLE;
301     }else if(hres != DISP_E_UNKNOWNNAME) {
302         return hres;
303     }
304 
305     hres = jsdisp_get_id(attr_obj, configurableW, 0, &id);
306     if(SUCCEEDED(hres)) {
307         desc->mask |= PROPF_CONFIGURABLE;
308         hres = jsdisp_propget(attr_obj, id, &v);
309         if(FAILED(hres))
310             return hres;
311         hres = to_boolean(v, &b);
312         jsval_release(v);
313         if(FAILED(hres))
314             return hres;
315         if(b)
316             desc->flags |= PROPF_CONFIGURABLE;
317     }else if(hres != DISP_E_UNKNOWNNAME) {
318         return hres;
319     }
320 
321     hres = jsdisp_get_id(attr_obj, valueW, 0, &id);
322     if(SUCCEEDED(hres)) {
323         hres = jsdisp_propget(attr_obj, id, &desc->value);
324         if(FAILED(hres))
325             return hres;
326         desc->explicit_value = TRUE;
327     }else if(hres != DISP_E_UNKNOWNNAME) {
328         return hres;
329     }
330 
331     hres = jsdisp_get_id(attr_obj, writableW, 0, &id);
332     if(SUCCEEDED(hres)) {
333         desc->mask |= PROPF_WRITABLE;
334         hres = jsdisp_propget(attr_obj, id, &v);
335         if(SUCCEEDED(hres)) {
336             hres = to_boolean(v, &b);
337             jsval_release(v);
338             if(SUCCEEDED(hres) && b)
339                 desc->flags |= PROPF_WRITABLE;
340         }
341     }else if(hres == DISP_E_UNKNOWNNAME) {
342         hres = S_OK;
343     }
344     if(FAILED(hres)) {
345         release_property_descriptor(desc);
346         return hres;
347     }
348 
349     hres = jsdisp_get_id(attr_obj, getW, 0, &id);
350     if(SUCCEEDED(hres)) {
351         desc->explicit_getter = TRUE;
352         hres = jsdisp_propget(attr_obj, id, &v);
353         if(SUCCEEDED(hres) && !is_undefined(v)) {
354             if(!is_object_instance(v)) {
355                 FIXME("getter is not an object\n");
356                 jsval_release(v);
357                 hres = E_FAIL;
358             }else {
359                 /* FIXME: Check IsCallable */
360                 desc->getter = to_jsdisp(get_object(v));
361                 if(!desc->getter)
362                     FIXME("getter is not JS object\n");
363             }
364         }
365     }else if(hres == DISP_E_UNKNOWNNAME) {
366         hres = S_OK;
367     }
368     if(FAILED(hres)) {
369         release_property_descriptor(desc);
370         return hres;
371     }
372 
373     hres = jsdisp_get_id(attr_obj, setW, 0, &id);
374     if(SUCCEEDED(hres)) {
375         desc->explicit_setter = TRUE;
376         hres = jsdisp_propget(attr_obj, id, &v);
377         if(SUCCEEDED(hres) && !is_undefined(v)) {
378             if(!is_object_instance(v)) {
379                 FIXME("setter is not an object\n");
380                 jsval_release(v);
381                 hres = E_FAIL;
382             }else {
383                 /* FIXME: Check IsCallable */
384                 desc->setter = to_jsdisp(get_object(v));
385                 if(!desc->setter)
386                     FIXME("setter is not JS object\n");
387             }
388         }
389     }else if(hres == DISP_E_UNKNOWNNAME) {
390         hres = S_OK;
391     }
392     if(FAILED(hres)) {
393         release_property_descriptor(desc);
394         return hres;
395     }
396 
397     if(desc->explicit_getter || desc->explicit_setter) {
398         if(desc->explicit_value)
399             hres = throw_type_error(ctx, JS_E_PROP_DESC_MISMATCH, NULL);
400         else if(desc->mask & PROPF_WRITABLE)
401             hres = throw_type_error(ctx, JS_E_INVALID_WRITABLE_PROP_DESC, NULL);
402     }
403 
404     if(FAILED(hres))
405         release_property_descriptor(desc);
406     return hres;
407 }
408 
409 static HRESULT Object_defineProperty(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags,
410                                      unsigned argc, jsval_t *argv, jsval_t *r)
411 {
412     property_desc_t prop_desc;
413     jsdisp_t *obj, *attr_obj;
414     const WCHAR *name;
415     jsstr_t *name_str;
416     HRESULT hres;
417 
418     TRACE("\n");
419 
420     if(argc < 1 || !is_object_instance(argv[0]))
421         return throw_type_error(ctx, JS_E_OBJECT_EXPECTED, NULL);
422     obj = to_jsdisp(get_object(argv[0]));
423     if(!obj) {
424         FIXME("not implemented non-JS object\n");
425         return E_NOTIMPL;
426     }
427 
428     hres = to_flat_string(ctx, argc >= 2 ? argv[1] : jsval_undefined(), &name_str, &name);
429     if(FAILED(hres))
430         return hres;
431 
432     if(argc >= 3 && is_object_instance(argv[2])) {
433         attr_obj = to_jsdisp(get_object(argv[2]));
434         if(attr_obj) {
435             hres = to_property_descriptor(ctx, attr_obj, &prop_desc);
436         }else {
437             FIXME("not implemented non-JS object\n");
438             hres = E_NOTIMPL;
439         }
440     }else {
441         hres = throw_type_error(ctx, JS_E_OBJECT_EXPECTED, NULL);
442     }
443     jsstr_release(name_str);
444     if(FAILED(hres))
445         return hres;
446 
447     hres = jsdisp_define_property(obj, name, &prop_desc);
448     release_property_descriptor(&prop_desc);
449     return hres;
450 }
451 
452 static HRESULT Object_defineProperties(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags,
453                                      unsigned argc, jsval_t *argv, jsval_t *r)
454 {
455     FIXME("\n");
456     return E_NOTIMPL;
457 }
458 
459 static HRESULT Object_getOwnPropertyDescriptor(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags,
460                                                unsigned argc, jsval_t *argv, jsval_t *r)
461 {
462     property_desc_t prop_desc;
463     jsdisp_t *obj, *desc_obj;
464     const WCHAR *name;
465     jsstr_t *name_str;
466     HRESULT hres;
467 
468     TRACE("\n");
469 
470     if(argc < 1 || !is_object_instance(argv[0]))
471         return throw_type_error(ctx, JS_E_OBJECT_EXPECTED, NULL);
472     obj = to_jsdisp(get_object(argv[0]));
473     if(!obj) {
474         FIXME("not implemented non-JS object\n");
475         return E_NOTIMPL;
476     }
477 
478     hres = to_flat_string(ctx, argc >= 2 ? argv[1] : jsval_undefined(), &name_str, &name);
479     if(FAILED(hres))
480         return hres;
481 
482     hres = jsdisp_get_own_property(obj, name, FALSE, &prop_desc);
483     jsstr_release(name_str);
484     if(hres == DISP_E_UNKNOWNNAME) {
485         if(r) *r = jsval_undefined();
486         return S_OK;
487     }
488     if(FAILED(hres))
489         return hres;
490 
491     hres = create_object(ctx, NULL, &desc_obj);
492     if(FAILED(hres))
493         return hres;
494 
495     if(prop_desc.explicit_getter || prop_desc.explicit_setter) {
496         hres = jsdisp_define_data_property(desc_obj, getW, PROPF_ALL,
497                 prop_desc.getter ? jsval_obj(prop_desc.getter) : jsval_undefined());
498         if(SUCCEEDED(hres))
499             hres = jsdisp_define_data_property(desc_obj, setW, PROPF_ALL,
500                     prop_desc.setter ? jsval_obj(prop_desc.setter) : jsval_undefined());
501     }else {
502         hres = jsdisp_propput_name(desc_obj, valueW, prop_desc.value);
503         if(SUCCEEDED(hres))
504             hres = jsdisp_define_data_property(desc_obj, writableW, PROPF_ALL,
505                     jsval_bool(!!(prop_desc.flags & PROPF_WRITABLE)));
506     }
507     if(SUCCEEDED(hres))
508         hres = jsdisp_define_data_property(desc_obj, enumerableW, PROPF_ALL,
509                 jsval_bool(!!(prop_desc.flags & PROPF_ENUMERABLE)));
510     if(SUCCEEDED(hres))
511         hres = jsdisp_define_data_property(desc_obj, configurableW, PROPF_ALL,
512                 jsval_bool(!!(prop_desc.flags & PROPF_CONFIGURABLE)));
513 
514     release_property_descriptor(&prop_desc);
515     if(SUCCEEDED(hres) && r)
516         *r = jsval_obj(desc_obj);
517     else
518         jsdisp_release(desc_obj);
519     return hres;
520 }
521 
522 static const builtin_prop_t ObjectConstr_props[] = {
523     {definePropertiesW,         Object_defineProperties,            PROPF_ES5|PROPF_METHOD|2},
524     {definePropertyW,           Object_defineProperty,              PROPF_ES5|PROPF_METHOD|2},
525     {getOwnPropertyDescriptorW, Object_getOwnPropertyDescriptor,    PROPF_ES5|PROPF_METHOD|2}
526 };
527 
528 static const builtin_info_t ObjectConstr_info = {
529     JSCLASS_FUNCTION,
530     DEFAULT_FUNCTION_VALUE,
531     ARRAY_SIZE(ObjectConstr_props),
532     ObjectConstr_props,
533     NULL,
534     NULL
535 };
536 
537 static HRESULT ObjectConstr_value(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
538         jsval_t *r)
539 {
540     HRESULT hres;
541 
542     TRACE("\n");
543 
544     switch(flags) {
545     case DISPATCH_METHOD:
546     case DISPATCH_CONSTRUCT: {
547         jsdisp_t *obj;
548 
549         if(argc) {
550             if(!is_undefined(argv[0]) && !is_null(argv[0]) && (!is_object_instance(argv[0]) || get_object(argv[0]))) {
551                 IDispatch *disp;
552 
553                 hres = to_object(ctx, argv[0], &disp);
554                 if(FAILED(hres))
555                     return hres;
556 
557                 if(r)
558                     *r = jsval_disp(disp);
559                 else
560                     IDispatch_Release(disp);
561                 return S_OK;
562             }
563         }
564 
565         hres = create_object(ctx, NULL, &obj);
566         if(FAILED(hres))
567             return hres;
568 
569         if(r)
570             *r = jsval_obj(obj);
571         else
572             jsdisp_release(obj);
573         break;
574     }
575 
576     default:
577         FIXME("unimplemented flags: %x\n", flags);
578         return E_NOTIMPL;
579     }
580 
581     return S_OK;
582 }
583 
584 HRESULT create_object_constr(script_ctx_t *ctx, jsdisp_t *object_prototype, jsdisp_t **ret)
585 {
586     static const WCHAR ObjectW[] = {'O','b','j','e','c','t',0};
587 
588     return create_builtin_constructor(ctx, ObjectConstr_value, ObjectW, &ObjectConstr_info, PROPF_CONSTR,
589             object_prototype, ret);
590 }
591 
592 HRESULT create_object_prototype(script_ctx_t *ctx, jsdisp_t **ret)
593 {
594     return create_dispex(ctx, &Object_info, NULL, ret);
595 }
596 
597 HRESULT create_object(script_ctx_t *ctx, jsdisp_t *constr, jsdisp_t **ret)
598 {
599     jsdisp_t *object;
600     HRESULT hres;
601 
602     object = heap_alloc_zero(sizeof(jsdisp_t));
603     if(!object)
604         return E_OUTOFMEMORY;
605 
606     hres = init_dispex_from_constr(object, ctx, &ObjectInst_info, constr ? constr : ctx->object_constr);
607     if(FAILED(hres)) {
608         heap_free(object);
609         return hres;
610     }
611 
612     *ret = object;
613     return S_OK;
614 }
615