1 // as_environment.cpp: Variable, Sprite, and Movie locators, for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
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 "smart_ptr.h"
29 #include "MovieClip.h"
30 #include "movie_root.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
49 /// Find a variable in the given as_object
52 /// Name of the local variable
55 /// If a variable is found it's assigned to this parameter.
56 /// Untouched if the variable is not found.
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
67 /// Name of the local variable
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.
75 /// Name of the local variable
78 /// Value to assign to the variable
80 /// @return true if the variable was found, false otherwise
81 bool setLocal(as_object
& locals
, const std::string
& varname
,
84 as_object
* getElement(as_object
* obj
, const ObjectURI
& uri
);
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
)
110 _stack(_vm
.getStack()),
117 findObject(const as_environment
& ctx
, const std::string
& path
,
118 const as_environment::ScopeStack
* scope
)
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.
134 const char* p
= path
.c_str();
136 // Check if it's an absolute path
140 if (ctx
.target()) root
= ctx
.target()->getAsRoot();
142 if (ctx
.get_original_target()) {
143 root
= ctx
.get_original_target()->getAsRoot();
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;
158 env
= getObject(ctx
.target());
167 // Skip past all colons (why?)
168 while (*p
== ':') ++p
;
171 // No more components to scan, so return the currently found
176 // Search for the next '/', ':' or '.'.
177 const char* next_slash
= next_slash_or_dot(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)"),
190 if (*next_slash
== '.') {
193 IF_VERBOSE_ASCODING_ERRORS(
194 log_aserror(_("invalid path '%s' (dot not allowed "
195 "after having seen a slash)"), path
);
199 // No dot allowed after a double-dot.
200 if (next_slash
[1] == '.') dot_allowed
= false;
202 else if (*next_slash
== '/') {
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);
223 for (size_t i
= scope
->size(); i
> 0; --i
) {
224 as_object
* obj
= (*scope
)[i
-1];
226 element
= getElement(obj
, subpartURI
);
232 // Try current target (if any)
233 assert(env
== getObject(ctx
.target()));
235 element
= getElement(env
, subpartURI
);
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
)) {
252 element
= getElement(global
, subpartURI
);
256 if (!element
) return 0;
259 firstElementParsed
= true;
264 as_object
* element
= getElement(env
, subpartURI
);
265 if (!element
) return 0;
269 if (!next_slash
) break;
277 as_environment::get_version() const
279 return _vm
.getSWFVersion();
283 as_environment::markReachableResources() const
285 if (_target
) _target
->setReachable();
286 if (_original_target
) _original_target
->setReachable();
290 getVariable(const as_environment
& env
, const std::string
& varname
,
291 const as_environment::ScopeStack
& scope
, as_object
** retTarget
)
293 // Path lookup rigamarole.
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
);
304 target
->get_member(getURI(env
.getVM(), var
), &val
);
305 if (retTarget
) *retTarget
= target
;
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
);
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
);
329 setVariable(const as_environment
& env
, const std::string
& varname
,
330 const as_value
& val
, const as_environment::ScopeStack
& scope
)
333 log_action("-------------- %s = %s", varname
, val
);
336 // Path lookup rigamarole.
340 if (parsePath(varname
, path
, var
)) {
341 as_object
* target
= findObject(env
, path
, &scope
);
343 target
->set_member(getURI(env
.getVM(), var
), val
);
346 IF_VERBOSE_ASCODING_ERRORS(
347 log_aserror(_("Path target '%s' not found while setting %s=%s"),
354 setVariableRaw(env
, varname
, val
, scope
);
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];
373 std::pair
<bool, bool> ret
= obj
->delProperty(varkey
);
380 // Check locals for deletion.
381 if (vm
.calling() && deleteLocal(vm
.currentCall().locals(), varname
)) {
386 std::pair
<bool, bool> ret
= getObject(ctx
.target())->delProperty(varkey
);
391 // TODO: try 'this' ? Add a testcase for it !
394 return vm
.getGlobal()->delProperty(varkey
).second
;
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
);
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;
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
) {
435 return (varname
.find(":::") == std::string::npos
);
438 // No path rigamarole.
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
);
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)) {
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
);
475 log_error("as_environment::setVariableRaw(%s, %s): "
476 "neither current target nor original target are defined, "
477 "can't set the variable",
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
);
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
;
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
)) {
518 // Check current target members. TODO: shouldn't target be in scope stack ?
520 as_object
* obj
= getObject(env
.target());
522 if (obj
->get_member(key
, &val
)) {
523 if (retTarget
) *retTarget
= obj
;
527 else if (env
.get_original_target()) {
528 as_object
* obj
= getObject(env
.get_original_target());
530 if (obj
->get_member(key
, &val
)) {
531 if (retTarget
) *retTarget
= obj
;
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 ??
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
);
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