1 // as_environment.cpp: Variable, Sprite, and Movie locators, for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 // Free Software Foundation, Inc
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.
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"
25 #include <boost/algorithm/string/case_conv.hpp>
26 #include <boost/format.hpp>
28 #include "MovieClip.h"
29 #include "movie_root.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
48 /// Find a variable in the given as_object
51 /// Name of the local variable
54 /// If a variable is found it's assigned to this parameter.
55 /// Untouched if the variable is not found.
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
66 /// Name of the local variable
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.
74 /// Name of the local variable
77 /// Value to assign to the variable
79 /// @return true if the variable was found, false otherwise
80 bool setLocal(as_object
& locals
, const std::string
& varname
,
83 as_object
* getElement(as_object
* obj
, const ObjectURI
& uri
);
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
)
109 _stack(_vm
.getStack()),
116 findObject(const as_environment
& ctx
, const std::string
& path
,
117 const as_environment::ScopeStack
* scope
)
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.
133 const char* p
= path
.c_str();
135 // Check if it's an absolute path
139 if (ctx
.target()) root
= ctx
.target()->getAsRoot();
141 if (ctx
.get_original_target()) {
142 root
= ctx
.get_original_target()->getAsRoot();
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;
157 env
= getObject(ctx
.target());
166 // Skip past all colons (why?)
167 while (*p
== ':') ++p
;
170 // No more components to scan, so return the currently found
175 // Search for the next '/', ':' or '.'.
176 const char* next_slash
= next_slash_or_dot(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)"),
189 if (*next_slash
== '.') {
192 IF_VERBOSE_ASCODING_ERRORS(
193 log_aserror(_("invalid path '%s' (dot not allowed "
194 "after having seen a slash)"), path
);
198 // No dot allowed after a double-dot.
199 if (next_slash
[1] == '.') dot_allowed
= false;
201 else if (*next_slash
== '/') {
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);
222 for (size_t i
= scope
->size(); i
> 0; --i
) {
223 as_object
* obj
= (*scope
)[i
-1];
225 element
= getElement(obj
, subpartURI
);
231 // Try current target (if any)
232 assert(env
== getObject(ctx
.target()));
234 element
= getElement(env
, subpartURI
);
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
)) {
251 element
= getElement(global
, subpartURI
);
255 if (!element
) return 0;
258 firstElementParsed
= true;
263 as_object
* element
= getElement(env
, subpartURI
);
264 if (!element
) return 0;
268 if (!next_slash
) break;
276 as_environment::get_version() const
278 return _vm
.getSWFVersion();
282 as_environment::markReachableResources() const
284 if (_target
) _target
->setReachable();
285 if (_original_target
) _original_target
->setReachable();
289 getVariable(const as_environment
& env
, const std::string
& varname
,
290 const as_environment::ScopeStack
& scope
, as_object
** retTarget
)
292 // Path lookup rigamarole.
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
);
303 target
->get_member(getURI(env
.getVM(), var
), &val
);
304 if (retTarget
) *retTarget
= target
;
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
);
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
);
328 setVariable(const as_environment
& env
, const std::string
& varname
,
329 const as_value
& val
, const as_environment::ScopeStack
& scope
)
332 log_action(_("-------------- %s = %s"), varname
, val
);
335 // Path lookup rigamarole.
339 if (parsePath(varname
, path
, var
)) {
340 as_object
* target
= findObject(env
, path
, &scope
);
342 target
->set_member(getURI(env
.getVM(), var
), val
);
345 IF_VERBOSE_ASCODING_ERRORS(
346 log_aserror(_("Path target '%s' not found while setting %s=%s"),
353 setVariableRaw(env
, varname
, val
, scope
);
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];
372 std::pair
<bool, bool> ret
= obj
->delProperty(varkey
);
379 // Check locals for deletion.
380 if (vm
.calling() && deleteLocal(vm
.currentCall().locals(), varname
)) {
385 std::pair
<bool, bool> ret
= getObject(ctx
.target())->delProperty(varkey
);
390 // TODO: try 'this' ? Add a testcase for it !
393 return vm
.getGlobal()->delProperty(varkey
).second
;
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
);
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;
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
) {
434 return (varname
.find(":::") == std::string::npos
);
437 // No path rigamarole.
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
);
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)) {
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
);
474 log_error(_("as_environment::setVariableRaw(%s, %s): neither current target nor original target are defined, can't set the variable"),
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
);
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
;
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
)) {
515 // Check current target members. TODO: shouldn't target be in scope stack ?
517 as_object
* obj
= getObject(env
.target());
519 if (obj
->get_member(key
, &val
)) {
520 if (retTarget
) *retTarget
= obj
;
524 else if (env
.get_original_target()) {
525 as_object
* obj
= getObject(env
.get_original_target());
527 if (obj
->get_member(key
, &val
)) {
528 if (retTarget
) *retTarget
= obj
;
533 // AS1 has neither "this" nor any global object.
534 if (swfVersion
< 5) return as_value();
536 const ObjectURI::CaseEquals
eq(getVM(env
).getStringTable(),
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
;
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
);
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
);
560 if (retTarget
) *retTarget
= global
;
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
);
574 getLocal(as_object
& locals
, const std::string
& name
, as_value
& ret
)
576 return locals
.get_member(getURI(getVM(locals
), name
), &ret
);
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
;
593 deleteLocal(as_object
& locals
, const std::string
& varname
)
595 return locals
.delProperty(getURI(getVM(locals
), varname
)).second
;
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
);
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
);
616 next_slash_or_dot(const char* word
)
618 for (const char* p
= word
; *p
; p
++) {
619 if (*p
== '.' && p
[1] == '.') {
622 else if (*p
== '.' || *p
== '/' || *p
== ':') {
629 } // unnamed namespace
632 findTarget(const as_environment
& env
, const std::string
& path
)
634 return get
<DisplayObject
>(findObject(env
, path
));
639 getStringTable(const as_environment
& env
)
641 return env
.getVM().getStringTable();
645 getRoot(const as_environment
& env
)
647 return env
.getVM().getRoot();
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
668 // indent-tabs-mode: t