1 // Object.cpp:  Implementation of ActionScript Object class, for Gnash.
2 //
3 //   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 //   Free Software Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program 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
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19 //
20 
21 #include "Object.h"
22 
23 #include <string>
24 #include <sstream>
25 
26 #include "Movie.h"
27 #include "DisplayObject.h"
28 #include "fn_call.h"
29 #include "as_object.h"
30 #include "NativeFunction.h"
31 #include "movie_definition.h"
32 #include "sprite_definition.h"
33 #include "VM.h"
34 #include "namedStrings.h" // for NSV::PROP_TO_STRING
35 #include "Global_as.h"
36 #include "movie_root.h"
37 #include "log.h"
38 
39 namespace gnash {
40 
41 // Forward declarations
42 namespace {
43 
44     as_value object_addproperty(const fn_call&);
45     as_value object_registerClass(const fn_call& fn);
46     as_value object_hasOwnProperty(const fn_call&);
47     as_value object_isPropertyEnumerable(const fn_call&);
48     as_value object_isPrototypeOf(const fn_call&);
49     as_value object_watch(const fn_call&);
50     as_value object_unwatch(const fn_call&);
51     as_value object_toLocaleString(const fn_call&);
52     as_value object_ctor(const fn_call& fn);
53     as_value object_valueOf(const fn_call& fn);
54     as_value object_toString(const fn_call& fn);
55 
56     void attachObjectInterface(as_object& o);
57 
58 }
59 
60 
61 void
registerObjectNative(as_object & global)62 registerObjectNative(as_object& global)
63 {
64     VM& vm = getVM(global);
65 
66     vm.registerNative(object_watch, 101, 0);
67     vm.registerNative(object_unwatch, 101, 1);
68     vm.registerNative(object_addproperty, 101, 2);
69     vm.registerNative(object_valueOf, 101, 3);
70     vm.registerNative(object_toString, 101, 4);
71     vm.registerNative(object_hasOwnProperty, 101, 5);
72     vm.registerNative(object_isPrototypeOf, 101, 6);
73     vm.registerNative(object_isPropertyEnumerable, 101, 7);
74     vm.registerNative(object_registerClass, 101, 8);
75     vm.registerNative(object_ctor, 101, 9);
76 }
77 
78 // extern (used by Global.cpp)
79 void
initObjectClass(as_object * proto,as_object & where,const ObjectURI & uri)80 initObjectClass(as_object* proto, as_object& where, const ObjectURI& uri)
81 {
82 
83     assert(proto);
84 
85     // Object is a native constructor.
86     VM& vm = getVM(where);
87     as_object* cl = vm.getNative(101, 9);
88     cl->init_member(NSV::PROP_PROTOTYPE, proto);
89     proto->init_member(NSV::PROP_CONSTRUCTOR, cl);
90 
91     attachObjectInterface(*proto);
92 
93     // The as_function ctor takes care of initializing these, but they
94     // are different for the Object class.
95     const int readOnly = PropFlags::readOnly;
96     cl->set_member_flags(NSV::PROP_uuPROTOuu, readOnly);
97     cl->set_member_flags(NSV::PROP_CONSTRUCTOR, readOnly);
98     cl->set_member_flags(NSV::PROP_PROTOTYPE, readOnly);
99 
100     const int readOnlyFlags = as_object::DefaultFlags | PropFlags::readOnly;
101     cl->init_member("registerClass", vm.getNative(101, 8), readOnlyFlags);
102 
103     // Register _global.Object (should only be visible in SWF5 up)
104     int flags = PropFlags::dontEnum;
105     where.init_member(uri, cl, flags);
106 
107 }
108 
109 
110 namespace {
111 
112 void
attachObjectInterface(as_object & o)113 attachObjectInterface(as_object& o)
114 {
115     VM& vm = getVM(o);
116 
117     // We register natives despite swf version,
118     Global_as& gl = getGlobal(o);
119 
120     o.init_member("valueOf", vm.getNative(101, 3));
121     o.init_member("toString", vm.getNative(101, 4));
122     o.init_member("toLocaleString", gl.createFunction(object_toLocaleString));
123 
124     int swf6flags = PropFlags::dontEnum |
125         PropFlags::dontDelete |
126         PropFlags::onlySWF6Up;
127 
128     o.init_member("addProperty", vm.getNative(101, 2), swf6flags);
129     o.init_member("hasOwnProperty", vm.getNative(101, 5), swf6flags);
130     o.init_member("isPropertyEnumerable", vm.getNative(101, 7), swf6flags);
131     o.init_member("isPrototypeOf", vm.getNative(101, 6), swf6flags);
132     o.init_member("watch", vm.getNative(101, 0), swf6flags);
133     o.init_member("unwatch", vm.getNative(101, 1), swf6flags);
134 }
135 
136 
137 as_value
object_ctor(const fn_call & fn)138 object_ctor(const fn_call& fn)
139 {
140 
141     if (fn.nargs == 1) {
142         as_object* obj = toObject(fn.arg(0), getVM(fn));
143         if (obj) return as_value(obj);
144     }
145 
146     if (fn.nargs > 1) {
147         IF_VERBOSE_ASCODING_ERRORS(
148             log_aserror(_("Too many args to Object constructor"));
149         );
150     }
151 
152     Global_as& gl = getGlobal(fn);
153 
154     if (!fn.isInstantiation()) {
155         return new as_object(gl);
156     }
157 
158     return as_value();
159 
160 }
161 
162 /// Object.toString returns one of two values: [type Function] if it is a
163 /// function, [object Object] if it is an object. Gnash wrongly regards super
164 /// as a function, so we have to handle that too.
165 as_value
object_toString(const fn_call & fn)166 object_toString(const fn_call& fn)
167 {
168     as_object* obj = ensure<ValidThis>(fn);
169     return as_value(obj->stringValue());
170 }
171 
172 as_value
object_valueOf(const fn_call & fn)173 object_valueOf(const fn_call& fn)
174 {
175     return fn.this_ptr;
176 }
177 
178 
179 /// The return value is not dependent on the result of add_property (though
180 /// this is always true anyway), but rather on the validity of the arguments.
181 as_value
object_addproperty(const fn_call & fn)182 object_addproperty(const fn_call& fn)
183 {
184     as_object* obj = ensure<ValidThis>(fn);
185 
186     /// Extra arguments are just ignored.
187     if ( fn.nargs < 3 )
188     {
189         IF_VERBOSE_ASCODING_ERRORS(
190         std::stringstream ss;
191         fn.dump_args(ss);
192         log_aserror(_("Invalid call to Object.addProperty(%s) - "
193             "expected 3 arguments (<name>, <getter>, <setter>)"),
194                    ss.str());
195         );
196 
197         // if we've been given more args then needed there's
198         // no need to abort here
199         if ( fn.nargs < 3 )
200         {
201             return as_value(false);
202         }
203     }
204 
205     const std::string& propname = fn.arg(0).to_string();
206     if (propname.empty())
207     {
208         IF_VERBOSE_ASCODING_ERRORS(
209         log_aserror(_("Invalid call to Object.addProperty() - "
210             "empty property name"));
211         );
212         return as_value(false);
213     }
214 
215     as_function* getter = fn.arg(1).to_function();
216     if (!getter)
217     {
218         IF_VERBOSE_ASCODING_ERRORS(
219         log_aserror(_("Invalid call to Object.addProperty() - "
220             "getter is not an AS function"));
221         );
222         return as_value(false);
223     }
224 
225     as_function* setter = nullptr;
226     const as_value& setterval = fn.arg(2);
227     if (!setterval.is_null())
228     {
229         setter = setterval.to_function();
230         if (!setter)
231         {
232             IF_VERBOSE_ASCODING_ERRORS(
233             log_aserror(_("Invalid call to Object.addProperty() - "
234                 "setter is not null and not an AS function (%s)"),
235                 setterval);
236             );
237             return as_value(false);
238         }
239     }
240 
241     // Now that we checked everything, let's call the as_object
242     // interface for getter/setter properties :)
243     obj->add_property(propname, *getter, setter);
244 
245     return as_value(true);
246 }
247 
248 
249 as_value
object_registerClass(const fn_call & fn)250 object_registerClass(const fn_call& fn)
251 {
252 
253     if (fn.nargs != 2) {
254         IF_VERBOSE_ASCODING_ERRORS(
255             std::stringstream ss;
256             fn.dump_args(ss);
257             log_aserror(_("Invalid call to Object.registerClass(%s) - "
258                 "expected 2 arguments (<symbol>, <constructor>)"),
259                 ss.str());
260         );
261 
262         // if we've been given more args then needed there's
263         // no need to abort here
264         if (fn.nargs < 2) {
265             return as_value(false);
266         }
267     }
268 
269     const std::string& symbolid = fn.arg(0).to_string();
270     if (symbolid.empty()) {
271         IF_VERBOSE_ASCODING_ERRORS(
272             std::stringstream ss;
273             fn.dump_args(ss);
274             log_aserror(_("Invalid call to Object.registerClass(%s) - "
275                 "first argument (symbol id) evaluates to empty string"),
276                 ss.str());
277         );
278         return as_value(false);
279     }
280 
281     as_function* theclass = fn.arg(1).to_function();
282     if (!theclass) {
283         IF_VERBOSE_ASCODING_ERRORS(
284             std::stringstream ss;
285             fn.dump_args(ss);
286             log_aserror(_("Invalid call to Object.registerClass(%s) - "
287                 "second argument (class) is not a function)"), ss.str());
288             );
289         return as_value(false);
290     }
291 
292     // Find the exported resource
293 
294     // Using definition of current target fixes the youtube beta case
295     // https://savannah.gnu.org/bugs/index.php?23130
296     DisplayObject* tgt = fn.env().target();
297     if (!tgt) {
298         log_error(_("current environment has no target, wouldn't know "
299                     "where to look for symbol required for registerClass"));
300         return as_value(false);
301     }
302 
303     Movie* relRoot = tgt->get_root();
304     assert(relRoot);
305     const movie_definition* def = relRoot->definition();
306 
307     // We only care about definitions, not other exportable resources.
308     const std::uint16_t id = def->exportID(symbolid);
309     SWF::DefinitionTag* d = def->getDefinitionTag(id);
310 
311     if (!d) {
312         IF_VERBOSE_ASCODING_ERRORS(
313             log_aserror(_("Object.registerClass('%s', %s): "
314                 "can't find exported symbol (id: %d)"),
315                 symbolid, typeName(theclass), id);
316             );
317         return as_value(false);
318     }
319 
320     // Check that the exported resource is a sprite_definition
321     // (we're looking for a MovieClip symbol)
322     sprite_definition* exp_clipdef(dynamic_cast<sprite_definition*>(d));
323 
324     if (!exp_clipdef) {
325         IF_VERBOSE_ASCODING_ERRORS(
326         log_aserror(_("Object.registerClass(%s, %s): "
327             "exported symbol is not a MovieClip symbol "
328             "(sprite_definition), but a %s"),
329             symbolid, typeName(theclass), typeName(d));
330         );
331         return as_value(false);
332     }
333 
334     movie_root& mr = getRoot(fn);
335     mr.registerClass(exp_clipdef, theclass);
336     return as_value(true);
337 }
338 
339 
340 as_value
object_hasOwnProperty(const fn_call & fn)341 object_hasOwnProperty(const fn_call& fn)
342 {
343     as_object* obj = ensure<ValidThis>(fn);
344 
345     if ( fn.nargs < 1 )
346     {
347         IF_VERBOSE_ASCODING_ERRORS(
348         log_aserror(_("Object.hasOwnProperty() requires one arg"));
349         );
350         return as_value(false);
351     }
352     const as_value& arg = fn.arg(0);
353     const std::string& propname = arg.to_string();
354     if (arg.is_undefined() || propname.empty())
355     {
356         IF_VERBOSE_ASCODING_ERRORS(
357         log_aserror(_("Invalid call to Object.hasOwnProperty('%s')"), arg);
358         );
359         return as_value(false);
360     }
361 
362     const bool found = hasOwnProperty(*obj, getURI(getVM(fn), propname));
363     return as_value(found);
364 }
365 
366 as_value
object_isPropertyEnumerable(const fn_call & fn)367 object_isPropertyEnumerable(const fn_call& fn)
368 {
369     as_object* obj = ensure<ValidThis>(fn);
370 
371     if (!fn.nargs) {
372          IF_VERBOSE_ASCODING_ERRORS(
373             log_aserror(_("Object.isPropertyEnumerable() requires one arg"));
374          );
375         return as_value(false);
376     }
377 
378     const as_value& arg = fn.arg(0);
379     const std::string& propname = arg.to_string();
380     if (arg.is_undefined() || propname.empty()) {
381         IF_VERBOSE_ASCODING_ERRORS(
382             log_aserror(_("Invalid call to Object.isPropertyEnumerable('%s')"),
383                 arg);
384             );
385         return as_value();
386     }
387 
388     Property* prop = obj->getOwnProperty(getURI(getVM(fn),propname));
389 
390     if (!prop) {
391         return as_value(false);
392     }
393 
394     return as_value(!prop->getFlags().test<PropFlags::dontEnum>());
395 }
396 
397 
398 as_value
object_isPrototypeOf(const fn_call & fn)399 object_isPrototypeOf(const fn_call& fn)
400 {
401     as_object* obj = ensure<ValidThis>(fn);
402 
403     if (fn.nargs < 1) {
404         IF_VERBOSE_ASCODING_ERRORS(
405             log_aserror(_("Object.isPrototypeOf() requires one arg"));
406         );
407         return as_value(false);
408     }
409 
410     as_object* arg = toObject(fn.arg(0), getVM(fn));
411     if (!arg) {
412         IF_VERBOSE_ASCODING_ERRORS(
413             log_aserror(_("First arg to Object.isPrototypeOf(%s) is "
414                     "not an object"), fn.arg(0));
415             );
416         return as_value(false);
417     }
418 
419     return as_value(obj->prototypeOf(*arg));
420 
421 }
422 
423 
424 as_value
object_watch(const fn_call & fn)425 object_watch(const fn_call& fn)
426 {
427     as_object* obj = ensure<ValidThis>(fn);
428 
429     if (fn.nargs < 2) {
430         IF_VERBOSE_ASCODING_ERRORS(
431             std::stringstream ss; fn.dump_args(ss);
432             log_aserror(_("Object.watch(%s): missing arguments"));
433         );
434         return as_value(false);
435     }
436 
437     const as_value& propval = fn.arg(0);
438     const as_value& funcval = fn.arg(1);
439 
440     if (!funcval.is_function()) {
441         IF_VERBOSE_ASCODING_ERRORS(
442             std::stringstream ss; fn.dump_args(ss);
443             log_aserror(_("Object.watch(%s): second argument is not a "
444                     "function"));
445         );
446         return as_value(false);
447     }
448 
449     VM& vm = getVM(fn);
450 
451     std::string propname = propval.to_string();
452     const ObjectURI& propkey = getURI(vm, propname);
453     as_function* trig = funcval.to_function();
454     const as_value cust = fn.nargs > 2 ? fn.arg(2) : as_value();
455 
456     return as_value(obj->watch(propkey, *trig, cust));
457 }
458 
459 
460 as_value
object_unwatch(const fn_call & fn)461 object_unwatch(const fn_call& fn)
462 {
463     as_object* obj = ensure<ValidThis>(fn);
464 
465     if ( fn.nargs < 1 )
466     {
467         IF_VERBOSE_ASCODING_ERRORS(
468         std::stringstream ss; fn.dump_args(ss);
469         log_aserror(_("Object.unwatch(%s): missing argument"));
470         );
471         return as_value(false);
472     }
473 
474     const as_value& propval = fn.arg(0);
475 
476     VM& vm = getVM(fn);
477 
478     std::string propname = propval.to_string();
479     const ObjectURI& propkey = getURI(vm, propname);
480 
481     return as_value(obj->unwatch(propkey));
482 }
483 
484 
485 as_value
object_toLocaleString(const fn_call & fn)486 object_toLocaleString(const fn_call& fn)
487 {
488     as_object* obj = ensure<ValidThis>(fn);
489     return callMethod(obj, NSV::PROP_TO_STRING);
490 }
491 
492 } // anonymous namespace
493 } // namespace gnash
494