Plug more leaks (in the test, not the core)
[gnash.git] / libcore / as_environment.cpp
blob00e379b2d563050f13ff9acfa298a4bcf43c2cc1
1 // as_environment.cpp: Variable, Sprite, and Movie locators, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
4 // 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.
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
21 #include "as_environment.h"
23 #include <string>
24 #include <utility>
25 #include <boost/algorithm/string/case_conv.hpp>
26 #include <boost/format.hpp>
28 #include "smart_ptr.h"
29 #include "MovieClip.h"
30 #include "movie_root.h"
31 #include "as_value.h"
32 #include "VM.h"
33 #include "log.h"
34 #include "Property.h"
35 #include "as_object.h"
36 #include "namedStrings.h"
37 #include "CallStack.h"
38 #include "Global_as.h"
40 // Define this to have find_target() calls trigger debugging output
41 //#define DEBUG_TARGET_FINDING 1
43 // Define this to have get_variable() calls trigger debugging output
44 //#define GNASH_DEBUG_GET_VARIABLE 1
46 namespace gnash {
48 namespace {
49 /// Find a variable in the given as_object
51 /// @param varname
52 /// Name of the local variable
53 ///
54 /// @param ret
55 /// If a variable is found it's assigned to this parameter.
56 /// Untouched if the variable is not found.
57 ///
58 /// @return true if the variable was found, false otherwise
59 bool getLocal(as_object& locals, const std::string& name, as_value& ret);
61 bool findLocal(as_object& locals, const std::string& varname, as_value& ret,
62 as_object** retTarget);
64 /// Delete a local variable
66 /// @param varname
67 /// Name of the local variable
68 ///
69 /// @return true if the variable was found and deleted, false otherwise
70 bool deleteLocal(as_object& locals, const std::string& varname);
72 /// Set a variable of the given object, if it exists.
74 /// @param varname
75 /// Name of the local variable
76 ///
77 /// @param val
78 /// Value to assign to the variable
79 ///
80 /// @return true if the variable was found, false otherwise
81 bool setLocal(as_object& locals, const std::string& varname,
82 const as_value& val);
84 as_object* getElement(as_object* obj, const ObjectURI& uri);
86 /// @param retTarget
87 /// If not NULL, the pointer will be set to the actual object containing the
88 /// found variable (if found).
89 as_value getVariableRaw(const as_environment& env,
90 const std::string& varname,
91 const as_environment::ScopeStack& scope,
92 as_object** retTarget = 0);
94 void setVariableRaw(const as_environment& env, const std::string& varname,
95 const as_value& val, const as_environment::ScopeStack& scope);
97 // Search for next '.' or '/' character in this word. Return
98 // a pointer to it, or null if it wasn't found.
99 static const char* next_slash_or_dot(const char* word);
101 static bool validRawVariableName(const std::string& varname);
105 as_value as_environment::undefVal;
107 as_environment::as_environment(VM& vm)
109 _vm(vm),
110 _stack(_vm.getStack()),
111 _target(0),
112 _original_target(0)
116 as_object*
117 findObject(const as_environment& ctx, const std::string& path,
118 const as_environment::ScopeStack* scope)
120 if (path.empty()) {
121 return getObject(ctx.target());
124 VM& vm = ctx.getVM();
125 string_table& st = vm.getStringTable();
126 const int swfVersion = vm.getSWFVersion();
127 ObjectURI globalURI(NSV::PROP_uGLOBAL);
129 bool firstElementParsed = false;
130 bool dot_allowed = true;
132 // This points to the current object being used for lookup.
133 as_object* env;
134 const char* p = path.c_str();
136 // Check if it's an absolute path
137 if (*p == '/') {
139 MovieClip* root = 0;
140 if (ctx.target()) root = ctx.target()->getAsRoot();
141 else {
142 if (ctx.get_original_target()) {
143 root = ctx.get_original_target()->getAsRoot();
145 return 0;
148 // If the path is just "/" return the root.
149 if (!*(++p)) return getObject(root);
151 // Otherwise we start at the root for lookup.
152 env = getObject(root);
153 firstElementParsed = true;
154 dot_allowed = false;
157 else {
158 env = getObject(ctx.target());
161 assert (*p);
163 std::string subpart;
165 while (1) {
167 // Skip past all colons (why?)
168 while (*p == ':') ++p;
170 if (!*p) {
171 // No more components to scan, so return the currently found
172 // object.
173 return env;
176 // Search for the next '/', ':' or '.'.
177 const char* next_slash = next_slash_or_dot(p);
178 subpart = p;
180 // Check whether p was pointing to one of those characters already.
181 if (next_slash == p) {
182 IF_VERBOSE_ASCODING_ERRORS(
183 log_aserror(_("invalid path '%s' (p=next_slash=%s)"),
184 path, next_slash);
186 return 0;
189 if (next_slash) {
190 if (*next_slash == '.') {
192 if (!dot_allowed) {
193 IF_VERBOSE_ASCODING_ERRORS(
194 log_aserror(_("invalid path '%s' (dot not allowed "
195 "after having seen a slash)"), path);
197 return 0;
199 // No dot allowed after a double-dot.
200 if (next_slash[1] == '.') dot_allowed = false;
202 else if (*next_slash == '/') {
203 dot_allowed = false;
206 // Cut off the slash and everything after it.
207 subpart.resize(next_slash - p);
210 assert(subpart[0] != ':');
212 // No more components to scan
213 if (subpart.empty()) break;
215 const ObjectURI subpartURI(getURI(vm, subpart));
217 if (!firstElementParsed) {
218 as_object* element(0);
220 do {
221 // Try scope stack
222 if (scope) {
223 for (size_t i = scope->size(); i > 0; --i) {
224 as_object* obj = (*scope)[i-1];
226 element = getElement(obj, subpartURI);
227 if (element) break;
229 if (element) break;
232 // Try current target (if any)
233 assert(env == getObject(ctx.target()));
234 if (env) {
235 element = getElement(env, subpartURI);
236 if (element) break;
239 // Looking for _global ?
240 as_object* global = vm.getGlobal();
241 const bool nocase = caseless(*global);
243 if (swfVersion > 5) {
244 const ObjectURI::CaseEquals ce(st, nocase);
245 if (ce(subpartURI, globalURI)) {
246 element = global;
247 break;
251 // Look for globals.
252 element = getElement(global, subpartURI);
254 } while (0);
256 if (!element) return 0;
258 env = element;
259 firstElementParsed = true;
261 else {
263 assert(env);
264 as_object* element = getElement(env, subpartURI);
265 if (!element) return 0;
266 env = element;
269 if (!next_slash) break;
271 p = next_slash + 1;
273 return env;
277 as_environment::get_version() const
279 return _vm.getSWFVersion();
282 void
283 as_environment::markReachableResources() const
285 if (_target) _target->setReachable();
286 if (_original_target) _original_target->setReachable();
289 as_value
290 getVariable(const as_environment& env, const std::string& varname,
291 const as_environment::ScopeStack& scope, as_object** retTarget)
293 // Path lookup rigamarole.
294 std::string path;
295 std::string var;
297 if (parsePath(varname, path, var)) {
298 // TODO: let find_target return generic as_objects, or use 'with' stack,
299 // see player2.swf or bug #18758 (strip.swf)
300 as_object* target = findObject(env, path, &scope);
302 if (target) {
303 as_value val;
304 target->get_member(getURI(env.getVM(), var), &val);
305 if (retTarget) *retTarget = target;
306 return val;
308 else {
309 return as_value();
313 if (varname.find('/') != std::string::npos &&
314 varname.find(':') == std::string::npos) {
316 // Consider it all a path ...
317 as_object* target = findObject(env, varname, &scope);
318 if (target) {
319 // ... but only if it resolves to a sprite
320 DisplayObject* d = target->displayObject();
321 MovieClip* m = d ? d->to_movie() : 0;
322 if (m) return as_value(getObject(m));
325 return getVariableRaw(env, varname, scope, retTarget);
328 void
329 setVariable(const as_environment& env, const std::string& varname,
330 const as_value& val, const as_environment::ScopeStack& scope)
332 IF_VERBOSE_ACTION(
333 log_action("-------------- %s = %s", varname, val);
336 // Path lookup rigamarole.
337 std::string path;
338 std::string var;
340 if (parsePath(varname, path, var)) {
341 as_object* target = findObject(env, path, &scope);
342 if (target) {
343 target->set_member(getURI(env.getVM(), var), val);
345 else {
346 IF_VERBOSE_ASCODING_ERRORS(
347 log_aserror(_("Path target '%s' not found while setting %s=%s"),
348 path, varname, val);
351 return;
354 setVariableRaw(env, varname, val, scope);
357 bool
358 delVariable(const as_environment& ctx, const std::string& varname,
359 const as_environment::ScopeStack& scope)
361 // varname must be a plain variable name; no path parsing.
362 assert(varname.find_first_of(":/.") == std::string::npos);
364 VM& vm = ctx.getVM();
366 const ObjectURI& varkey = getURI(vm, varname);
368 // Check the with-stack.
369 for (size_t i = scope.size(); i > 0; --i) {
370 as_object* obj = scope[i - 1];
372 if (obj) {
373 std::pair<bool, bool> ret = obj->delProperty(varkey);
374 if (ret.first) {
375 return ret.second;
380 // Check locals for deletion.
381 if (vm.calling() && deleteLocal(vm.currentCall().locals(), varname)) {
382 return true;
385 // Try target
386 std::pair<bool, bool> ret = getObject(ctx.target())->delProperty(varkey);
387 if (ret.first) {
388 return ret.second;
391 // TODO: try 'this' ? Add a testcase for it !
393 // Try _global
394 return vm.getGlobal()->delProperty(varkey).second;
397 bool
398 parsePath(const std::string& var_path_in, std::string& path, std::string& var)
401 const size_t lastDotOrColon = var_path_in.find_last_of(":.");
402 if (lastDotOrColon == std::string::npos) return false;
404 const std::string p(var_path_in, 0, lastDotOrColon);
405 const std::string v(var_path_in, lastDotOrColon + 1, var_path_in.size());
407 #ifdef DEBUG_TARGET_FINDING
408 log_debug("path: %s, var: %s", p, v);
409 #endif
411 if (p.empty()) return false;
413 // The path may apparently not end with more than one colon.
414 if (p.size() > 1 && !p.compare(p.size() - 2, 2, "::")) return false;
416 path = p;
417 var = v;
419 return true;
422 namespace {
424 static bool
425 validRawVariableName(const std::string& varname)
427 if (varname.empty()) return false;
429 if (varname[0] == '.') return false;
431 if (varname[0] == ':' &&
432 varname.find_first_of(":.", 1) == std::string::npos) {
433 return false;
435 return (varname.find(":::") == std::string::npos);
438 // No path rigamarole.
439 void
440 setVariableRaw(const as_environment& env, const std::string& varname,
441 const as_value& val, const as_environment::ScopeStack& scope)
444 if (!validRawVariableName(varname)) {
445 IF_VERBOSE_ASCODING_ERRORS(
446 log_aserror(_("Won't set invalid raw variable name: %s"), varname);
448 return;
451 VM& vm = env.getVM();
452 const ObjectURI& varkey = getURI(vm, varname);
454 // in SWF5 and lower, scope stack should just contain 'with' elements
456 // Check the scope stack.
457 for (size_t i = scope.size(); i > 0; --i) {
458 as_object* obj = scope[i - 1];
459 if (obj && obj->set_member(varkey, val, true)) {
460 return;
464 const int swfVersion = vm.getSWFVersion();
465 if (swfVersion < 6 && vm.calling()) {
466 if (setLocal(vm.currentCall().locals(), varname, val)) return;
469 // TODO: shouldn't _target be in the scope chain ?
470 if (env.target()) getObject(env.target())->set_member(varkey, val);
471 else if (env.get_original_target()) {
472 getObject(env.get_original_target())->set_member(varkey, val);
474 else {
475 log_error("as_environment::setVariableRaw(%s, %s): "
476 "neither current target nor original target are defined, "
477 "can't set the variable",
478 varname, val);
482 as_value
483 getVariableRaw(const as_environment& env, const std::string& varname,
484 const as_environment::ScopeStack& scope, as_object** retTarget)
487 if (!validRawVariableName(varname)) {
488 IF_VERBOSE_ASCODING_ERRORS(
489 log_aserror(_("Won't get invalid raw variable name: %s"), varname);
491 return as_value();
494 as_value val;
496 VM& vm = env.getVM();
497 const int swfVersion = vm.getSWFVersion();
498 const ObjectURI& key = getURI(vm, varname);
500 // Check the scope stack.
501 for (size_t i = scope.size(); i > 0; --i) {
503 as_object* obj = scope[i - 1];
504 if (obj && obj->get_member(key, &val)) {
505 if (retTarget) *retTarget = obj;
506 return val;
510 // Check locals for getting them
511 // for SWF6 and up locals should be in the scope stack
512 if (swfVersion < 6 && vm.calling()) {
513 if (findLocal(vm.currentCall().locals(), varname, val, retTarget)) {
514 return val;
518 // Check current target members. TODO: shouldn't target be in scope stack ?
519 if (env.target()) {
520 as_object* obj = getObject(env.target());
521 assert(obj);
522 if (obj->get_member(key, &val)) {
523 if (retTarget) *retTarget = obj;
524 return val;
527 else if (env.get_original_target()) {
528 as_object* obj = getObject(env.get_original_target());
529 assert(obj);
530 if (obj->get_member(key, &val)) {
531 if (retTarget) *retTarget = obj;
532 return val;
536 // Looking for "this" (TODO: add NSV::PROP_THIS)
537 if (varname == "this") {
538 val.set_as_object(getObject(env.get_original_target()));
539 if (retTarget) *retTarget = NULL; // correct ??
540 return val;
543 as_object* global = vm.getGlobal();
545 // TODO: check if we really want case-sensitive comparison
546 ObjectURI::CaseEquals eq(getVM(env).getStringTable());
547 if (swfVersion > 5 && eq(key, NSV::PROP_uGLOBAL)) {
548 #ifdef GNASH_DEBUG_GET_VARIABLE
549 log_debug("Took %s as _global, returning _global", varname);
550 #endif
551 // The "_global" ref was added in SWF6
552 if (retTarget) *retTarget = NULL; // correct ??
553 return as_value(global);
556 if (global->get_member(key, &val)) {
557 #ifdef GNASH_DEBUG_GET_VARIABLE
558 log_debug("Found %s in _global", varname);
559 #endif
560 if (retTarget) *retTarget = global;
561 return val;
564 // Fallback.
565 // FIXME, should this be log_error? or log_swferror?
566 IF_VERBOSE_ASCODING_ERRORS(
567 log_aserror(_("reference to non-existent variable '%s'"), varname);
570 return as_value();
573 bool
574 getLocal(as_object& locals, const std::string& name, as_value& ret)
576 return locals.get_member(getURI(getVM(locals), name), &ret);
579 bool
580 findLocal(as_object& locals, const std::string& varname, as_value& ret,
581 as_object** retTarget)
584 if (getLocal(locals, varname, ret)) {
585 if (retTarget) *retTarget = &locals;
586 return true;
589 return false;
592 bool
593 deleteLocal(as_object& locals, const std::string& varname)
595 return locals.delProperty(getURI(getVM(locals), varname)).second;
598 bool
599 setLocal(as_object& locals, const std::string& varname, const as_value& val)
601 Property* prop = locals.getOwnProperty(getURI(getVM(locals), varname));
602 if (!prop) return false;
603 prop->setValue(locals, val);
604 return true;
607 as_object*
608 getElement(as_object* obj, const ObjectURI& uri)
610 DisplayObject* d = obj->displayObject();
611 if (d) return d->pathElement(uri);
612 return getPathElement(*obj, uri);
615 static const char*
616 next_slash_or_dot(const char* word)
618 for (const char* p = word; *p; p++) {
619 if (*p == '.' && p[1] == '.') {
620 ++p;
622 else if (*p == '.' || *p == '/' || *p == ':') {
623 return p;
626 return 0;
629 } // unnamed namespace
631 DisplayObject*
632 findTarget(const as_environment& env, const std::string& path)
634 return get<DisplayObject>(findObject(env, path));
638 string_table&
639 getStringTable(const as_environment& env)
641 return env.getVM().getStringTable();
644 movie_root&
645 getRoot(const as_environment& env)
647 return env.getVM().getRoot();
650 Global_as&
651 getGlobal(const as_environment& env)
653 return *env.getVM().getGlobal();
657 getSWFVersion(const as_environment& env)
659 return env.getVM().getSWFVersion();
662 } // end of gnash namespace
666 // Local Variables:
667 // mode: C++
668 // indent-tabs-mode: t
669 // End: