don't use CROSS_FLAGS for executables, as -all-staic prevents Android from looking...
[gnash.git] / libcore / as_environment.cpp
blobdc03fc75a0f6bd5833f1ac4c8cd5f8bee9b0a9c3
1 // as_environment.cpp: Variable, Sprite, and Movie locators, 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.
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 "MovieClip.h"
29 #include "movie_root.h"
30 #include "as_value.h"
31 #include "VM.h"
32 #include "log.h"
33 #include "Property.h"
34 #include "as_object.h"
35 #include "namedStrings.h"
36 #include "CallStack.h"
37 #include "Global_as.h"
39 // Define this to have find_target() calls trigger debugging output
40 //#define DEBUG_TARGET_FINDING 1
42 // Define this to have get_variable() calls trigger debugging output
43 //#define GNASH_DEBUG_GET_VARIABLE 1
45 namespace gnash {
47 namespace {
48 /// Find a variable in the given as_object
50 /// @param varname
51 /// Name of the local variable
52 ///
53 /// @param ret
54 /// If a variable is found it's assigned to this parameter.
55 /// Untouched if the variable is not found.
56 ///
57 /// @return true if the variable was found, false otherwise
58 bool getLocal(as_object& locals, const std::string& name, as_value& ret);
60 bool findLocal(as_object& locals, const std::string& varname, as_value& ret,
61 as_object** retTarget);
63 /// Delete a local variable
65 /// @param varname
66 /// Name of the local variable
67 ///
68 /// @return true if the variable was found and deleted, false otherwise
69 bool deleteLocal(as_object& locals, const std::string& varname);
71 /// Set a variable of the given object, if it exists.
73 /// @param varname
74 /// Name of the local variable
75 ///
76 /// @param val
77 /// Value to assign to the variable
78 ///
79 /// @return true if the variable was found, false otherwise
80 bool setLocal(as_object& locals, const std::string& varname,
81 const as_value& val);
83 as_object* getElement(as_object* obj, const ObjectURI& uri);
85 /// @param retTarget
86 /// If not NULL, the pointer will be set to the actual object containing the
87 /// found variable (if found).
88 as_value getVariableRaw(const as_environment& env,
89 const std::string& varname,
90 const as_environment::ScopeStack& scope,
91 as_object** retTarget = 0);
93 void setVariableRaw(const as_environment& env, const std::string& varname,
94 const as_value& val, const as_environment::ScopeStack& scope);
96 // Search for next '.' or '/' character in this word. Return
97 // a pointer to it, or null if it wasn't found.
98 static const char* next_slash_or_dot(const char* word);
100 static bool validRawVariableName(const std::string& varname);
104 as_value as_environment::undefVal;
106 as_environment::as_environment(VM& vm)
108 _vm(vm),
109 _stack(_vm.getStack()),
110 _target(0),
111 _original_target(0)
115 as_object*
116 findObject(const as_environment& ctx, const std::string& path,
117 const as_environment::ScopeStack* scope)
119 if (path.empty()) {
120 return getObject(ctx.target());
123 VM& vm = ctx.getVM();
124 string_table& st = vm.getStringTable();
125 const int swfVersion = vm.getSWFVersion();
126 ObjectURI globalURI(NSV::PROP_uGLOBAL);
128 bool firstElementParsed = false;
129 bool dot_allowed = true;
131 // This points to the current object being used for lookup.
132 as_object* env;
133 const char* p = path.c_str();
135 // Check if it's an absolute path
136 if (*p == '/') {
138 MovieClip* root = 0;
139 if (ctx.target()) root = ctx.target()->getAsRoot();
140 else {
141 if (ctx.get_original_target()) {
142 root = ctx.get_original_target()->getAsRoot();
144 return 0;
147 // If the path is just "/" return the root.
148 if (!*(++p)) return getObject(root);
150 // Otherwise we start at the root for lookup.
151 env = getObject(root);
152 firstElementParsed = true;
153 dot_allowed = false;
156 else {
157 env = getObject(ctx.target());
160 assert (*p);
162 std::string subpart;
164 while (1) {
166 // Skip past all colons (why?)
167 while (*p == ':') ++p;
169 if (!*p) {
170 // No more components to scan, so return the currently found
171 // object.
172 return env;
175 // Search for the next '/', ':' or '.'.
176 const char* next_slash = next_slash_or_dot(p);
177 subpart = p;
179 // Check whether p was pointing to one of those characters already.
180 if (next_slash == p) {
181 IF_VERBOSE_ASCODING_ERRORS(
182 log_aserror(_("invalid path '%s' (p=next_slash=%s)"),
183 path, next_slash);
185 return 0;
188 if (next_slash) {
189 if (*next_slash == '.') {
191 if (!dot_allowed) {
192 IF_VERBOSE_ASCODING_ERRORS(
193 log_aserror(_("invalid path '%s' (dot not allowed "
194 "after having seen a slash)"), path);
196 return 0;
198 // No dot allowed after a double-dot.
199 if (next_slash[1] == '.') dot_allowed = false;
201 else if (*next_slash == '/') {
202 dot_allowed = false;
205 // Cut off the slash and everything after it.
206 subpart.resize(next_slash - p);
209 assert(subpart[0] != ':');
211 // No more components to scan
212 if (subpart.empty()) break;
214 const ObjectURI subpartURI(getURI(vm, subpart));
216 if (!firstElementParsed) {
217 as_object* element(0);
219 do {
220 // Try scope stack
221 if (scope) {
222 for (size_t i = scope->size(); i > 0; --i) {
223 as_object* obj = (*scope)[i-1];
225 element = getElement(obj, subpartURI);
226 if (element) break;
228 if (element) break;
231 // Try current target (if any)
232 assert(env == getObject(ctx.target()));
233 if (env) {
234 element = getElement(env, subpartURI);
235 if (element) break;
238 // Looking for _global ?
239 as_object* global = vm.getGlobal();
240 const bool nocase = caseless(*global);
242 if (swfVersion > 5) {
243 const ObjectURI::CaseEquals ce(st, nocase);
244 if (ce(subpartURI, globalURI)) {
245 element = global;
246 break;
250 // Look for globals.
251 element = getElement(global, subpartURI);
253 } while (0);
255 if (!element) return 0;
257 env = element;
258 firstElementParsed = true;
260 else {
262 assert(env);
263 as_object* element = getElement(env, subpartURI);
264 if (!element) return 0;
265 env = element;
268 if (!next_slash) break;
270 p = next_slash + 1;
272 return env;
276 as_environment::get_version() const
278 return _vm.getSWFVersion();
281 void
282 as_environment::markReachableResources() const
284 if (_target) _target->setReachable();
285 if (_original_target) _original_target->setReachable();
288 as_value
289 getVariable(const as_environment& env, const std::string& varname,
290 const as_environment::ScopeStack& scope, as_object** retTarget)
292 // Path lookup rigamarole.
293 std::string path;
294 std::string var;
296 if (parsePath(varname, path, var)) {
297 // TODO: let find_target return generic as_objects, or use 'with' stack,
298 // see player2.swf or bug #18758 (strip.swf)
299 as_object* target = findObject(env, path, &scope);
301 if (target) {
302 as_value val;
303 target->get_member(getURI(env.getVM(), var), &val);
304 if (retTarget) *retTarget = target;
305 return val;
307 else {
308 return as_value();
312 if (varname.find('/') != std::string::npos &&
313 varname.find(':') == std::string::npos) {
315 // Consider it all a path ...
316 as_object* target = findObject(env, varname, &scope);
317 if (target) {
318 // ... but only if it resolves to a sprite
319 DisplayObject* d = target->displayObject();
320 MovieClip* m = d ? d->to_movie() : 0;
321 if (m) return as_value(getObject(m));
324 return getVariableRaw(env, varname, scope, retTarget);
327 void
328 setVariable(const as_environment& env, const std::string& varname,
329 const as_value& val, const as_environment::ScopeStack& scope)
331 IF_VERBOSE_ACTION(
332 log_action(_("-------------- %s = %s"), varname, val);
335 // Path lookup rigamarole.
336 std::string path;
337 std::string var;
339 if (parsePath(varname, path, var)) {
340 as_object* target = findObject(env, path, &scope);
341 if (target) {
342 target->set_member(getURI(env.getVM(), var), val);
344 else {
345 IF_VERBOSE_ASCODING_ERRORS(
346 log_aserror(_("Path target '%s' not found while setting %s=%s"),
347 path, varname, val);
350 return;
353 setVariableRaw(env, varname, val, scope);
356 bool
357 delVariable(const as_environment& ctx, const std::string& varname,
358 const as_environment::ScopeStack& scope)
360 // varname must be a plain variable name; no path parsing.
361 assert(varname.find_first_of(":/.") == std::string::npos);
363 VM& vm = ctx.getVM();
365 const ObjectURI& varkey = getURI(vm, varname);
367 // Check the with-stack.
368 for (size_t i = scope.size(); i > 0; --i) {
369 as_object* obj = scope[i - 1];
371 if (obj) {
372 std::pair<bool, bool> ret = obj->delProperty(varkey);
373 if (ret.first) {
374 return ret.second;
379 // Check locals for deletion.
380 if (vm.calling() && deleteLocal(vm.currentCall().locals(), varname)) {
381 return true;
384 // Try target
385 std::pair<bool, bool> ret = getObject(ctx.target())->delProperty(varkey);
386 if (ret.first) {
387 return ret.second;
390 // TODO: try 'this' ? Add a testcase for it !
392 // Try _global
393 return vm.getGlobal()->delProperty(varkey).second;
396 bool
397 parsePath(const std::string& var_path_in, std::string& path, std::string& var)
400 const size_t lastDotOrColon = var_path_in.find_last_of(":.");
401 if (lastDotOrColon == std::string::npos) return false;
403 const std::string p(var_path_in, 0, lastDotOrColon);
404 const std::string v(var_path_in, lastDotOrColon + 1, var_path_in.size());
406 #ifdef DEBUG_TARGET_FINDING
407 log_debug("path: %s, var: %s", p, v);
408 #endif
410 if (p.empty()) return false;
412 // The path may apparently not end with more than one colon.
413 if (p.size() > 1 && !p.compare(p.size() - 2, 2, "::")) return false;
415 path = p;
416 var = v;
418 return true;
421 namespace {
423 static bool
424 validRawVariableName(const std::string& varname)
426 if (varname.empty()) return false;
428 if (varname[0] == '.') return false;
430 if (varname[0] == ':' &&
431 varname.find_first_of(":.", 1) == std::string::npos) {
432 return false;
434 return (varname.find(":::") == std::string::npos);
437 // No path rigamarole.
438 void
439 setVariableRaw(const as_environment& env, const std::string& varname,
440 const as_value& val, const as_environment::ScopeStack& scope)
443 if (!validRawVariableName(varname)) {
444 IF_VERBOSE_ASCODING_ERRORS(
445 log_aserror(_("Won't set invalid raw variable name: %s"), varname);
447 return;
450 VM& vm = env.getVM();
451 const ObjectURI& varkey = getURI(vm, varname);
453 // in SWF5 and lower, scope stack should just contain 'with' elements
455 // Check the scope stack.
456 for (size_t i = scope.size(); i > 0; --i) {
457 as_object* obj = scope[i - 1];
458 if (obj && obj->set_member(varkey, val, true)) {
459 return;
463 const int swfVersion = vm.getSWFVersion();
464 if (swfVersion < 6 && vm.calling()) {
465 if (setLocal(vm.currentCall().locals(), varname, val)) return;
468 // TODO: shouldn't _target be in the scope chain ?
469 if (env.target()) getObject(env.target())->set_member(varkey, val);
470 else if (env.get_original_target()) {
471 getObject(env.get_original_target())->set_member(varkey, val);
473 else {
474 log_error(_("as_environment::setVariableRaw(%s, %s): neither current target nor original target are defined, can't set the variable"),
475 varname, val);
479 as_value
480 getVariableRaw(const as_environment& env, const std::string& varname,
481 const as_environment::ScopeStack& scope, as_object** retTarget)
484 if (!validRawVariableName(varname)) {
485 IF_VERBOSE_ASCODING_ERRORS(
486 log_aserror(_("Won't get invalid raw variable name: %s"), varname);
488 return as_value();
491 as_value val;
493 VM& vm = env.getVM();
494 const int swfVersion = vm.getSWFVersion();
495 const ObjectURI& key = getURI(vm, varname);
497 // Check the scope stack.
498 for (size_t i = scope.size(); i > 0; --i) {
500 as_object* obj = scope[i - 1];
501 if (obj && obj->get_member(key, &val)) {
502 if (retTarget) *retTarget = obj;
503 return val;
507 // Check locals for getting them
508 // for SWF6 and up locals should be in the scope stack
509 if (swfVersion < 6 && vm.calling()) {
510 if (findLocal(vm.currentCall().locals(), varname, val, retTarget)) {
511 return val;
515 // Check current target members. TODO: shouldn't target be in scope stack ?
516 if (env.target()) {
517 as_object* obj = getObject(env.target());
518 assert(obj);
519 if (obj->get_member(key, &val)) {
520 if (retTarget) *retTarget = obj;
521 return val;
524 else if (env.get_original_target()) {
525 as_object* obj = getObject(env.get_original_target());
526 assert(obj);
527 if (obj->get_member(key, &val)) {
528 if (retTarget) *retTarget = obj;
529 return val;
533 // AS1 has neither "this" nor any global object.
534 if (swfVersion < 5) return as_value();
536 const ObjectURI::CaseEquals eq(getVM(env).getStringTable(),
537 swfVersion < 7);
539 // Looking for "this"
540 if (eq(key, NSV::PROP_THIS)) {
541 val.set_as_object(getObject(env.get_original_target()));
542 if (retTarget) *retTarget = NULL;
543 return val;
546 as_object* global = vm.getGlobal();
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: