2 * Copyright (C) 2007 Benjamin Otte <otte@gnome.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301 USA
27 #include "swfdec_as_context.h"
28 #include "swfdec_as_array.h"
29 #include "swfdec_as_frame_internal.h"
30 #include "swfdec_as_function.h"
31 #include "swfdec_as_initialize.h"
32 #include "swfdec_as_internal.h"
33 #include "swfdec_as_interpret.h"
34 #include "swfdec_as_native_function.h"
35 #include "swfdec_as_object.h"
36 #include "swfdec_as_stack.h"
37 #include "swfdec_as_strings.h"
38 #include "swfdec_as_types.h"
39 #include "swfdec_debug.h"
40 #include "swfdec_internal.h" /* for swfdec_player_preinit_global() */
41 #include "swfdec_script.h"
43 /*** GARBAGE COLLECTION DOCS ***/
45 #define SWFDEC_AS_GC_MARK (1 << 0) /* only valid during GC */
46 #define SWFDEC_AS_GC_ROOT (1 << 1) /* for objects: rooted, for strings: static */
50 * @title: Internals and garbage collection
51 * @short_description: understanding internals such as garbage collection
52 * @see_also: #SwfdecAsContext
54 * This section deals with the internals of the Swfdec Actionscript engine. You
55 * should probably read this first when trying to write code with it. If you're
56 * just trying to use Swfdec for creating Flash content, you can probably skip
59 * First, I'd like to note that the Swfdec script engine has to be modeled very
60 * closely after the existing Flash players. So if there are some behaviours
61 * that seem stupid at first sight, it might very well be that it was chosen for
62 * a very particular reason. Now on to the features.
64 * The Swfdec script engine tries to imitate Actionscript as good as possible.
65 * Actionscript is similar to Javascript, but not equal. Depending on the
66 * version of the script executed it might be more or less similar. An important
67 * example is that Flash in versions up to 6 did case-insensitive variable
70 * The script engine starts its life when it is initialized via
71 * swfdec_as_context_startup (). At that point, the basic objects are created.
72 * After this function has been called, you can start executing code. All code
73 * execution happens by creating a new #SwfdecAsFrame and then calling
74 * swfdec_as_context_run() to execute it. This function is the single entry
75 * point for code execution. Convenience functions exist that make executing
76 * code easy, most notably swfdec_as_object_run() and
77 * swfdec_as_object_call().
79 * It is also easily possible to extend the environment by adding new objects.
80 * In fact, without doing so, the environment is pretty bare as it just contains
81 * the basic Math, String, Number, Array, Date and Boolean objects. This is done
82 * by adding #SwfdecAsNative functions to the environment. The easy way
83 * to do this is via swfdec_as_object_add_function().
85 * The Swfdec script engine is dynamically typed and knows about different types
86 * of values. See #SwfdecAsValue for the different values. Memory management is
87 * done using a mark and sweep garbage collector. You can initiate a garbage
88 * collection cycle by calling swfdec_as_context_gc() or
89 * swfdec_as_context_maybe_gc(). You should do this regularly to avoid excessive
90 * memory use. The #SwfdecAsContext will then collect the objects and strings it
91 * is keeping track of. If you want to use an object or string in the script
92 * engine, you'll have to add it first via swfdec_as_object_add() or
93 * swfdec_as_context_get_string() and swfdec_as_context_give_string(),
96 * Garbage-collected strings are special in Swfdec in that they are unique. This
97 * means the same string exists exactly once. Because of this you can do
98 * equality comparisons using == instead of strcmp. It also means that if you
99 * forget to add a string to the context before using it, your app will most
100 * likely not work, since the string will not compare equal to any other string.
102 * When a garbage collection cycle happens, all reachable objects and strings
103 * are marked and all unreachable ones are freed. This is done by calling the
104 * context class's mark function which will mark all reachable objects. This is
105 * usually called the "root set". For any reachable object, the object's mark
106 * function is called so that the object in turn can mark all objects it can
107 * reach itself. Marking is done via functions described below.
113 * SECTION:SwfdecAsContext
114 * @title: SwfdecAsContext
115 * @short_description: the main script engine context
116 * @see_also: SwfdecPlayer
118 * A #SwfdecAsContext provides the main execution environment for Actionscript
119 * execution. It provides the objects typically available in ECMAScript and
120 * manages script execution, garbage collection etc. #SwfdecPlayer is a
121 * subclass of the context that implements Flash specific objects on top of it.
122 * However, it is possible to use the context for completely different functions
123 * where a sandboxed scripting environment is needed. An example is the Swfdec
125 * <note>The Actionscript engine is similar, but not equal to Javascript. It
126 * is not very different, but it is different.</note>
132 * This is the main object ued to hold the state of a script engine. All members
133 * are private and should not be accessed.
135 * Subclassing this structure to get scripting support in your own appliation is
140 * SwfdecAsContextState
141 * @SWFDEC_AS_CONTEXT_NEW: the context is not yet initialized,
142 * swfdec_as_context_startup() needs to be called.
143 * @SWFDEC_AS_CONTEXT_RUNNING: the context is running normally
144 * @SWFDEC_AS_CONTEXT_INTERRUPTED: the context has been interrupted by a
146 * @SWFDEC_AS_CONTEXT_ABORTED: the context has aborted execution due to a
149 * The state of the context describes what operations are possible on the context.
150 * It will be in the state @SWFDEC_AS_CONTEXT_STATE_RUNNING almost all the time. If
151 * it is in the state @SWFDEC_AS_CONTEXT_STATE_ABORTED, it will not work anymore and
152 * every operation on it will instantly fail.
155 /*** RUNNING STATE ***/
158 * swfdec_as_context_abort:
159 * @context: a #SwfdecAsContext
160 * @reason: a string describing why execution was aborted
162 * Aborts script execution in @context. Call this functon if the script engine
163 * encountered a fatal error and cannot continue. A possible reason for this is
164 * an out-of-memory condition.
167 swfdec_as_context_abort (SwfdecAsContext
*context
, const char *reason
)
169 g_return_if_fail (context
);
171 SWFDEC_ERROR ("%s", reason
);
172 context
->state
= SWFDEC_AS_CONTEXT_ABORTED
;
175 /*** MEMORY MANAGEMENT ***/
178 * swfdec_as_context_use_mem:
179 * @context: a #SwfdecAsContext
180 * @bytes: number of bytes to use
182 * Registers @bytes additional bytes as in use by the @context. This function
183 * keeps track of the memory that script code consumes. If too many memory is
184 * in use, this function may decide to stop the script engine with an out of
187 * Returns: %TRUE if the memory could be allocated. %FALSE on OOM.
190 swfdec_as_context_use_mem (SwfdecAsContext
*context
, gsize bytes
)
192 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), FALSE
);
193 g_return_val_if_fail (bytes
> 0, FALSE
);
195 if (context
->state
== SWFDEC_AS_CONTEXT_ABORTED
)
198 context
->memory
+= bytes
;
199 context
->memory_since_gc
+= bytes
;
200 /* FIXME: Don't foget to abort on OOM */
205 * swfdec_as_context_unuse_mem:
206 * @context: a #SwfdecAsContext
207 * @bytes: number of bytes to release
209 * Releases a number of bytes previously allocated using
210 * swfdec_as_context_use_mem(). See that function for details.
213 swfdec_as_context_unuse_mem (SwfdecAsContext
*context
, gsize bytes
)
215 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
216 g_return_if_fail (bytes
> 0);
217 g_return_if_fail (context
->memory
>= bytes
);
219 context
->memory
-= bytes
;
225 swfdec_as_context_remove_strings (gpointer key
, gpointer value
, gpointer data
)
227 SwfdecAsContext
*context
= data
;
230 string
= (char *) value
;
231 /* it doesn't matter that rooted strings aren't destroyed, they're constant */
232 if (string
[0] & SWFDEC_AS_GC_ROOT
) {
233 SWFDEC_LOG ("rooted: %s", (char *) key
);
235 } else if (string
[0] & SWFDEC_AS_GC_MARK
) {
236 SWFDEC_LOG ("marked: %s", (char *) key
);
237 string
[0] &= ~SWFDEC_AS_GC_MARK
;
241 SWFDEC_LOG ("deleted: %s", (char *) key
);
242 len
= (strlen ((char *) key
) + 2);
243 swfdec_as_context_unuse_mem (context
, len
);
244 g_slice_free1 (len
, value
);
250 swfdec_as_context_remove_objects (gpointer key
, gpointer value
, gpointer debugger
)
252 SwfdecAsObject
*object
;
255 /* we only check for mark here, not root, since this works on destroy, too */
256 if (object
->flags
& SWFDEC_AS_GC_MARK
) {
257 object
->flags
&= ~SWFDEC_AS_GC_MARK
;
258 SWFDEC_LOG ("%s: %s %p", (object
->flags
& SWFDEC_AS_GC_ROOT
) ? "rooted" : "marked",
259 G_OBJECT_TYPE_NAME (object
), object
);
262 SWFDEC_LOG ("deleted: %s %p", G_OBJECT_TYPE_NAME (object
), object
);
264 SwfdecAsDebuggerClass
*klass
= SWFDEC_AS_DEBUGGER_GET_CLASS (debugger
);
266 klass
->remove (debugger
, object
->context
, object
);
268 swfdec_as_object_collect (object
);
274 swfdec_as_context_collect (SwfdecAsContext
*context
)
276 SWFDEC_INFO (">> collecting garbage");
277 /* NB: This functions is called without GC from swfdec_as_context_dispose */
278 g_hash_table_foreach_remove (context
->strings
,
279 swfdec_as_context_remove_strings
, context
);
280 g_hash_table_foreach_remove (context
->objects
,
281 swfdec_as_context_remove_objects
, context
->debugger
);
282 SWFDEC_INFO (">> done collecting garbage");
286 * swfdec_as_object_mark:
287 * @object: a #SwfdecAsObject
289 * Mark @object as being in use. Calling this function is only valid during
290 * the marking phase of garbage collection.
293 swfdec_as_object_mark (SwfdecAsObject
*object
)
295 SwfdecAsObjectClass
*klass
;
297 g_return_if_fail (SWFDEC_IS_AS_OBJECT (object
));
299 if (object
->flags
& SWFDEC_AS_GC_MARK
)
301 object
->flags
|= SWFDEC_AS_GC_MARK
;
302 klass
= SWFDEC_AS_OBJECT_GET_CLASS (object
);
303 g_assert (klass
->mark
);
304 klass
->mark (object
);
308 * swfdec_as_string_mark:
309 * @string: a garbage-collected string
311 * Mark @string as being in use. Calling this function is only valid during
312 * the marking phase of garbage collection.
315 swfdec_as_string_mark (const char *string
)
319 g_return_if_fail (string
!= NULL
);
321 str
= (char *) string
- 1;
323 *str
= SWFDEC_AS_GC_MARK
;
327 * swfdec_as_value_mark:
328 * @value: a #SwfdecAsValue
330 * Mark @value as being in use. This is just a convenience function that calls
331 * the right marking function depending on the value's type. Calling this
332 * function is only valid during the marking phase of garbage collection.
335 swfdec_as_value_mark (SwfdecAsValue
*value
)
337 g_return_if_fail (SWFDEC_IS_AS_VALUE (value
));
339 if (SWFDEC_AS_VALUE_IS_OBJECT (value
)) {
340 swfdec_as_object_mark (SWFDEC_AS_VALUE_GET_OBJECT (value
));
341 } else if (SWFDEC_AS_VALUE_IS_STRING (value
)) {
342 swfdec_as_string_mark (SWFDEC_AS_VALUE_GET_STRING (value
));
347 swfdec_as_context_mark_roots (gpointer key
, gpointer value
, gpointer data
)
349 SwfdecAsObject
*object
= key
;
351 if ((object
->flags
& (SWFDEC_AS_GC_MARK
| SWFDEC_AS_GC_ROOT
)) == SWFDEC_AS_GC_ROOT
)
352 swfdec_as_object_mark (object
);
356 swfdec_as_context_do_mark (SwfdecAsContext
*context
)
358 swfdec_as_object_mark (context
->global
);
359 swfdec_as_object_mark (context
->Function
);
360 if (context
->Function_prototype
)
361 swfdec_as_object_mark (context
->Function_prototype
);
362 swfdec_as_object_mark (context
->Object
);
363 swfdec_as_object_mark (context
->Object_prototype
);
364 g_hash_table_foreach (context
->objects
, swfdec_as_context_mark_roots
, NULL
);
368 * swfdec_as_context_gc:
369 * @context: a #SwfdecAsContext
371 * Calls the Swfdec Gargbage collector and reclaims any unused memory. You
372 * should call this function or swfdec_as_context_maybe_gc() regularly.
373 * <warning>Calling the GC during execution of code or initialization is not
377 swfdec_as_context_gc (SwfdecAsContext
*context
)
379 SwfdecAsContextClass
*klass
;
381 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
382 g_return_if_fail (context
->frame
== NULL
);
383 g_return_if_fail (context
->state
== SWFDEC_AS_CONTEXT_RUNNING
);
385 if (context
->state
== SWFDEC_AS_CONTEXT_ABORTED
)
387 SWFDEC_INFO ("invoking the garbage collector");
388 klass
= SWFDEC_AS_CONTEXT_GET_CLASS (context
);
389 g_assert (klass
->mark
);
390 klass
->mark (context
);
391 swfdec_as_context_collect (context
);
392 context
->memory_since_gc
= 0;
396 swfdec_as_context_needs_gc (SwfdecAsContext
*context
)
398 return context
->memory_since_gc
>= context
->memory_until_gc
;
402 * swfdec_as_context_maybe_gc:
403 * @context: a #SwfdecAsContext
405 * Calls the garbage collector if necessary. It's a good idea to call this
406 * function regularly instead of swfdec_as_context_gc() as it only does collect
407 * garage as needed. For example, #SwfdecPlayer calls this function after every
411 swfdec_as_context_maybe_gc (SwfdecAsContext
*context
)
413 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
414 g_return_if_fail (context
->state
== SWFDEC_AS_CONTEXT_RUNNING
);
415 g_return_if_fail (context
->frame
== NULL
);
417 if (swfdec_as_context_needs_gc (context
))
418 swfdec_as_context_gc (context
);
421 /*** SWFDEC_AS_CONTEXT ***/
434 G_DEFINE_TYPE (SwfdecAsContext
, swfdec_as_context
, G_TYPE_OBJECT
)
435 static guint signals
[LAST_SIGNAL
] = { 0, };
438 swfdec_as_context_get_property (GObject
*object
, guint param_id
, GValue
*value
,
441 SwfdecAsContext
*context
= SWFDEC_AS_CONTEXT (object
);
446 g_value_set_object (value
, context
->debugger
);
449 g_value_set_ulong (value
, (gulong
) context
->memory_until_gc
);
452 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
458 swfdec_as_context_set_property (GObject
*object
, guint param_id
, const GValue
*value
,
461 SwfdecAsContext
*context
= SWFDEC_AS_CONTEXT (object
);
466 context
->debugger
= SWFDEC_AS_DEBUGGER (g_value_dup_object (value
));
469 context
->memory_until_gc
= g_value_get_ulong (value
);
472 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
478 swfdec_as_context_dispose (GObject
*object
)
480 SwfdecAsContext
*context
= SWFDEC_AS_CONTEXT (object
);
482 while (context
->stack
)
483 swfdec_as_stack_pop_segment (context
);
484 swfdec_as_context_collect (context
);
485 if (context
->memory
!= 0) {
486 g_critical ("%zu bytes of memory left over\n", context
->memory
);
488 g_assert (g_hash_table_size (context
->objects
) == 0);
489 g_hash_table_destroy (context
->objects
);
490 g_hash_table_destroy (context
->strings
);
491 g_rand_free (context
->rand
);
492 if (context
->debugger
) {
493 g_object_unref (context
->debugger
);
494 context
->debugger
= NULL
;
497 G_OBJECT_CLASS (swfdec_as_context_parent_class
)->dispose (object
);
501 swfdec_as_context_class_init (SwfdecAsContextClass
*klass
)
503 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
505 object_class
->dispose
= swfdec_as_context_dispose
;
506 object_class
->get_property
= swfdec_as_context_get_property
;
507 object_class
->set_property
= swfdec_as_context_set_property
;
509 g_object_class_install_property (object_class
, PROP_DEBUGGER
,
510 g_param_spec_object ("debugger", "debugger", "debugger used in this player",
511 SWFDEC_TYPE_AS_DEBUGGER
, G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
512 g_object_class_install_property (object_class
, PROP_UNTIL_GC
,
513 g_param_spec_ulong ("memory-until-gc", "memory until gc",
514 "amount of bytes that need to be allocated before garbage collection triggers",
515 0, G_MAXULONG
, 8 * 1024 * 1024, G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
));
518 * SwfdecAsContext::trace:
519 * @context: the #SwfdecAsContext affected
520 * @text: the debugging string
522 * Emits a debugging string while running. The effect of calling any swfdec
523 * functions on the emitting @context is undefined.
525 signals
[TRACE
] = g_signal_new ("trace", G_TYPE_FROM_CLASS (klass
),
526 G_SIGNAL_RUN_LAST
, 0, NULL
, NULL
, g_cclosure_marshal_VOID__STRING
,
527 G_TYPE_NONE
, 1, G_TYPE_STRING
);
529 klass
->mark
= swfdec_as_context_do_mark
;
533 swfdec_as_context_init (SwfdecAsContext
*context
)
537 context
->strings
= g_hash_table_new (g_str_hash
, g_str_equal
);
538 context
->objects
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
540 for (s
= swfdec_as_strings
; *s
== '\2'; s
+= strlen (s
) + 1) {
541 g_hash_table_insert (context
->strings
, (char *) s
+ 1, (char *) s
);
544 context
->global
= swfdec_as_object_new_empty (context
);
545 context
->rand
= g_rand_new ();
546 g_get_current_time (&context
->start_time
);
552 swfdec_as_context_create_string (SwfdecAsContext
*context
, const char *string
, gsize len
)
556 if (!swfdec_as_context_use_mem (context
, sizeof (char) * (2 + len
)))
557 return SWFDEC_AS_STR_EMPTY
;
559 new = g_slice_alloc (2 + len
);
560 memcpy (&new[1], string
, len
);
562 new[0] = 0; /* GC flags */
563 g_hash_table_insert (context
->strings
, new + 1, new);
569 * swfdec_as_context_get_string:
570 * @context: a #SwfdecAsContext
571 * @string: a sting that is not garbage-collected
573 * Gets the garbage-collected version of @string. You need to call this function
574 * for every not garbage-collected string that you want to use in Swfdecs script
577 * Returns: the garbage-collected version of @string
580 swfdec_as_context_get_string (SwfdecAsContext
*context
, const char *string
)
585 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), NULL
);
586 g_return_val_if_fail (string
!= NULL
, NULL
);
588 if (g_hash_table_lookup_extended (context
->strings
, string
, (gpointer
) &ret
, NULL
))
591 len
= strlen (string
);
592 return swfdec_as_context_create_string (context
, string
, len
);
596 * swfdec_as_context_give_string:
597 * @context: a #SwfdecAsContext
598 * @string: string to make refcounted
600 * Takes ownership of @string and returns a refcounted version of the same
601 * string. This function is the same as swfdec_as_context_get_string(), but
602 * takes ownership of @string.
604 * Returns: A refcounted string
607 swfdec_as_context_give_string (SwfdecAsContext
*context
, char *string
)
611 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), NULL
);
612 g_return_val_if_fail (string
!= NULL
, NULL
);
614 ret
= swfdec_as_context_get_string (context
, string
);
620 * swfdec_as_context_is_constructing:
621 * @context: a #SwfdecAsConstruct
623 * Determines if the contexxt is currently constructing. This information is
624 * used by various constructors to do different things when they are
625 * constructing and when they are not. The Boolean, Number and String functions
626 * for example setup the newly constructed objects when constructing but only
627 * cast the provided argument when being called.
629 * Returns: %TRUE if the currently executing frame is a constructor
632 swfdec_as_context_is_constructing (SwfdecAsContext
*context
)
634 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), FALSE
);
636 return context
->frame
&& context
->frame
->construct
;
640 * swfdec_as_context_get_frame:
641 * @context: a #SwfdecAsContext
643 * This is a debugging function. It gets the topmost stack frame that is
644 * currently executing. If no function is executing, %NULL is returned. You can
645 * easily get a backtrace with code like this:
646 * |[for (frame = swfdec_as_context_get_frame (context); frame != NULL;
647 * frame = swfdec_as_frame_get_next (frame)) {
648 * char *s = swfdec_as_object_get_debug (SWFDEC_AS_OBJECT (frame));
649 * g_print ("%s\n", s);
653 * Returns: the currently executing frame or %NULL if none
656 swfdec_as_context_get_frame (SwfdecAsContext
*context
)
658 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), NULL
);
660 return context
->frame
;
664 * swfdec_as_context_get_time:
665 * @context: a #SwfdecAsContext
666 * @tv: a #GTimeVal to be set to the context's time
668 * This function queries the time to be used inside this context. By default,
669 * this is the same as g_get_current_time(), but it may be overwriten to allow
670 * things such as slower or faster playback.
673 swfdec_as_context_get_time (SwfdecAsContext
*context
, GTimeVal
*tv
)
675 SwfdecAsContextClass
*klass
;
677 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
678 g_return_if_fail (tv
!= NULL
);
680 klass
= SWFDEC_AS_CONTEXT_GET_CLASS (context
);
682 klass
->get_time (context
, tv
);
684 g_get_current_time (tv
);
688 * swfdec_as_context_run:
689 * @context: a #SwfdecAsContext
691 * Continues running the script engine. Executing code in this engine works
692 * in 2 steps: First, you push the frame to be executed onto the stack, then
693 * you call this function to execute it. So this function is the single entry
694 * point to script execution. This might be helpful when debugging your
696 * <note>A lot of convenience functions like swfdec_as_object_run() call this
697 * function automatically.</note>
700 swfdec_as_context_run (SwfdecAsContext
*context
)
702 SwfdecAsFrame
*frame
, *last_frame
;
703 SwfdecScript
*script
;
704 const SwfdecActionSpec
*spec
;
705 SwfdecActionExec exec
;
706 guint8
*startpc
, *pc
, *endpc
, *nextpc
;
707 #ifndef G_DISABLE_ASSERT
708 SwfdecAsValue
*check
;
713 guint original_version
;
714 void (* step
) (SwfdecAsDebugger
*debugger
, SwfdecAsContext
*context
);
715 gboolean check_block
; /* some opcodes avoid a scope check */
717 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
718 if (context
->frame
== NULL
|| context
->state
== SWFDEC_AS_CONTEXT_ABORTED
)
721 if (context
->debugger
) {
722 SwfdecAsDebuggerClass
*klass
= SWFDEC_AS_DEBUGGER_GET_CLASS (context
->debugger
);
728 last_frame
= context
->last_frame
;
729 context
->last_frame
= context
->frame
->next
;
730 original_version
= context
->version
;
733 frame
= context
->frame
;
734 if (frame
== context
->last_frame
)
736 if (context
->call_depth
> 256) {
737 /* we've exceeded our maximum call depth, throw an error and abort */
738 swfdec_as_context_abort (context
, "Stack overflow");
741 if (SWFDEC_IS_AS_NATIVE_FUNCTION (frame
->function
)) {
742 SwfdecAsNativeFunction
*native
= SWFDEC_AS_NATIVE_FUNCTION (frame
->function
);
743 SwfdecAsValue rval
= { 0, };
744 if (frame
->argc
>= native
->min_args
&&
745 (native
->type
== 0 ||
746 g_type_is_a (G_OBJECT_TYPE (frame
->thisp
), native
->type
))) {
748 /* accumulate argv */
749 if (frame
->argc
== 0 || frame
->argv
!= NULL
) {
750 /* FIXME FIXME FIXME: no casting here please! */
751 argv
= (SwfdecAsValue
*) frame
->argv
;
753 SwfdecAsStack
*stack
;
756 if (frame
->argc
> 128) {
757 SWFDEC_FIXME ("allow calling native functions with more than 128 args");
762 argv
= g_new (SwfdecAsValue
, n
);
763 stack
= context
->stack
;
765 for (i
= 0; i
< n
; i
++) {
766 if (cur
<= &stack
->elements
[0]) {
768 cur
= &stack
->elements
[stack
->used_elements
];
774 native
->native (context
, frame
->thisp
, frame
->argc
,
776 if (argv
!= frame
->argv
)
779 swfdec_as_frame_return (frame
, &rval
);
782 g_assert (frame
->script
);
783 g_assert (frame
->target
);
784 script
= frame
->script
;
785 version
= SWFDEC_AS_EXTRACT_SCRIPT_VERSION (script
->version
);
786 context
->version
= script
->version
;
787 startpc
= script
->buffer
->data
;
788 endpc
= startpc
+ script
->buffer
->length
;
792 while (context
->state
< SWFDEC_AS_CONTEXT_ABORTED
) {
793 if (check_block
&& (pc
< frame
->block_start
|| pc
>= frame
->block_end
)) {
794 SWFDEC_LOG ("code exited block");
795 swfdec_as_frame_check_block (frame
);
797 if (frame
!= context
->frame
)
800 if (pc
< startpc
|| pc
>= endpc
) {
802 swfdec_as_frame_return (frame
, NULL
);
805 SWFDEC_ERROR ("pc %p not in valid range [%p, %p) anymore", pc
, startpc
, endpc
);
809 /* decode next action */
812 swfdec_as_frame_return (frame
, NULL
);
815 /* invoke debugger if there is one */
818 (* step
) (context
->debugger
, context
);
819 if (frame
!= context
->frame
||
825 spec
= swfdec_as_actions
+ action
;
827 if (pc
+ 2 >= endpc
) {
828 SWFDEC_ERROR ("action %u length value out of range", action
);
832 len
= pc
[1] | pc
[2] << 8;
833 if (data
+ len
> endpc
) {
834 SWFDEC_ERROR ("action %u length %u out of range", action
, len
);
837 nextpc
= pc
+ 3 + len
;
843 /* check action is valid */
844 exec
= spec
->exec
[version
];
847 for (real_version
= version
+ 1; !exec
&&
848 real_version
<= SWFDEC_AS_MAX_SCRIPT_VERSION
- SWFDEC_AS_MIN_SCRIPT_VERSION
;
850 exec
= spec
->exec
[real_version
];
853 SWFDEC_WARNING ("cannot interpret action %3u 0x%02X %s for version %u, skipping it", action
,
854 action
, spec
->name
? spec
->name
: "Unknown", script
->version
);
855 frame
->pc
= pc
= nextpc
;
859 SWFDEC_WARNING ("cannot interpret action %3u 0x%02X %s for version %u, using version %u instead",
860 action
, action
, spec
->name
? spec
->name
: "Unknown", script
->version
,
861 script
->version
+ real_version
- version
);
863 if (spec
->remove
> 0) {
864 if (spec
->add
> spec
->remove
)
865 swfdec_as_stack_ensure_free (context
, spec
->add
- spec
->remove
);
866 swfdec_as_stack_ensure_size (context
, spec
->remove
);
869 swfdec_as_stack_ensure_free (context
, spec
->add
);
871 if (context
->state
> SWFDEC_AS_CONTEXT_RUNNING
) {
872 SWFDEC_WARNING ("context not running anymore, aborting");
875 #ifndef G_DISABLE_ASSERT
876 check
= (spec
->add
>= 0 && spec
->remove
>= 0) ? context
->cur
+ spec
->add
- spec
->remove
: NULL
;
879 exec (context
, action
, data
, len
);
880 /* adapt the pc if the action did not, otherwise, leave it alone */
881 /* FIXME: do this via flag? */
882 if (frame
->pc
== pc
) {
883 frame
->pc
= pc
= nextpc
;
889 if (frame
== context
->frame
) {
890 #ifndef G_DISABLE_ASSERT
891 if (check
!= NULL
&& check
!= context
->cur
) {
892 g_error ("action %s was supposed to change the stack by %d (+%d -%d), but it changed by %td",
893 spec
->name
, spec
->add
- spec
->remove
, spec
->add
, spec
->remove
,
894 context
->cur
- check
+ spec
->add
- spec
->remove
);
898 /* someone called/returned from a function, reread variables */
904 while (context
->frame
!= context
->last_frame
)
905 swfdec_as_frame_return (context
->frame
, NULL
);
907 context
->last_frame
= last_frame
;
908 context
->version
= original_version
;
915 swfdec_as_slash_to_dot (const char *slash_str
)
917 const char *cur
= slash_str
;
918 GString
*str
= g_string_new ("");
921 g_string_append (str
, "_root");
925 while (cur
&& *cur
== '/') {
929 g_string_append_c (str
, '.');
930 if (cur
[0] == '.' && cur
[1] == '.') {
931 g_string_append (str
, "_parent");
934 char *slash
= strchr (cur
, '/');
936 g_string_append_len (str
, cur
, slash
- cur
);
939 g_string_append (str
, cur
);
943 /* cur should now point to the slash */
949 SWFDEC_DEBUG ("parsed slash-notated string \"%s\" into dot notation \"%s\"",
950 slash_str
, str
->str
);
951 return g_string_free (str
, FALSE
);
954 SWFDEC_WARNING ("failed to parse slash-notated string \"%s\" into dot notation", slash_str
);
955 g_string_free (str
, TRUE
);
960 swfdec_as_context_eval_get_property (SwfdecAsContext
*cx
,
961 SwfdecAsObject
*obj
, const char *name
, SwfdecAsValue
*ret
)
964 swfdec_as_object_get_variable (obj
, name
, ret
);
967 swfdec_as_frame_get_variable (cx
->frame
, name
, ret
);
969 SWFDEC_WARNING ("eval called without a frame");
970 swfdec_as_object_get_variable (cx
->global
, name
, ret
);
976 swfdec_as_context_eval_set_property (SwfdecAsContext
*cx
,
977 SwfdecAsObject
*obj
, const char *name
, const SwfdecAsValue
*ret
)
980 if (cx
->frame
== NULL
) {
981 SWFDEC_ERROR ("no frame in eval_set?");
984 swfdec_as_frame_set_variable (cx
->frame
, name
, ret
);
986 swfdec_as_object_set_variable (obj
, name
, ret
);
991 swfdec_as_context_eval_internal (SwfdecAsContext
*cx
, SwfdecAsObject
*obj
, const char *str
,
992 SwfdecAsValue
*val
, gboolean set
)
998 SWFDEC_LOG ("eval called with \"%s\" on %p", str
, obj
);
999 if (strchr (str
, '/')) {
1000 char *work
= swfdec_as_slash_to_dot (str
);
1002 SWFDEC_AS_VALUE_SET_UNDEFINED (val
);
1005 varlist
= g_strsplit (work
, ".", -1);
1008 varlist
= g_strsplit (str
, ".", -1);
1010 for (i
= 0; varlist
[i
] != NULL
; i
++) {
1011 const char *dot
= swfdec_as_context_get_string (cx
, varlist
[i
]);
1012 if (varlist
[i
+1] != NULL
) {
1013 swfdec_as_context_eval_get_property (cx
, obj
, dot
, &cur
);
1014 if (!SWFDEC_AS_VALUE_IS_OBJECT (&cur
)) {
1015 SWFDEC_AS_VALUE_SET_UNDEFINED (&cur
);
1018 obj
= SWFDEC_AS_VALUE_GET_OBJECT (&cur
);
1021 swfdec_as_context_eval_set_property (cx
, obj
, dot
, val
);
1023 swfdec_as_context_eval_get_property (cx
, obj
, dot
, &cur
);
1030 obj
= cx
->frame
->target
;
1034 g_assert (obj
!= NULL
);
1035 SWFDEC_AS_VALUE_SET_OBJECT (&cur
, obj
);
1038 g_strfreev (varlist
);
1043 * swfdec_as_context_eval:
1044 * @context: a #SwfdecAsContext
1045 * @obj: #SwfdecAsObject to use as source for evaluating or NULL for the
1046 * current frame's scope
1047 * @str: The string to evaluate
1048 * @val: location for the return value
1050 * This function works like the Actionscript eval function used on @obj.
1051 * It handles both slash-style and dot-style notation. If an error occured
1052 * during evaluation, the return value will be the undefined value.
1055 swfdec_as_context_eval (SwfdecAsContext
*context
, SwfdecAsObject
*obj
, const char *str
,
1058 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
1059 g_return_if_fail (obj
== NULL
|| SWFDEC_IS_AS_OBJECT (obj
));
1060 g_return_if_fail (str
!= NULL
);
1061 g_return_if_fail (val
!= NULL
);
1063 swfdec_as_context_eval_internal (context
, obj
, str
, val
, FALSE
);
1067 * swfdec_as_context_eval_set:
1068 * @context: a #SwfdecAsContext
1069 * @obj: #SwfdecAsObject to use as source for evaluating or NULL for the
1071 * @str: The string to evaluate
1072 * @val: the value to set the variable to
1074 * Sets the variable referenced by @str to @val. If @str does not reference
1075 * a valid property, nothing happens.
1078 swfdec_as_context_eval_set (SwfdecAsContext
*context
, SwfdecAsObject
*obj
, const char *str
,
1079 const SwfdecAsValue
*val
)
1081 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
1082 g_return_if_fail (obj
== NULL
|| SWFDEC_IS_AS_OBJECT (obj
));
1083 g_return_if_fail (str
!= NULL
);
1084 g_return_if_fail (val
!= NULL
);
1086 swfdec_as_context_eval_internal (context
, obj
, str
, (SwfdecAsValue
*) val
, TRUE
);
1092 swfdec_as_context_ASSetPropFlags_set_one_flag (SwfdecAsObject
*object
,
1093 const char *s
, guint
*flags
)
1095 swfdec_as_object_unset_variable_flags (object
, s
, flags
[1]);
1096 swfdec_as_object_set_variable_flags (object
, s
, flags
[0]);
1100 swfdec_as_context_ASSetPropFlags_foreach (SwfdecAsObject
*object
,
1101 const char *s
, SwfdecAsValue
*val
, guint cur_flags
, gpointer data
)
1103 guint
*flags
= data
;
1105 /* shortcut if the flags already match */
1106 if (cur_flags
== ((cur_flags
&~ flags
[1]) | flags
[0]))
1109 swfdec_as_context_ASSetPropFlags_set_one_flag (object
, s
, flags
);
1114 swfdec_as_context_ASSetPropFlags (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1115 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1117 guint flags
[2]; /* flags and mask - array so we can pass it as data pointer */
1118 SwfdecAsObject
*obj
;
1120 if (!SWFDEC_AS_VALUE_IS_OBJECT (&argv
[0]))
1122 obj
= SWFDEC_AS_VALUE_GET_OBJECT (&argv
[0]);
1123 flags
[0] = swfdec_as_value_to_integer (cx
, &argv
[2]);
1124 flags
[1] = (argc
> 3) ? swfdec_as_value_to_integer (cx
, &argv
[3]) : 0;
1126 if (flags
[0] == 0 && flags
[1] == 0) {
1127 // we should add autosizing length attribute here
1128 SWFDEC_FIXME ("ASSetPropFlags to set special length attribute not implemented");
1132 if (SWFDEC_AS_VALUE_IS_NULL (&argv
[1])) {
1133 swfdec_as_object_foreach (obj
, swfdec_as_context_ASSetPropFlags_foreach
, flags
);
1136 g_strsplit (swfdec_as_value_to_string (cx
, &argv
[1]), ",", -1);
1138 for (i
= 0; split
[i
]; i
++) {
1139 swfdec_as_context_ASSetPropFlags_set_one_flag (obj
,
1140 swfdec_as_context_get_string (cx
, split
[i
]), flags
);
1146 SWFDEC_AS_NATIVE (200, 19, swfdec_as_context_isFinite
)
1148 swfdec_as_context_isFinite (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1149 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1156 d
= swfdec_as_value_to_number (cx
, &argv
[0]);
1157 SWFDEC_AS_VALUE_SET_BOOLEAN (retval
, isfinite (d
) ? TRUE
: FALSE
);
1160 SWFDEC_AS_NATIVE (200, 18, swfdec_as_context_isNaN
)
1162 swfdec_as_context_isNaN (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1163 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1170 d
= swfdec_as_value_to_number (cx
, &argv
[0]);
1171 SWFDEC_AS_VALUE_SET_BOOLEAN (retval
, isnan (d
) ? TRUE
: FALSE
);
1174 SWFDEC_AS_NATIVE (100, 2, swfdec_as_context_parseInt
)
1176 swfdec_as_context_parseInt (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1177 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1187 s
= swfdec_as_value_to_string (cx
, &argv
[0]);
1190 radix
= swfdec_as_value_to_integer (cx
, &argv
[1]);
1192 if (radix
< 2 || radix
> 36) {
1193 SWFDEC_AS_VALUE_SET_NUMBER (retval
, NAN
);
1197 // special case, strtol parses things that we shouldn't parse
1199 const char *end
= s
+ strspn (s
, " \t\r\n");
1200 if (end
!= s
&& end
[0] == '0' && end
[1] == 'x') {
1201 SWFDEC_AS_VALUE_SET_NUMBER (retval
, 0);
1210 if ((s
[0] == '-' || s
[0] == '+') && s
[1] == '0' && s
[2] == 'x') {
1211 SWFDEC_AS_VALUE_SET_NUMBER (retval
, NAN
);
1215 if (s
[0] == '0' && s
[1] == 'x') {
1217 i
= strtol (s
, &tail
, (radix
!= 0 ? radix
: 16));
1219 i
= strtol (s
, &tail
, (radix
!= 0 ? radix
: 10));
1223 SWFDEC_AS_VALUE_SET_NUMBER (retval
, NAN
);
1227 SWFDEC_AS_VALUE_SET_INT (retval
, i
);
1230 SWFDEC_AS_NATIVE (100, 3, swfdec_as_context_parseFloat
)
1232 swfdec_as_context_parseFloat (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1233 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1241 // we need to remove everything after x or I, since strtod parses hexadecimal
1242 // numbers and Infinity
1243 s
= g_strdup (swfdec_as_value_to_string (cx
, &argv
[0]));
1244 if ((p
= strpbrk (s
, "xI")) != NULL
) {
1248 d
= strtod (s
, &tail
);
1251 SWFDEC_AS_VALUE_SET_NUMBER (retval
, NAN
);
1253 SWFDEC_AS_VALUE_SET_NUMBER (retval
, d
);
1259 swfdec_as_context_init_global (SwfdecAsContext
*context
, guint version
)
1263 swfdec_as_object_add_function (context
->global
, SWFDEC_AS_STR_ASSetPropFlags
, 0,
1264 swfdec_as_context_ASSetPropFlags
, 3);
1265 SWFDEC_AS_VALUE_SET_NUMBER (&val
, NAN
);
1266 swfdec_as_object_set_variable (context
->global
, SWFDEC_AS_STR_NaN
, &val
);
1267 SWFDEC_AS_VALUE_SET_NUMBER (&val
, HUGE_VAL
);
1268 swfdec_as_object_set_variable (context
->global
, SWFDEC_AS_STR_Infinity
, &val
);
1272 swfdec_as_context_run_init_script (SwfdecAsContext
*context
, const guint8
*data
,
1273 gsize length
, guint version
)
1275 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
1276 g_return_if_fail (data
!= NULL
);
1277 g_return_if_fail (length
> 0);
1281 SwfdecScript
*script
;
1282 swfdec_bits_init_data (&bits
, data
, length
);
1283 script
= swfdec_script_new_from_bits (&bits
, "init", version
);
1284 if (script
== NULL
) {
1285 g_warning ("script passed to swfdec_as_context_run_init_script is invalid");
1288 swfdec_as_object_run (context
->global
, script
);
1289 swfdec_script_unref (script
);
1291 SWFDEC_LOG ("not running init script, since version is <= 4");
1296 * swfdec_as_context_startup:
1297 * @context: a #SwfdecAsContext
1298 * @version: Flash version to use
1300 * Starts up the context. This function must be called before any Actionscript
1301 * is called on @context. The version is responsible for deciding which native
1302 * functions and properties are available in the context.
1305 swfdec_as_context_startup (SwfdecAsContext
*context
, guint version
)
1307 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
1308 g_return_if_fail (context
->state
== SWFDEC_AS_CONTEXT_NEW
);
1310 if (!swfdec_as_stack_push_segment (context
))
1312 context
->version
= version
;
1313 /* init the two internal functions */
1314 /* FIXME: remove them for normal contexts? */
1315 swfdec_player_preinit_global (context
, version
);
1316 /* get the necessary objects up to define objects and functions sanely */
1317 swfdec_as_function_init_context (context
, version
);
1318 swfdec_as_object_init_context (context
, version
);
1319 /* define the global object and other important ones */
1320 swfdec_as_context_init_global (context
, version
);
1322 /* run init script */
1323 swfdec_as_context_run_init_script (context
, swfdec_as_initialize
, sizeof (swfdec_as_initialize
), 8);
1325 if (context
->state
== SWFDEC_AS_CONTEXT_NEW
)
1326 context
->state
= SWFDEC_AS_CONTEXT_RUNNING
;