"Fossies" - the Fresh Open Source Software Archive 
Member "gnash-0.8.10/libcore/vm/ActionExec.cpp" (19 Jan 2012, 23266 Bytes) of package /linux/www/old/gnash-0.8.10.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
1 // ActionExec.cpp: ActionScript execution, 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.
15 //
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
19
20 #ifdef HAVE_CONFIG_H
21 #include "gnashconfig.h"
22 #endif
23
24 #include "ActionExec.h"
25 #include "action_buffer.h"
26 #include "Function.h"
27 #include "log.h"
28 #include "VM.h"
29 #include "GnashException.h"
30 #include "DisplayObject.h"
31 #include "movie_root.h"
32 #include "SWF.h"
33 #include "ASHandlers.h"
34 #include "as_environment.h"
35 #include "SystemClock.h"
36 #include "CallStack.h"
37
38 #include <sstream>
39 #include <string>
40 #include <boost/format.hpp>
41
42 #ifndef DEBUG_STACK
43
44 // temporarily disabled as will produce lots of output with -v
45 // we'd need another switch maybe, as -va does also produce
46 // too much information for my tastes. I really want just
47 // to see how stack changes while executing actions...
48 // --strk Fri Jun 30 02:28:46 CEST 2006
49 # define DEBUG_STACK 1
50
51 // Max number of stack item to dump. 0 for unlimited.
52 # define STACK_DUMP_LIMIT 32
53
54 // Define to get debugging messages for try / catch
55 //#define GNASH_DEBUG_TRY 1
56
57 #endif
58
59 namespace gnash {
60
61 ActionExec::ActionExec(const Function& func, as_environment& newEnv,
62 as_value* nRetVal, as_object* this_ptr)
63 :
64 code(func.getActionBuffer()),
65 env(newEnv),
66 retval(nRetVal),
67 _withStack(),
68 _scopeStack(func.getScopeStack()),
69 _func(&func),
70 _this_ptr(this_ptr),
71 _initialStackSize(0),
72 _originalTarget(0),
73 _origExecSWFVersion(0),
74 _tryList(),
75 _returning(false),
76 _abortOnUnload(false),
77 pc(func.getStartPC()),
78 next_pc(pc),
79 stop_pc(pc + func.getLength())
80 {
81 assert(stop_pc < code.size());
82
83 // Functions defined in SWF version 6 and higher pushes
84 // the activation object to the scope stack
85 // NOTE: if we query env.get_version() we get version of the calling
86 // code, not the one we're about to call. This is because
87 // it is ActionExec's call operator switching the VM version!
88 //
89 // TESTCASE: winterbell from orisinal.
90 // If we query VM version here, the movie results in:
91 // set local var: i=[number:0]
92 // push 'i' getvariable
93 // get var: i=[undefined]
94 if (code.getDefinitionVersion() > 5) {
95 // We assume that the Function () operator already initialized
96 // its environment so that its activation object is now in the
97 // top element of the CallFrame stack
98 CallFrame& topFrame = getVM(newEnv).currentCall();
99 assert(&topFrame.function() == &func);
100 _scopeStack.push_back(&topFrame.locals());
101 }
102 }
103
104 ActionExec::ActionExec(const action_buffer& abuf, as_environment& newEnv,
105 bool abortOnUnloaded)
106 :
107 code(abuf),
108 env(newEnv),
109 retval(0),
110 _withStack(),
111 _scopeStack(),
112 _func(0),
113 _this_ptr(0),
114 _initialStackSize(0),
115 _originalTarget(0),
116 _origExecSWFVersion(0),
117 _tryList(),
118 _returning(false),
119 _abortOnUnload(abortOnUnloaded),
120 pc(0),
121 next_pc(0),
122 stop_pc(abuf.size())
123 {
124 }
125
126 void
127 ActionExec::operator()()
128 {
129 VM& vm = getVM(env);
130
131 // Do not execute if scripts are disabled
132 if (vm.getRoot().scriptsDisabled()) return;
133
134 _origExecSWFVersion = vm.getSWFVersion();
135
136 const int codeVersion = code.getDefinitionVersion();
137 vm.setSWFVersion(codeVersion);
138
139 static const SWF::SWFHandlers& ash = SWF::SWFHandlers::instance();
140
141 _originalTarget = env.target();
142
143 _initialStackSize = env.stack_size();
144
145 #if DEBUG_STACK
146 IF_VERBOSE_ACTION (
147 log_action(_("at ActionExec operator() start, pc=%d"
148 ", stop_pc=%d, code.size=%d, func=%d, codeVersion=%d"),
149 pc, stop_pc, code.size(), _func ? _func : 0, codeVersion);
150 std::stringstream ss;
151 getVM(env).dumpState(ss, STACK_DUMP_LIMIT);
152 log_action(_("%s"), ss.str());
153 );
154 #endif
155
156 // stop_pc: set to the code boundary at which we should check
157 // for exceptions. If there is no exception in a TryBlock, it
158 // is set to the end of that block; all the code (including catch
159 // and finally) should be executed.
160 // If 'try' finds an exception, stop_pc causes execution to stop
161 // at 'catch', while we find whether it catches the exception, and
162 // at 'finally', where we handle any exceptions thrown in the meantime.
163 //
164 // The stages of our TryBlock only roughly correspond to the ActionScript
165 // code. There may be no catch and/or finally block, but certain operations
166 // must still be carried out.
167
168 const size_t maxTime = getRoot(vm).getTimeoutLimit() * 1000;
169 SystemClock clock; // TODO: should we use a CPUClock here ?
170
171 try {
172
173 // We might not stop at stop_pc, if we are trying.
174 while (1) {
175
176 if (pc >= stop_pc) {
177 // No try blocks
178 if (_tryList.empty()) {
179
180 // Check for any uncaught exceptions.
181 if (env.stack_size() && env.top(0).is_exception()) {
182 log_debug ("Exception on stack, no handlers left.");
183 // Stop execution if an exception
184 // is still on the stack and there is nothing
185 // left to catch it.
186 cleanupAfterRun();
187
188 // Forceably clear the stack.
189 // - Fixes misc-mtasc.all/exception.swf
190 // By commenting the line above, we get an XPASS in
191 // - swfdec/catch-in-caller.swf
192 env.drop(env.stack_size());
193
194 return;
195 }
196 break;
197 }
198
199 // If we are in a try block, check to see if we have thrown.
200 TryBlock& t = _tryList.back();
201
202 if (!processExceptions(t)) break;
203
204 continue;
205 }
206
207 // Cleanup any expired "with" blocks.
208 while (!_withStack.empty() && pc >= _withStack.back().end_pc()) {
209
210 // Drop last stack element
211 assert(_withStack.back().object() == _scopeStack.back());
212 _withStack.pop_back();
213
214 // hopefully nothing gets after the 'with' stack.
215 _scopeStack.pop_back();
216 }
217
218 // Get the opcode.
219 boost::uint8_t action_id = code[pc];
220
221 IF_VERBOSE_ACTION (
222 log_action(_("PC:%d - EX: %s"), pc, code.disasm(pc));
223 );
224
225 // Set default next_pc offset, control flow action handlers
226 // will be able to reset it.
227 if ((action_id & 0x80) == 0) {
228 // action with no extra data
229 next_pc = pc+1;
230 }
231 else {
232 // action with extra data
233 // Note this converts from int to uint!
234 boost::uint16_t length(code.read_int16(pc + 1));
235
236 next_pc = pc + length + 3;
237 if (next_pc > stop_pc) {
238 IF_VERBOSE_MALFORMED_SWF(
239 log_swferror(_("Length %u (%d) of action tag"
240 " id %u at pc %d"
241 " overflows actions buffer size %d"),
242 length, static_cast<int>(length),
243 static_cast<unsigned>(action_id), pc,
244 stop_pc);
245 );
246
247 // no way to recover from this actually...
248 // Give this action handler a chance anyway.
249 // Maybe it will be able to do something about
250 // this anyway.
251 break;
252 }
253 }
254
255 // Do we still need this ?
256 if (action_id == SWF::ACTION_END) {
257 break;
258 }
259
260 ash.execute(static_cast<SWF::ActionType>(action_id), *this);
261
262 // Code round here has to do with bugs: #20974, #21069, #20996,
263 // but since there is so much disabled code it's not clear exactly
264 // what part.
265
266 // curveball.swf and feed.swf suggest that it is the
267 // *current* target, not the *original* one that matters.
268 DisplayObject* guardedChar = env.target();
269
270 if (_abortOnUnload && guardedChar && guardedChar->unloaded()) {
271
272 IF_VERBOSE_ACTION(
273 // action_execution_order_test8.c shows that the opcode
274 // guard is not SWF version based
275 std::stringstream ss;
276 ss << "Target of action_buffer (" <<
277 guardedChar->getTarget() << " of type " <<
278 typeName(*guardedChar) << ") unloaded "
279 "by execution of opcode: " << std::endl;
280
281 dumpActions(pc, next_pc, ss);
282 ss << "Discarding " << stop_pc-next_pc
283 << " bytes of remaining opcodes: " << std::endl;
284 dumpActions(next_pc, stop_pc, ss);
285 log_action(_("%s"), ss.str());
286 );
287 break;
288 }
289
290 #if DEBUG_STACK
291 IF_VERBOSE_ACTION (
292 log_action(_("After execution: PC %d, next PC %d, "
293 "stack follows"), pc, next_pc);
294 std::stringstream ss;
295 getVM(env).dumpState(ss, STACK_DUMP_LIMIT);
296 log_action(_("%s"), ss.str());
297 );
298 #endif
299
300 // Do some housecleaning on branch back
301 if (next_pc <= pc) {
302 // Check for script limits hit.
303 // See: http://www.gnashdev.org/wiki/index.php/ScriptLimits
304 if (clock.elapsed() > maxTime) {
305 boost::format fmt =
306 boost::format(_("Time exceeded (%4% secs) while "
307 "executing code in %1% between pc %2% and %3%. "
308 "Disable scripts?")) %
309 code.getMovieDefinition().get_url() % next_pc %
310 pc % (maxTime/1000);
311
312 if (getRoot(env).queryInterface(fmt.str())) {
313 throw ActionLimitException(fmt.str());
314 }
315
316 clock.restart();
317 }
318 // TODO: Run garbage collector ? If stack isn't too big ?
319 }
320
321 // Control flow actions will change the PC (next_pc)
322 pc = next_pc;
323 }
324 }
325 catch (const ActionLimitException&) {
326 // Class execution should stop (for this frame only?)
327 // Here's were we should pop-up a window to prompt user about
328 // what to do next (abort or not ?)
329 cleanupAfterRun(); // we expect inconsistencies here
330 throw;
331 }
332
333 cleanupAfterRun();
334
335 }
336
337
338 // Try / catch / finally rules:
339 //
340 // The actionscript code looks like this:
341 // try { }
342 // catch { }
343 // finally { };
344 //
345 // For functions:
346 //
347 // 1. The catch block is only executed when an exception is thrown.
348 // 2. The finally block is *always* executed, even when there is no exception
349 // *and* a return in try!
350 // 3. Unhandled executions are handled the same.
351 // 4. If an exception is thrown in a function and not caught within that function,
352 // the return value is the exception, unless the 'finally' block has its own
353 // return. (In that case, the exception is never handled).
354
355 bool
356 ActionExec::processExceptions(TryBlock& t)
357 {
358
359 switch (t._tryState) {
360 case TryBlock::TRY_TRY:
361 {
362 if (env.stack_size() && env.top(0).is_exception()) {
363 #ifdef GNASH_DEBUG_TRY
364 as_value ex = env.top(0);
365 ex.unflag_exception();
366 log_debug("TRY block: Encountered exception (%s). "
367 "Set PC to catch.", ex);
368 #endif
369 // We have an exception. Don't execute any more of the try
370 // block and process exception.
371 pc = t._catchOffset;
372 t._tryState = TryBlock::TRY_CATCH;
373
374 if (!t._hasName) {
375 // Used when exceptions are thrown in functions.
376 // Tests in misc-mtasc.all/exception.as
377 as_value ex = env.pop();
378 ex.unflag_exception();
379
380 getVM(env).setRegister(t._registerIndex, ex);
381 }
382 }
383 else {
384 #ifdef GNASH_DEBUG_TRY
385 log_debug("TRY block: No exception, continuing as normal.");
386 #endif
387
388 // No exception, so the try block should be executed,
389 // then finally (even if a function return is in try). There
390 // should be an action jump to finally at the end of try; if
391 // this is missing, catch must be executed as well.
392
393 // If there is a return in the try block, go straight to
394 // finally.
395 if (_returning) pc = t._finallyOffset;
396 else stop_pc = t._finallyOffset;
397
398 t._tryState = TryBlock::TRY_FINALLY;
399 }
400 break;
401 }
402
403
404 case TryBlock::TRY_CATCH:
405 {
406 #ifdef GNASH_DEBUG_TRY
407 log_debug("CATCH: TryBlock name = %s", t._name);
408 #endif
409 // If we are here, there should have been an exception
410 // in 'try'.
411
412 if (env.stack_size() && env.top(0).is_exception()) {
413 // This was thrown in "try". Remove it from
414 // the stack and remember it so that
415 // further exceptions can be caught.
416 t._lastThrow = env.pop();
417 as_value ex = t._lastThrow;
418 ex.unflag_exception();
419
420 #ifdef GNASH_DEBUG_TRY
421 log_debug("CATCH block: top of stack is an exception (%s)", ex);
422 #endif
423
424 if (t._hasName && !t._name.empty()) {
425 // If name isn't empty, it means we have a catch block.
426 // We should set its argument to the exception value.
427 setLocalVariable(t._name, ex);
428 t._lastThrow = as_value();
429 #ifdef GNASH_DEBUG_TRY
430 log_debug("CATCH block: encountered exception (%s). "
431 "Assigning to catch arg %d.", ex, t._name);
432 #endif
433 }
434 }
435
436 // Go to finally
437 stop_pc = t._finallyOffset;
438 t._tryState = TryBlock::TRY_FINALLY;
439 break;
440 }
441
442
443 case TryBlock::TRY_FINALLY:
444 {
445 // FINALLY. This may or may not exist, but these actions
446 // are carried out anyway.
447 #ifdef GNASH_DEBUG_TRY
448 log_debug("FINALLY: TryBlock name = %s", t._name);
449 #endif
450 // If the exception is here, we have thrown in catch.
451 if (env.stack_size() && env.top(0).is_exception()) {
452
453 t._lastThrow = env.pop();
454 #ifdef GNASH_DEBUG_TRY
455 as_value ex = t._lastThrow;
456 ex.unflag_exception();
457 log_debug("FINALLY: top of stack is an exception "
458 "again (%s). Replaces any previous "
459 "uncaught exceptions", ex);
460 #endif
461
462 // Return any exceptions thrown in catch. This
463 // can be overridden by a return in finally.
464 // Should these be thrown to the register too
465 // if it is a catch-in-register case?
466 if (retval) *retval = t._lastThrow;
467
468 }
469 stop_pc = t._afterTriedOffset;
470 t._tryState = TryBlock::TRY_END;
471 break;
472 }
473
474 case TryBlock::TRY_END:
475 {
476 // If there's no exception here, we can execute the
477 // rest of the code. If there is, it will be caught
478 // by the next TryBlock or stop execution.
479 if (env.stack_size() && env.top(0).is_exception()) {
480 // Check for exception handlers straight away
481 stop_pc = t._afterTriedOffset;
482 #ifdef GNASH_DEBUG_TRY
483 as_value ex = env.top(0);
484 ex.unflag_exception();
485 log_debug("END: exception thrown in finally(%s). "
486 "Leaving on the stack", ex);
487 #endif
488 _tryList.pop_back();
489 return true;
490 }
491 else if (t._lastThrow.is_exception()) {
492 // Check for exception handlers straight away
493 stop_pc = t._afterTriedOffset;
494 #ifdef GNASH_DEBUG_TRY
495 as_value ex = t._lastThrow;
496 ex.unflag_exception();
497 log_debug("END: no new exceptions thrown. Pushing "
498 "uncaught one (%s) back on stack", ex);
499 #endif
500
501 env.push(t._lastThrow);
502
503
504 _tryList.pop_back();
505 return true;
506 }
507 #ifdef GNASH_DEBUG_TRY
508 log_debug("END: no new exceptions thrown. Continuing");
509 #endif
510 // No uncaught exceptions left in TryBlock:
511 // execute rest of code.
512 stop_pc = t._savedEndOffset;
513
514 // Finished with this TryBlock.
515 _tryList.pop_back();
516
517 // Will break out of action execution.
518 if (_returning) return false;
519
520 break;
521 }
522
523
524 } // end switch
525 return true;
526 }
527
528 void
529 ActionExec::cleanupAfterRun()
530 {
531 VM& vm = getVM(env);
532
533 env.set_target(_originalTarget);
534 _originalTarget = NULL;
535
536 vm.setSWFVersion(_origExecSWFVersion);
537
538 IF_VERBOSE_MALFORMED_SWF(
539 // check if the stack was smashed
540 if (_initialStackSize > env.stack_size()) {
541 log_swferror(_("Stack smashed (ActionScript compiler bug, or "
542 "obfuscated SWF). Taking no action to fix (as expected)."));
543 }
544 else if (_initialStackSize < env.stack_size()) {
545 log_swferror(_("%d elements left on the stack after block "
546 "execution."), env.stack_size() - _initialStackSize);
547 }
548 );
549
550 // Have movie_root flush any newly pushed actions in higher priority queues
551 getRoot(env).flushHigherPriorityActionQueues();
552
553 }
554
555 void
556 ActionExec::skip_actions(size_t offset)
557 {
558
559 for (size_t i = 0; i < offset; ++i) {
560 // we need to check at every iteration because
561 // an action can be longer then a single byte
562 if (next_pc >= stop_pc) {
563 IF_VERBOSE_MALFORMED_SWF(
564 log_swferror(_("End of DoAction block hit while skipping "
565 "%d action tags (pc:%d, stop_pc:%d) "
566 "(WaitForFrame, probably)"), offset, next_pc,
567 stop_pc);
568 )
569 next_pc = stop_pc;
570 return;
571 }
572
573 // Get the opcode.
574 const boost::uint8_t action_id = code[next_pc];
575
576 // Set default next_pc offset, control flow action handlers
577 // will be able to reset it.
578 if ((action_id & 0x80) == 0) {
579 // action with no extra data
580 ++next_pc;
581 }
582 else {
583 // action with extra data
584 const boost::int16_t length = code.read_int16(next_pc + 1);
585 assert(length >= 0);
586 next_pc += length + 3;
587 }
588 }
589 }
590
591 bool
592 ActionExec::pushWith(const With& entry)
593 {
594 // The maximum number of withs supported is 13, regardless of the
595 // other documented figures. See actionscript.all/with.as.
596 const size_t withLimit = 13;
597
598 if (_withStack.size() == withLimit) {
599 IF_VERBOSE_ASCODING_ERRORS(
600 log_aserror("With stack limit of %s exceeded");
601 );
602 return false;
603 }
604
605 _withStack.push_back(entry);
606 _scopeStack.push_back(entry.object());
607 return true;
608 }
609
610 bool
611 ActionExec::delVariable(const std::string& name)
612 {
613 return gnash::delVariable(env, name, getScopeStack());
614 }
615
616 void
617 ActionExec::setVariable(const std::string& name, const as_value& val)
618 {
619 gnash::setVariable(env, name, val, getScopeStack());
620 }
621
622 as_value
623 ActionExec::getVariable(const std::string& name, as_object** target)
624 {
625 return gnash::getVariable(env, name, getScopeStack(), target);
626 }
627
628 void
629 ActionExec::setLocalVariable(const std::string& name, const as_value& val)
630 {
631 if (isFunction()) {
632 // TODO: set local in the function object?
633 setLocal(getVM(env).currentCall(), getURI(getVM(env), name), val);
634 } else {
635 // TODO: set target member ?
636 // what about 'with' stack ?
637 gnash::setVariable(env, name, val, getScopeStack());
638 }
639 }
640
641 as_object*
642 ActionExec::getTarget()
643 {
644 if (_withStack.empty()) {
645 return getObject(env.target());
646 }
647 return _withStack.back().object();
648 }
649
650 void
651 ActionExec::pushTryBlock(TryBlock t)
652 {
653 // The current block should end at the end of the try block.
654 t._savedEndOffset = stop_pc;
655 stop_pc = t._catchOffset;
656
657 _tryList.push_back(t);
658 }
659
660 void
661 ActionExec::pushReturn(const as_value& t)
662 {
663 if (retval) {
664 *retval = t;
665 }
666 _returning = true;
667 }
668
669 void
670 ActionExec::adjustNextPC(int offset)
671 {
672 const int tagPos = offset + static_cast<int>(pc);
673 if (tagPos < 0) {
674 log_unimpl(_("Jump outside DoAction tag requested (offset %d "
675 "before tag start)"), -tagPos);
676 return;
677 }
678 next_pc += offset;
679 }
680
681 void
682 ActionExec::dumpActions(size_t from, size_t to, std::ostream& os)
683 {
684 size_t lpc = from;
685 while (lpc < to) {
686 // Get the opcode.
687 const boost::uint8_t action_id = code[lpc];
688
689 os << " PC:" << lpc << " - EX: " << code.disasm(lpc) << std::endl;
690
691 // Set default next_pc offset, control flow action handlers
692 // will be able to reset it.
693 if ((action_id & 0x80) == 0) {
694 // action with no extra data
695 ++lpc;
696 }
697 else {
698 // action with extra data
699 const boost::int16_t length = code.read_int16(lpc + 1);
700 assert(length >= 0);
701 lpc += length + 3;
702 }
703
704 }
705 }
706
707 as_object*
708 ActionExec::getThisPointer()
709 {
710 return _func ? _this_ptr : getObject(env.get_original_target());
711 }
712
713 } // end of namespace gnash
714
715
716 // Local Variables:
717 // mode: C++
718 // indent-tabs-mode: t
719 // End: