1
// as_environment.cpp: Variable, Sprite, and Movie locators, for Gnash.
3
// Copyright (C) 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
5
// This program is free software; you can redistribute it and/or modify
6
// it under the terms of the GNU General Public License as published by
7
// the Free Software Foundation; either version 3 of the License, or
8
// (at your option) any later version.
10
// This program is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
// GNU General Public License for more details.
15
// You should have received a copy of the GNU General Public License
16
// along with this program; if not, write to the Free Software
17
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
#include "smart_ptr.h" // GNASH_USE_GC
21
#include "as_environment.h"
22
#include "sprite_instance.h"
23
#include "shape_character_def.h"
25
#include "with_stack_entry.h"
29
#include "as_object.h"
30
#include "namedStrings.h"
31
#include "as_function.h"
32
#include "CallStack.h"
34
#include <cstring> // std::strpbrk
36
#include <utility> // for std::pair
37
#include <boost/algorithm/string/case_conv.hpp>
38
#include <boost/format.hpp>
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
48
as_value as_environment::undefVal;
50
as_environment::as_environment(VM& vm)
53
_stack(_vm.getStack()),
54
_localFrames(_vm.getCallStack()),
60
// Return the value of the given var, if it's defined.
62
as_environment::get_variable(const std::string& varname,
63
const ScopeStack& scopeStack, as_object** retTarget) const
65
// Path lookup rigamarole.
69
#ifdef GNASH_DEBUG_GET_VARIABLE
70
log_debug(_("get_variable(%s)"), varname);
73
if ( parse_path(varname, path, var) )
75
// TODO: let find_target return generic as_objects, or use 'with' stack,
76
// see player2.swf or bug #18758 (strip.swf)
77
// @@ TODO: should we use scopeStack here too ?
78
as_object* target = find_object(path, &scopeStack);
83
target->get_member(_vm.getStringTable().find(var), &val);
84
if ( retTarget ) *retTarget = target;
90
IF_VERBOSE_ASCODING_ERRORS(
91
log_aserror(_("find_object(\"%s\") [ varname = '%s' - "
92
"current target = '%s' ] failed"),
93
path, varname, m_target ? m_target->get_text_value() : "<null>");
94
as_value tmp = get_variable_raw(path, scopeStack, retTarget);
95
if ( ! tmp.is_undefined() )
97
log_aserror(_("...but get_variable_raw(%s, <scopeStack>) "
98
"succeeded (%s)!"), path, tmp);
101
return as_value(); // TODO: should we check get_variable_raw ?
106
// TODO: have this checked by parse_path as an optimization
107
if ( varname.find_first_of('/') != std::string::npos && varname.find_first_of(':') == std::string::npos )
109
// Consider it all a path ...
110
as_object* target = find_object(varname, &scopeStack);
113
// ... but only if it resolves to a sprite
114
sprite_instance* m = target->to_movie();
115
if ( m ) return as_value(m);
118
return get_variable_raw(varname, scopeStack, retTarget);
123
as_environment::get_variable(const std::string& varname) const
125
static ScopeStack empty_scopeStack;
126
return get_variable(varname, empty_scopeStack);
129
static bool validRawVariableName(const std::string& varname)
131
return (varname.find(":::") == std::string::npos);
135
as_environment::get_variable_raw(
136
const std::string& varname,
137
const ScopeStack& scopeStack, as_object** retTarget) const
138
// varname must be a plain variable name; no path parsing.
140
//assert(strchr(varname.c_str(), ':') == NULL);
142
if ( ! validRawVariableName(varname) )
144
IF_VERBOSE_ASCODING_ERRORS(
145
log_aserror(_("Won't get invalid raw variable name: %s"), varname);
153
int swfVersion = vm.getSWFVersion();
154
string_table& st = vm.getStringTable();
155
string_table::key key = st.find(varname);
157
// Check the scope stack.
158
for (size_t i = scopeStack.size(); i > 0; --i)
160
// const_cast needed due to non-const as_object::get_member
161
as_object* obj = const_cast<as_object*>(scopeStack[i-1].get());
162
if (obj && obj->get_member(key, &val))
164
// Found the var in with context.
165
//log_debug("Found %s in object %d/%d of scope stack (%p)", varname, i, scopeStack.size(), obj);
166
if ( retTarget ) *retTarget = obj;
171
// Check locals for getting them
172
if ( swfVersion < 6 ) // for SWF6 and up locals should be in the scope stack
174
if ( findLocal(varname, val, retTarget) )
181
// Check current target members. TODO: shouldn't target be in scope stack ?
184
if (m_target->get_member(key, &val)) {
185
if ( retTarget ) *retTarget = m_target;
189
else if ( _original_target ) // this only for swf5+ ?
191
if (_original_target->get_member(key, &val)) {
192
if ( retTarget ) *retTarget = _original_target;
197
// Looking for "this"
198
if (varname == "this") {
199
val.set_as_object(_original_target);
200
if ( retTarget ) *retTarget = NULL; // correct ??
204
as_object* global = vm.getGlobal();
206
if ( swfVersion > 5 && key == NSV::PROP_uGLOBAL )
208
// The "_global" ref was added in SWF6
209
if ( retTarget ) *retTarget = NULL; // correct ??
210
return as_value(global);
213
if (global->get_member(key, &val))
215
if ( retTarget ) *retTarget = global;
220
// FIXME, should this be log_error? or log_swferror?
221
IF_VERBOSE_ASCODING_ERRORS (
222
log_aserror(_("reference to non-existent variable '%s'"),
230
as_environment::del_variable_raw(
231
const std::string& varname,
232
const ScopeStack& scopeStack)
233
// varname must be a plain variable name; no path parsing.
235
assert( ! std::strpbrk(varname.c_str(), ":/.") );
237
string_table::key varkey = _vm.getStringTable().find(varname);
240
// Check the with-stack.
241
for (size_t i = scopeStack.size(); i > 0; --i)
243
// const_cast needed due to non-const as_object::get_member
244
as_object* obj = const_cast<as_object*>(scopeStack[i-1].get());
247
std::pair<bool,bool> ret = obj->delProperty(varkey);
255
// Check locals for deletion.
256
if ( delLocal(varname) )
263
std::pair<bool,bool> ret = m_target->delProperty(varkey);
269
// TODO: try 'this' ? Add a testcase for it !
272
return _vm.getGlobal()->delProperty(varkey).second;
275
// varname must be a plain variable name; no path parsing.
277
as_environment::get_variable_raw(const std::string& varname) const
279
static ScopeStack empty_scopeStack;
280
return get_variable_raw(varname, empty_scopeStack);
283
// Given a path to variable, set its value.
285
as_environment::set_variable(
286
const std::string& varname,
288
const ScopeStack& scopeStack)
291
log_action("-------------- %s = %s",
295
// Path lookup rigamarole.
296
as_object* target = m_target;
299
//log_debug(_("set_variable(%s, %s)"), varname, val);
300
if ( parse_path(varname, path, var) )
302
//log_debug(_("Variable '%s' parsed into path='%s', var='%s'"), varname, path, var);
303
//target = find_target(path);
304
target = find_object(path, &scopeStack);
307
target->set_member(_vm.getStringTable().find(var), val);
311
IF_VERBOSE_ASCODING_ERRORS(
312
log_aserror(_("Path target '%s' not found while setting %s=%s"),
317
set_variable_raw(varname, val, scopeStack);
322
as_environment::set_variable(
323
const std::string& varname,
326
static ScopeStack empty_scopeStack;
327
set_variable(varname, val, empty_scopeStack);
330
// No path rigamarole.
332
as_environment::set_variable_raw(
333
const std::string& varname,
335
const ScopeStack& scopeStack)
338
if ( ! validRawVariableName(varname) )
340
IF_VERBOSE_ASCODING_ERRORS(
341
log_aserror(_("Won't set invalid raw variable name: %s"), varname);
347
int swfVersion = vm.getSWFVersion();
348
string_table& st = vm.getStringTable();
349
string_table::key varkey = st.find(varname);
351
if ( swfVersion < 6 )
353
// in SWF5 and lower, scope stack should just contain 'with' elements
355
// Check the with-stack.
356
for (size_t i = scopeStack.size(); i > 0; --i)
358
// const_cast needed due to non-const as_object::get_member
359
as_object* obj = const_cast<as_object*>(scopeStack[i-1].get());
360
if (obj && obj->set_member(varkey, val, 0, true) )
366
// Check locals for setting them
367
if ( setLocal(varname, val) ) return;
373
// Check the scope-stack (would include locals)
375
for (size_t i = scopeStack.size(); i > 0; --i)
377
// const_cast needed due to non-const as_object::get_member
378
as_object* obj = const_cast<as_object*>(scopeStack[i-1].get());
379
if (obj && obj->set_member(varkey, val, 0, true))
387
// TODO: shouldn't m_target be in the scope chain ?
389
if ( m_target ) m_target->set_member(varkey, val);
390
else if ( _original_target ) _original_target->set_member(varkey, val);
393
log_error("as_environment(%p)::set_variable_raw(%s, %s): "
394
"neither current target nor original target are defined, "
395
"can't set the variable",
401
as_environment::set_variable_raw(
402
const std::string& varname,
405
static ScopeStack empty_scopeStack;
406
set_variable_raw(varname, val, empty_scopeStack);
409
// Set/initialize the value of the local variable.
411
as_environment::set_local(const std::string& varname, const as_value& val)
413
// why would you want to set a local if there's no call frame on the
415
assert( ! _localFrames.empty() );
417
string_table::key varkey = _vm.getStringTable().find(varname);
418
// Is it in the current frame already?
419
if ( setLocal(varname, val) )
425
// Not in frame; create a new local var.
426
assert( ! varname.empty() ); // null varnames are invalid!
427
as_object* locals = _localFrames.back().locals;
428
//locals.push_back(as_environment::frame_slot(varname, val));
429
locals->set_member(varkey, val);
433
// Create the specified local var if it doesn't exist already.
435
as_environment::declare_local(const std::string& varname)
438
if ( ! findLocal(varname, tmp) )
440
// Not in frame; create a new local var.
441
assert( ! _localFrames.empty() );
442
assert( ! varname.empty() ); // null varnames are invalid!
443
as_object* locals = _localFrames.back().locals;
444
//locals.push_back(as_environment::frame_slot(varname, as_value()));
445
locals->set_member(_vm.getStringTable().find(varname), as_value());
451
as_environment::parse_path(const std::string& var_path_in,
452
std::string& path, std::string& var)
454
#ifdef DEBUG_TARGET_FINDING
455
log_debug("parse_path(%s)", var_path_in);
458
size_t lastDotOrColon = var_path_in.find_last_of(":.");
459
if ( lastDotOrColon == std::string::npos ) return false;
461
std::string thePath, theVar;
463
thePath.assign(var_path_in, 0, lastDotOrColon);
464
theVar.assign(var_path_in, lastDotOrColon+1, var_path_in.length());
466
#ifdef DEBUG_TARGET_FINDING
467
log_debug("path: %s, var: %s", thePath, theVar);
470
if ( thePath.empty() ) return false;
472
// this check should be performed by callers (getvariable/setvariable in particular)
473
size_t pathlen = thePath.length();
474
size_t i = pathlen-1;
475
size_t contiguoscommas = 0;
476
while ( i && thePath[i--] == ':' )
478
if ( ++contiguoscommas > 1 )
480
#ifdef DEBUG_TARGET_FINDING
481
log_debug("path '%s' ends with too many colon chars, not considering a path", thePath);
487
#ifdef DEBUG_TARGET_FINDING
488
log_debug("contiguoscommas: %d", contiguoscommas);
491
//if ( var.empty() ) return false;
500
as_environment::parse_path(const std::string& var_path,
501
as_object** target, as_value& val)
505
if ( ! parse_path(var_path, path, var) ) return false;
506
as_object* target_ptr = find_object(path);
507
if ( ! target_ptr ) return false;
509
target_ptr->get_member(_vm.getStringTable().find(var), &val);
510
*target = target_ptr;
514
// Search for next '.' or '/' character in this word. Return
515
// a pointer to it, or to NULL if it wasn't found.
517
next_slash_or_dot(const char* word)
519
for (const char* p = word; *p; p++) {
520
if (*p == '.' && p[1] == '.') {
522
} else if (*p == '.' || *p == '/' || *p == ':') {
530
// Find the sprite/movie referenced by the given path.
532
// Supports both /slash/syntax and dot.syntax
535
as_environment::find_target(const std::string& path_in) const
537
as_object* o = find_object(path_in);
538
if ( o ) return o->to_character(); // can be NULL (not a character)...
543
as_environment::find_object(const std::string& path_in, const ScopeStack* scopeStack) const
545
#ifdef DEBUG_TARGET_FINDING
546
log_debug(_("find_object(%s) called"), path_in);
551
#ifdef DEBUG_TARGET_FINDING
552
log_debug(_("Returning m_target (empty path)"));
554
return m_target; // or should we return the *original* path ?
557
std::string path = PROPNAME(path_in);
559
string_table& st = vm.getStringTable();
560
int swfVersion = vm.getSWFVersion();
565
bool firstElementParsed = false;
566
bool dot_allowed=true;
568
const char* p = path.c_str();
571
// Absolute path. Start at the (AS) root (handle _lockroot)
572
sprite_instance* root = 0;
573
if ( m_target ) root = const_cast<sprite_instance*>(m_target->getAsRoot());
575
if ( _original_target )
577
log_debug("current target is undefined on as_environment::find_object, we'll use original");
578
root = const_cast<sprite_instance*>(_original_target->getAsRoot());
582
log_debug("both current and original target are undefined on as_environment::find_object, we'll return 0");
589
#ifdef DEBUG_TARGET_FINDING
590
log_debug(_("Path is '/', return the root (%p)"), (void*)root);
592
return root; // that's all folks..
596
firstElementParsed = true;
599
#ifdef DEBUG_TARGET_FINDING
600
log_debug(_("Absolute path, start at the root (%p)"), (void*)env);
604
#ifdef DEBUG_TARGET_FINDING
607
log_debug(_("Relative path, start at (%s)"), m_target->getTarget());
616
while ( *p == ':' ) ++p;
618
// No more components to scan
621
#ifdef DEBUG_TARGET_FINDING
622
log_debug(_("Path is %s, returning whatever we were up to"), path);
628
const char* next_slash = next_slash_or_dot(p);
632
IF_VERBOSE_ASCODING_ERRORS(
633
log_aserror(_("invalid path '%s' (p=next_slash=%s)"), path, next_slash);
639
if ( *next_slash == '.' )
643
IF_VERBOSE_ASCODING_ERRORS(
644
log_aserror(_("invalid path '%s' (dot not allowed after having seen a slash)"), path);
649
else if ( *next_slash == '/' )
654
// Cut off the slash and everything after it.
655
subpart.resize(next_slash - p);
658
assert(subpart[0] != ':');
660
// No more components to scan
661
if ( subpart.empty() )
663
#ifdef DEBUG_TARGET_FINDING
664
log_debug(_("No more subparts, env is %p"), (void*)env);
669
string_table::key subpartKey = st.find(subpart);
671
if ( ! firstElementParsed )
673
as_object* element = NULL;
680
for (size_t i = scopeStack->size(); i > 0; --i)
682
// const_cast needed due to non-const as_object::get_member
683
as_object* obj = const_cast<as_object*>((*scopeStack)[i-1].get());
684
element = obj->get_path_element(subpartKey);
685
if ( element ) break;
687
if ( element ) break;
690
// Try current target (if any)
691
assert(env == m_target);
694
element = env->get_path_element(subpartKey);
695
if ( element ) break;
697
// else if ( _original_target) // TODO: try orig target too ?
699
// Looking for _global ?
700
as_object* global = _vm.getGlobal();
701
if ( swfVersion > 5 && subpartKey == NSV::PROP_uGLOBAL )
708
element = global->get_path_element(subpartKey);
709
//if ( element ) break;
715
#ifdef DEBUG_TARGET_FINDING
716
log_debug("subpart %s of path %s not found in any "
717
"scope stack element", subpart, path);
723
firstElementParsed = true;
730
#ifdef DEBUG_TARGET_FINDING
731
log_debug(_("Invoking get_path_element(%s) on object "
732
"%p (%s)"), subpart, (void *)env, env->get_text_value());
735
as_object* element = env->get_path_element(subpartKey);
738
#ifdef DEBUG_TARGET_FINDING
739
log_debug(_("Path element %s not found in "
740
"object %p"), subpart, (void *)env);
747
if (next_slash == NULL)
758
as_environment::get_version() const
760
return _vm.getSWFVersion();
764
dump(const as_environment::Registers& r, std::ostream& out)
766
for (size_t i=0; i<r.size(); ++i)
769
out << i << ':' << '"' << r[i] << '"';
774
as_environment::dump_local_registers(std::ostream& out) const
776
if ( _localFrames.empty() ) return;
777
out << "Local registers: ";
778
#ifndef DUMP_LOCAL_REGISTERS_IN_ALL_CALL_FRAMES
779
dump(_localFrames.back().registers, out);
781
for (CallStack::const_iterator it=_localFrames.begin(),
782
itEnd=_localFrames.end();
785
if ( it != _localFrames.begin() ) out << " | ";
786
dump(it->registers, out);
793
dump(const as_object* locals, std::ostream& out)
795
typedef std::map<std::string, as_value> PropMap;
797
const_cast<as_object*>(locals)->dump_members(props);
799
//log_debug("FIXME: implement dumper for local variables now that they are simple objects");
801
for (PropMap::iterator i=props.begin(), e=props.end(); i!=e; ++i)
803
if (count++) out << ", ";
804
// TODO: define output operator for as_value !
805
out << i->first << "==" << i->second;
811
as_environment::dump_local_variables(std::ostream& out) const
813
if ( _localFrames.empty() ) return;
814
out << "Local variables: ";
815
#ifndef DUMP_LOCAL_VARIABLES_IN_ALL_CALL_FRAMES
816
dump(_localFrames.back().locals, out);
818
for (CallStack::const_iterator it=_localFrames.begin(),
819
itEnd=_localFrames.end();
822
if ( it != _localFrames.begin() ) out << " | ";
823
dump(it->locals, out);
830
as_environment::dump_global_registers(std::ostream& out) const
832
std::string registers;
834
std::stringstream ss;
836
ss << "Global registers: ";
838
for (unsigned int i=0; i<numGlobalRegisters; ++i)
840
if ( m_global_register[i].is_undefined() ) continue;
842
if ( defined++ ) ss << ", ";
844
ss << i << ":" << m_global_register[i];
847
if ( defined ) out << ss.str() << std::endl;
852
as_environment::findLocal(const std::string& varname, as_value& ret, as_object** retTarget)
854
if ( _localFrames.empty() ) return false;
855
if ( findLocal(_localFrames.back().locals, varname, ret) )
857
if ( retTarget ) *retTarget = _localFrames.back().locals;
865
as_environment::findLocal(as_object* locals, const std::string& name, as_value& ret)
867
// TODO: get VM passed as arg
868
return locals->get_member(_vm.getStringTable().find(name), &ret);
873
as_environment::delLocal(as_object* locals, const std::string& varname)
875
// TODO: get VM passed as arg
876
return locals->delProperty(_vm.getStringTable().find(varname)).second;
881
as_environment::delLocal(const std::string& varname)
883
if ( _localFrames.empty() ) return false;
884
return delLocal(_localFrames.back().locals, varname);
889
as_environment::setLocal(const std::string& varname, const as_value& val)
891
if ( _localFrames.empty() ) return false;
892
return setLocal(_localFrames.back().locals, varname, val);
897
as_environment::setLocal(as_object* locals,
898
const std::string& varname, const as_value& val)
900
Property* prop = locals->getOwnProperty(_vm.getStringTable().find(varname));
901
if ( ! prop ) return false;
902
prop->setValue(*locals, val);
908
as_environment::padStack(size_t /*offset*/, size_t /*count*/)
910
// do nothing here, instead return undefined from top() and pop()
911
//assert( offset <= _stack.size() );
912
//m_stack.insert(m_stack.begin()+offset, count, as_value());
916
as_environment::pushCallFrame(as_function* func)
919
// The stack size can be changed by the ScriptLimits
920
// tag. There is *no* difference between SWF versions.
921
// TODO: override from gnashrc.
923
// A stack size of 0 is apparently legitimate.
924
const boost::uint16_t maxstacksize = func->getVM().getRoot().getRecursionLimit();
926
// Doesn't proceed if the stack size would reach the limit; should
927
// this check be done somewhere after adding to the stack? Would
928
// it make any difference?
929
if ( _localFrames.size() + 1 >= maxstacksize )
931
std::ostringstream ss;
932
ss << boost::format(_("Max stack count reached (%u)")) % _localFrames.size();
935
throw ActionLimitException(ss.str());
938
_localFrames.push_back(CallFrame(func));
943
as_environment::popCallFrame()
945
assert(!_localFrames.empty());
946
_localFrames.pop_back();
950
as_environment::set_target(character* target)
953
if ( ! _original_target )
955
//assert(target); // we assume any as_environment creator sets a target too I guess..
956
// WRONG ASSUMPTION: ^^^^^^^^^ : as_value::to_primitive doesn't care about setting a target here
958
//log_debug("as_environment(%p)::set_target(%p): setting original target to %s", this, target, target ? target->getTarget() : "<null>");
959
_original_target = target;
965
as_environment::add_local(const std::string& varname, const as_value& val)
967
assert( ! varname.empty() ); // null varnames are invalid!
968
assert( ! _localFrames.empty() );
969
as_object* locals = _localFrames.back().locals;
970
//locals.push_back(frame_slot(varname, val));
971
locals->set_member(_vm.getStringTable().find(varname), val);
975
as_environment::dump_stack(std::ostream& out, unsigned int limit) const
977
unsigned int si=0, n=_stack.size();
978
if ( limit && n > limit )
981
out << "Stack (last " << limit << " of " << n << " items): ";
988
for (unsigned int i=si; i<n; i++)
990
if (i!=si) out << " | ";
991
out << '"' << _stack.value(i) << '"';
999
as_environment::setRegister(unsigned int regnum, const as_value& v)
1001
if ( _localFrames.empty() || _localFrames.back().registers.empty() )
1003
if ( regnum >= numGlobalRegisters ) return 0;
1004
m_global_register[regnum] = v;
1008
Registers& registers = _localFrames.back().registers;
1009
if ( regnum < registers.size() )
1011
registers[regnum] = v;
1019
as_environment::getRegister(unsigned int regnum, as_value& v)
1021
if ( _localFrames.empty() || _localFrames.back().registers.empty() )
1023
if ( regnum >= numGlobalRegisters ) return 0;
1024
v = m_global_register[regnum];
1028
Registers& registers = _localFrames.back().registers;
1029
if ( regnum < registers.size() )
1031
v = registers[regnum];
1039
as_environment::markReachableResources() const
1041
for (size_t i=0; i<4; ++i)
1043
m_global_register[i].setReachable();
1046
if ( m_target ) m_target->setReachable();
1047
if ( _original_target ) _original_target->setReachable();
1049
assert ( _localFrames.empty() );
1050
#if 1 // I think we expect the stack to be empty !
1051
for (CallStack::const_iterator i=_localFrames.begin(), e=_localFrames.end(); i!=e; ++i)
1053
i->markReachableResources();
1057
assert ( _stack.empty() );
1059
#endif // GNASH_USE_GC
1061
} // end of gnash namespace
1067
// indent-tabs-mode: t