2 * Copyright (C) 2007-2008 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_gcable.h"
32 #include "swfdec_as_initialize.h"
33 #include "swfdec_as_internal.h"
34 #include "swfdec_as_interpret.h"
35 #include "swfdec_as_movie_value.h"
36 #include "swfdec_as_native_function.h"
37 #include "swfdec_as_object.h"
38 #include "swfdec_as_stack.h"
39 #include "swfdec_as_strings.h"
40 #include "swfdec_as_types.h"
41 #include "swfdec_constant_pool.h"
42 #include "swfdec_debug.h"
43 #include "swfdec_gc_object.h"
44 #include "swfdec_internal.h" /* for swfdec_player_preinit_global() */
45 #include "swfdec_script.h"
47 /*** GARBAGE COLLECTION DOCS ***/
51 * @title: Internals of the script engine
52 * @short_description: understanding internals such as garbage collection
53 * @see_also: #SwfdecAsContext, #SwfdecGcObject
55 * This section deals with the internals of the Swfdec Actionscript engine. You
56 * should probably read this first when trying to write code with it. If you're
57 * just trying to use Swfdec for creating Flash content, you can probably skip
60 * First, I'd like to note that the Swfdec script engine has to be modeled very
61 * closely after the existing Flash players. So if there are some behaviours
62 * that seem stupid at first sight, it might very well be that it was chosen for
63 * a very particular reason. Now on to the features.
65 * The Swfdec script engine tries to imitate Actionscript as good as possible.
66 * Actionscript is similar to Javascript, but not equal. Depending on the
67 * version of the script executed it might be more or less similar. An important
68 * example is that Flash in versions up to 6 did case-insensitive variable
71 * The script engine starts its life when it is initialized via
72 * swfdec_as_context_startup(). At that point, the basic objects are created.
73 * After this function has been called, you can start executing code. Code
74 * execution happens by calling swfdec_as_function_call_full() or convenience
75 * wrappers like swfdec_as_object_run() or swfdec_as_object_call().
77 * It is also easily possible to extend the environment by adding new objects.
78 * In fact, without doing so, the environment is pretty bare as it just contains
79 * the basic Math, String, Number, Array, Date and Boolean objects. This is done
80 * by adding #SwfdecAsNative functions to the environment. The easy way
81 * to do this is via swfdec_as_object_add_function().
83 * The Swfdec script engine is dynamically typed and knows about different types
84 * of values. See #SwfdecAsValue for the different values. Memory management is
85 * done using a mark and sweep garbage collector. You can initiate a garbage
86 * collection cycle by calling swfdec_as_context_gc() or
87 * swfdec_as_context_maybe_gc(). You should do this regularly to avoid excessive
88 * memory use. The #SwfdecAsContext will then collect the objects and strings it
89 * is keeping track of. If you want to use an object or string in the script
90 * engine, you'll have to add it first via swfdec_as_object_add() or
91 * swfdec_as_context_get_string() and swfdec_as_context_give_string(),
94 * Garbage-collected strings are special in Swfdec in that they are unique. This
95 * means the same string exists exactly once. Because of this you can do
96 * equality comparisons using == instead of strcmp. It also means that if you
97 * forget to add a string to the context before using it, your app will most
98 * likely not work, since the string will not compare equal to any other string.
100 * When a garbage collection cycle happens, all reachable objects and strings
101 * are marked and all unreachable ones are freed. This is done by calling the
102 * context class's mark function which will mark all reachable objects. This is
103 * usually called the "root set". For any reachable object, the object's mark
104 * function is called so that the object in turn can mark all objects it can
105 * reach itself. Marking is done via functions described below.
111 * SECTION:SwfdecAsContext
112 * @title: SwfdecAsContext
113 * @short_description: the main script engine context
114 * @see_also: SwfdecPlayer
116 * A #SwfdecAsContext provides the main execution environment for Actionscript
117 * execution. It provides the objects typically available in ECMAScript and
118 * manages script execution, garbage collection etc. #SwfdecPlayer is a
119 * subclass of the context that implements Flash specific objects on top of it.
120 * However, it is possible to use the context for completely different functions
121 * where a sandboxed scripting environment is needed. An example is the Swfdec
123 * <note>The Actionscript engine is similar, but not equal to Javascript. It
124 * is not very different, but it is different.</note>
130 * This is the main object ued to hold the state of a script engine. All members
131 * are private and should not be accessed.
133 * Subclassing this structure to get scripting support in your own appliation is
138 * SwfdecAsContextState
139 * @SWFDEC_AS_CONTEXT_NEW: the context is not yet initialized,
140 * swfdec_as_context_startup() needs to be called.
141 * @SWFDEC_AS_CONTEXT_RUNNING: the context is running normally
142 * @SWFDEC_AS_CONTEXT_INTERRUPTED: the context has been interrupted by a
144 * @SWFDEC_AS_CONTEXT_ABORTED: the context has aborted execution due to a
147 * The state of the context describes what operations are possible on the context.
148 * It will be in the state @SWFDEC_AS_CONTEXT_STATE_RUNNING almost all the time. If
149 * it is in the state @SWFDEC_AS_CONTEXT_STATE_ABORTED, it will not work anymore and
150 * every operation on it will instantly fail.
153 /*** RUNNING STATE ***/
156 * swfdec_as_context_abort:
157 * @context: a #SwfdecAsContext
158 * @reason: a string describing why execution was aborted
160 * Aborts script execution in @context. Call this functon if the script engine
161 * encountered a fatal error and cannot continue. A possible reason for this is
162 * an out-of-memory condition.
165 swfdec_as_context_abort (SwfdecAsContext
*context
, const char *reason
)
167 g_return_if_fail (context
);
169 if (context
->state
!= SWFDEC_AS_CONTEXT_ABORTED
) {
170 SWFDEC_ERROR ("abort: %s", reason
);
171 context
->state
= SWFDEC_AS_CONTEXT_ABORTED
;
172 g_object_notify (G_OBJECT (context
), "aborted");
176 /*** MEMORY MANAGEMENT ***/
179 * swfdec_as_context_try_use_mem:
180 * @context: a #SwfdecAsContext
181 * @bytes: number of bytes to use
183 * Tries to register @bytes additional bytes as in use by the @context. This
184 * function keeps track of the memory that script code consumes. The scripting
185 * engine won't be stopped, even if there wasn't enough memory left.
187 * Returns: %TRUE if the memory could be allocated. %FALSE on OOM.
190 swfdec_as_context_try_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 SWFDEC_LOG ("+%4"G_GSIZE_FORMAT
" bytes, total %7"G_GSIZE_FORMAT
" (%7"G_GSIZE_FORMAT
" since GC)",
201 bytes
, context
->memory
, context
->memory_since_gc
);
207 * swfdec_as_context_use_mem:
208 * @context: a #SwfdecAsContext
209 * @bytes: number of bytes to use
211 * Registers @bytes additional bytes as in use by the @context. This function
212 * keeps track of the memory that script code consumes. If too much memory is
213 * in use, this function may decide to stop the script engine with an out of
217 swfdec_as_context_use_mem (SwfdecAsContext
*context
, gsize bytes
)
219 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
220 g_return_if_fail (bytes
> 0);
222 /* FIXME: Don't forget to abort on OOM */
223 if (!swfdec_as_context_try_use_mem (context
, bytes
)) {
224 swfdec_as_context_abort (context
, "Out of memory");
225 /* add the memory anyway, as we're gonna make use of it. */
226 context
->memory
+= bytes
;
227 context
->memory_since_gc
+= bytes
;
232 * swfdec_as_context_unuse_mem:
233 * @context: a #SwfdecAsContext
234 * @bytes: number of bytes to release
236 * Releases a number of bytes previously allocated using
237 * swfdec_as_context_use_mem(). See that function for details.
240 swfdec_as_context_unuse_mem (SwfdecAsContext
*context
, gsize bytes
)
242 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
243 g_return_if_fail (bytes
> 0);
244 g_return_if_fail (context
->memory
>= bytes
);
246 context
->memory
-= bytes
;
247 SWFDEC_LOG ("-%4"G_GSIZE_FORMAT
" bytes, total %7"G_GSIZE_FORMAT
" (%7"G_GSIZE_FORMAT
" since GC)",
248 bytes
, context
->memory
, context
->memory_since_gc
);
254 swfdec_as_context_remove_gc_objects (SwfdecAsContext
*context
)
256 SwfdecGcObject
*gc
, *prev
, *next
;
259 gc
= context
->gc_objects
;
262 /* we only check for mark here, not root, since this works on destroy, too */
263 if (gc
->flags
& SWFDEC_AS_GC_MARK
) {
264 gc
->flags
&= ~SWFDEC_AS_GC_MARK
;
265 SWFDEC_LOG ("%s: %s %p", (gc
->flags
& SWFDEC_AS_GC_ROOT
) ? "rooted" : "marked",
266 G_OBJECT_TYPE_NAME (gc
), gc
);
269 SWFDEC_LOG ("deleted: %s %p", G_OBJECT_TYPE_NAME (gc
), gc
);
274 context
->gc_objects
= next
;
282 swfdec_as_context_collect_string (SwfdecAsContext
*context
, gpointer gc
)
284 SwfdecAsStringValue
*string
;
287 if (!g_hash_table_remove (context
->interned_strings
, string
->string
)) {
288 g_assert_not_reached ();
290 swfdec_as_gcable_free (context
, gc
, sizeof (SwfdecAsStringValue
) + string
->length
+ 1);
294 swfdec_as_context_collect_double (SwfdecAsContext
*context
, gpointer gc
)
296 swfdec_as_gcable_free (context
, gc
, sizeof (SwfdecAsDoubleValue
));
300 swfdec_as_context_collect_movie (SwfdecAsContext
*context
, gpointer gc
)
302 swfdec_as_movie_value_free ((SwfdecAsMovieValue
*) gc
);
306 swfdec_as_context_collect (SwfdecAsContext
*context
)
308 /* NB: This functions is called without GC from swfdec_as_context_dispose */
309 SWFDEC_INFO (">> collecting garbage");
311 swfdec_as_context_remove_gc_objects (context
);
313 context
->objects
= swfdec_as_gcable_collect (context
, context
->objects
,
314 (SwfdecAsGcableDestroyNotify
) swfdec_as_object_free
);
315 context
->strings
= swfdec_as_gcable_collect (context
, context
->strings
,
316 swfdec_as_context_collect_string
);
317 context
->numbers
= swfdec_as_gcable_collect (context
, context
->numbers
,
318 swfdec_as_context_collect_double
);
319 context
->movies
= swfdec_as_gcable_collect (context
, context
->movies
,
320 swfdec_as_context_collect_movie
);
322 SWFDEC_INFO (">> done collecting garbage");
326 * swfdec_as_string_mark:
327 * @string: a garbage-collected string
329 * Mark @string as being in use. Calling this function is only valid during
330 * the marking phase of garbage collection.
333 swfdec_as_string_mark (const char *string
)
335 SwfdecAsStringValue
*value
;
337 g_return_if_fail (string
!= NULL
);
339 value
= (SwfdecAsStringValue
*) (gpointer
) ((guint8
*) string
- G_STRUCT_OFFSET (SwfdecAsStringValue
, string
));
340 if (!SWFDEC_AS_GCABLE_FLAG_IS_SET (value
, SWFDEC_AS_GC_ROOT
))
341 SWFDEC_AS_GCABLE_SET_FLAG (value
, SWFDEC_AS_GC_MARK
);
345 * swfdec_as_value_mark:
346 * @value: a #SwfdecAsValue
348 * Mark @value as being in use. This is just a convenience function that calls
349 * the right marking function depending on the value's type. Calling this
350 * function is only valid during the marking phase of garbage collection.
353 swfdec_as_value_mark (SwfdecAsValue
*value
)
355 SwfdecAsGcable
*gcable
= SWFDEC_AS_VALUE_GET_VALUE (value
);
356 if (SWFDEC_AS_VALUE_IS_OBJECT (value
)) {
357 if (!SWFDEC_AS_GCABLE_FLAG_IS_SET (gcable
, SWFDEC_AS_GC_MARK
))
358 swfdec_as_object_mark ((SwfdecAsObject
*) gcable
);
359 } else if (SWFDEC_AS_VALUE_IS_MOVIE (value
)) {
360 if (!SWFDEC_AS_GCABLE_FLAG_IS_SET (gcable
, SWFDEC_AS_GC_MARK
))
361 swfdec_as_movie_value_mark ((SwfdecAsMovieValue
*) gcable
);
362 } else if (SWFDEC_AS_VALUE_IS_STRING (value
) ||
363 SWFDEC_AS_VALUE_IS_NUMBER (value
)) {
364 if (!SWFDEC_AS_GCABLE_FLAG_IS_SET (gcable
, SWFDEC_AS_GC_ROOT
| SWFDEC_AS_GC_MARK
))
365 SWFDEC_AS_GCABLE_SET_FLAG (gcable
, SWFDEC_AS_GC_MARK
);
369 /* FIXME: replace this with refcounted strings? */
371 swfdec_as_context_mark_constant_pools (gpointer key
, gpointer value
, gpointer unused
)
373 SwfdecConstantPool
*pool
= value
;
376 for (i
= 0; i
< swfdec_constant_pool_size (pool
); i
++) {
377 const char *s
= swfdec_constant_pool_get (pool
, i
);
379 swfdec_as_string_mark (s
);
384 swfdec_as_context_do_mark (SwfdecAsContext
*context
)
386 /* This if is needed for SwfdecPlayer */
388 swfdec_as_object_mark (context
->global
);
389 if (context
->exception
)
390 swfdec_as_value_mark (&context
->exception_value
);
391 g_hash_table_foreach (context
->constant_pools
, swfdec_as_context_mark_constant_pools
, NULL
);
395 * swfdec_as_context_gc:
396 * @context: a #SwfdecAsContext
398 * Calls the Swfdec Gargbage collector and reclaims any unused memory. You
399 * should call this function or swfdec_as_context_maybe_gc() regularly.
400 * <warning>Calling the GC during execution of code or initialization is not
404 swfdec_as_context_gc (SwfdecAsContext
*context
)
406 SwfdecAsContextClass
*klass
;
408 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
409 g_return_if_fail (context
->frame
== NULL
);
410 g_return_if_fail (context
->state
== SWFDEC_AS_CONTEXT_RUNNING
);
412 if (context
->state
== SWFDEC_AS_CONTEXT_ABORTED
)
414 SWFDEC_INFO ("invoking the garbage collector");
415 klass
= SWFDEC_AS_CONTEXT_GET_CLASS (context
);
416 g_assert (klass
->mark
);
417 klass
->mark (context
);
418 swfdec_as_context_collect (context
);
419 context
->memory_since_gc
= 0;
423 swfdec_as_context_needs_gc (SwfdecAsContext
*context
)
425 return context
->memory_since_gc
>= context
->memory_until_gc
;
429 * swfdec_as_context_maybe_gc:
430 * @context: a #SwfdecAsContext
432 * Calls the garbage collector if necessary. It's a good idea to call this
433 * function regularly instead of swfdec_as_context_gc() as it only does collect
434 * garage as needed. For example, #SwfdecPlayer calls this function after every
438 swfdec_as_context_maybe_gc (SwfdecAsContext
*context
)
440 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
441 g_return_if_fail (context
->state
== SWFDEC_AS_CONTEXT_RUNNING
);
442 g_return_if_fail (context
->frame
== NULL
);
444 if (swfdec_as_context_needs_gc (context
))
445 swfdec_as_context_gc (context
);
448 /*** SWFDEC_AS_CONTEXT ***/
463 G_DEFINE_TYPE (SwfdecAsContext
, swfdec_as_context
, G_TYPE_OBJECT
)
464 static guint signals
[LAST_SIGNAL
] = { 0, };
467 swfdec_as_context_get_property (GObject
*object
, guint param_id
, GValue
*value
,
470 SwfdecAsContext
*context
= SWFDEC_AS_CONTEXT (object
);
474 g_value_set_object (value
, context
->debugger
);
477 g_value_set_boolean (value
, context
->state
== SWFDEC_AS_CONTEXT_ABORTED
);
480 g_value_set_ulong (value
, (gulong
) context
->memory_until_gc
);
483 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
489 swfdec_as_context_set_property (GObject
*object
, guint param_id
, const GValue
*value
,
492 SwfdecAsContext
*context
= SWFDEC_AS_CONTEXT (object
);
496 context
->debugger
= SWFDEC_AS_DEBUGGER (g_value_dup_object (value
));
498 case PROP_RANDOM_SEED
:
499 g_rand_set_seed (context
->rand
, g_value_get_uint (value
));
502 context
->memory_until_gc
= g_value_get_ulong (value
);
505 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
511 swfdec_as_context_dispose (GObject
*object
)
513 SwfdecAsContext
*context
= SWFDEC_AS_CONTEXT (object
);
515 while (context
->stack
)
516 swfdec_as_stack_pop_segment (context
);
517 /* We need to make sure there's no exception here. Otherwise collecting
518 * frames that are inside a try block will assert */
519 swfdec_as_context_catch (context
, NULL
);
520 swfdec_as_context_collect (context
);
521 if (context
->memory
!= 0) {
522 g_critical ("%zu bytes of memory left over\n", context
->memory
);
524 g_assert (context
->strings
== NULL
);
525 g_assert (context
->numbers
== NULL
);
526 g_assert (g_hash_table_size (context
->constant_pools
) == 0);
527 g_assert (context
->gc_objects
== 0);
528 g_hash_table_destroy (context
->constant_pools
);
529 g_hash_table_destroy (context
->interned_strings
);
530 g_rand_free (context
->rand
);
531 if (context
->debugger
) {
532 g_object_unref (context
->debugger
);
533 context
->debugger
= NULL
;
536 G_OBJECT_CLASS (swfdec_as_context_parent_class
)->dispose (object
);
540 swfdec_as_context_class_init (SwfdecAsContextClass
*klass
)
542 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
544 object_class
->dispose
= swfdec_as_context_dispose
;
545 object_class
->get_property
= swfdec_as_context_get_property
;
546 object_class
->set_property
= swfdec_as_context_set_property
;
548 g_object_class_install_property (object_class
, PROP_DEBUGGER
,
549 g_param_spec_object ("debugger", "debugger", "debugger used in this player",
550 SWFDEC_TYPE_AS_DEBUGGER
, G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
551 g_object_class_install_property (object_class
, PROP_RANDOM_SEED
,
552 g_param_spec_uint ("random-seed", "random seed",
553 "seed used for calculating random numbers",
554 0, G_MAXUINT32
, 0, G_PARAM_WRITABLE
)); /* FIXME: make this readwrite for replaying? */
555 g_object_class_install_property (object_class
, PROP_ABORTED
,
556 g_param_spec_boolean ("aborted", "aborted", "set when the script engine aborts due to an error",
557 FALSE
, G_PARAM_READABLE
));
558 g_object_class_install_property (object_class
, PROP_UNTIL_GC
,
559 g_param_spec_ulong ("memory-until-gc", "memory until gc",
560 "amount of bytes that need to be allocated before garbage collection triggers",
561 0, G_MAXULONG
, 8 * 1024 * 1024, G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
));
564 * SwfdecAsContext::trace:
565 * @context: the #SwfdecAsContext affected
566 * @text: the debugging string
568 * Emits a debugging string while running. The effect of calling any swfdec
569 * functions on the emitting @context is undefined.
571 signals
[TRACE
] = g_signal_new ("trace", G_TYPE_FROM_CLASS (klass
),
572 G_SIGNAL_RUN_LAST
, 0, NULL
, NULL
, g_cclosure_marshal_VOID__STRING
,
573 G_TYPE_NONE
, 1, G_TYPE_STRING
);
575 klass
->mark
= swfdec_as_context_do_mark
;
579 swfdec_as_context_init (SwfdecAsContext
*context
)
581 const SwfdecAsConstantStringValue
*s
;
583 context
->version
= G_MAXUINT
;
585 context
->interned_strings
= g_hash_table_new (g_str_hash
, g_str_equal
);
586 context
->constant_pools
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
588 for (s
= swfdec_as_strings
; s
->next
; s
++) {
589 g_hash_table_insert (context
->interned_strings
, (gpointer
) s
->string
, (gpointer
) s
);
591 context
->rand
= g_rand_new ();
592 g_get_current_time (&context
->start_time
);
598 swfdec_as_context_create_string (SwfdecAsContext
*context
, const char *string
, gsize len
)
600 SwfdecAsStringValue
*new;
602 new = swfdec_as_gcable_alloc (context
, sizeof (SwfdecAsStringValue
) + len
+ 1);
604 memcpy (new->string
, string
, new->length
+ 1);
605 g_hash_table_insert (context
->interned_strings
, new->string
, new);
606 SWFDEC_AS_GCABLE_SET_NEXT (new, context
->strings
);
607 context
->strings
= new;
613 * swfdec_as_context_get_string:
614 * @context: a #SwfdecAsContext
615 * @string: a sting that is not garbage-collected
617 * Gets the garbage-collected version of @string. You need to call this function
618 * for every not garbage-collected string that you want to use in Swfdecs script
621 * Returns: the garbage-collected version of @string
624 swfdec_as_context_get_string (SwfdecAsContext
*context
, const char *string
)
626 const SwfdecAsStringValue
*ret
;
629 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), NULL
);
630 g_return_val_if_fail (string
!= NULL
, NULL
);
632 ret
= g_hash_table_lookup (context
->interned_strings
, string
);
636 len
= strlen (string
);
637 return swfdec_as_context_create_string (context
, string
, len
);
641 * swfdec_as_context_give_string:
642 * @context: a #SwfdecAsContext
643 * @string: string to make refcounted
645 * Takes ownership of @string and returns a refcounted version of the same
646 * string. This function is the same as swfdec_as_context_get_string(), but
647 * takes ownership of @string.
649 * Returns: A refcounted string
652 swfdec_as_context_give_string (SwfdecAsContext
*context
, char *string
)
656 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), NULL
);
657 g_return_val_if_fail (string
!= NULL
, NULL
);
659 ret
= swfdec_as_context_get_string (context
, string
);
665 * swfdec_as_context_is_constructing:
666 * @context: a #SwfdecAsConstruct
668 * Determines if the contexxt is currently constructing. This information is
669 * used by various constructors to do different things when they are
670 * constructing and when they are not. The Boolean, Number and String functions
671 * for example setup the newly constructed objects when constructing but only
672 * cast the provided argument when being called.
674 * Returns: %TRUE if the currently executing frame is a constructor
677 swfdec_as_context_is_constructing (SwfdecAsContext
*context
)
679 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), FALSE
);
681 return context
->frame
&& context
->frame
->construct
;
685 * swfdec_as_context_get_frame:
686 * @context: a #SwfdecAsContext
688 * This is a debugging function. It gets the topmost stack frame that is
689 * currently executing. If no function is executing, %NULL is returned.
691 * Returns: the currently executing frame or %NULL if none
694 swfdec_as_context_get_frame (SwfdecAsContext
*context
)
696 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), NULL
);
698 return context
->frame
;
702 * swfdec_as_context_throw:
703 * @context: a #SwfdecAsContext
704 * @value: a #SwfdecAsValue to be thrown
706 * Throws a new exception in the @context using the given @value. This function
707 * can only be called if the @context is not already throwing an exception.
710 swfdec_as_context_throw (SwfdecAsContext
*context
, const SwfdecAsValue
*value
)
712 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
713 g_return_if_fail (!context
->exception
);
715 context
->exception
= TRUE
;
716 context
->exception_value
= *value
;
720 * swfdec_as_context_catch:
721 * @context: a #SwfdecAsContext
722 * @value: a #SwfdecAsValue to be thrown
724 * Removes the currently thrown exception from @context and sets @value to the
727 * Returns: %TRUE if an exception was catched, %FALSE otherwise
730 swfdec_as_context_catch (SwfdecAsContext
*context
, SwfdecAsValue
*value
)
732 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), FALSE
);
734 if (!context
->exception
)
738 *value
= context
->exception_value
;
740 context
->exception
= FALSE
;
741 SWFDEC_AS_VALUE_SET_UNDEFINED (&context
->exception_value
);
747 * swfdec_as_context_get_time:
748 * @context: a #SwfdecAsContext
749 * @tv: a #GTimeVal to be set to the context's time
751 * This function queries the time to be used inside this context. By default,
752 * this is the same as g_get_current_time(), but it may be overwriten to allow
753 * things such as slower or faster playback.
756 swfdec_as_context_get_time (SwfdecAsContext
*context
, GTimeVal
*tv
)
758 SwfdecAsContextClass
*klass
;
760 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
761 g_return_if_fail (tv
!= NULL
);
763 klass
= SWFDEC_AS_CONTEXT_GET_CLASS (context
);
765 klass
->get_time (context
, tv
);
767 g_get_current_time (tv
);
771 * swfdec_as_context_return:
772 * @context: the context to return the topmost frame in
773 * @return_value: return value of the function or %NULL for none. An undefined
774 * value will be used in that case.
776 * Ends execution of the currently executing frame and continues execution with
780 swfdec_as_context_return (SwfdecAsContext
*context
, SwfdecAsValue
*return_value
)
782 SwfdecAsValue retval
;
783 SwfdecAsFrame
*frame
, *next
;
785 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
786 g_return_if_fail (context
->frame
!= NULL
);
788 frame
= context
->frame
;
790 /* save return value in case it was on the stack somewhere */
791 if (frame
->construct
) {
792 retval
= frame
->thisp
;
793 } else if (return_value
) {
794 retval
= *return_value
;
796 SWFDEC_AS_VALUE_SET_UNDEFINED (&retval
);
798 /* pop frame and leftover stack */
800 context
->frame
= next
;
801 g_assert (context
->call_depth
> 0);
802 context
->call_depth
--;
803 while (context
->base
> frame
->stack_begin
||
804 context
->end
< frame
->stack_begin
)
805 swfdec_as_stack_pop_segment (context
);
806 context
->cur
= frame
->stack_begin
;
807 /* setup stack for previous frame */
809 if (next
->stack_begin
>= &context
->stack
->elements
[0] &&
810 next
->stack_begin
<= context
->cur
) {
811 context
->base
= next
->stack_begin
;
813 context
->base
= &context
->stack
->elements
[0];
816 g_assert (context
->stack
->next
== NULL
);
817 context
->base
= &context
->stack
->elements
[0];
819 /* pop argv if on stack */
820 if (frame
->argv
== NULL
&& frame
->argc
> 0) {
821 guint i
= frame
->argc
;
823 guint n
= context
->cur
- context
->base
;
825 swfdec_as_stack_pop_n (context
, n
);
829 swfdec_as_stack_pop_segment (context
);
832 if (context
->debugger
) {
833 SwfdecAsDebuggerClass
*klass
= SWFDEC_AS_DEBUGGER_GET_CLASS (context
->debugger
);
835 if (klass
->leave_frame
)
836 klass
->leave_frame (context
->debugger
, context
, frame
, &retval
);
838 /* set return value */
839 if (frame
->return_value
) {
840 *frame
->return_value
= retval
;
842 swfdec_as_stack_ensure_free (context
, 1);
843 *swfdec_as_stack_push (context
) = retval
;
845 swfdec_as_frame_free (context
, frame
);
849 * swfdec_as_context_run:
850 * @context: a #SwfdecAsContext
852 * Continues running the script engine. Executing code in this engine works
853 * in 2 steps: First, you push the frame to be executed onto the stack, then
854 * you call this function to execute it. So this function is the single entry
855 * point to script execution. This might be helpful when debugging your
857 * <note>A lot of convenience functions like swfdec_as_object_run() call this
858 * function automatically.</note>
861 swfdec_as_context_run (SwfdecAsContext
*context
)
863 SwfdecAsFrame
*frame
;
864 SwfdecScript
*script
;
865 const SwfdecActionSpec
*spec
;
866 const guint8
*startpc
, *pc
, *endpc
, *nextpc
, *exitpc
;
867 #ifndef G_DISABLE_ASSERT
868 SwfdecAsValue
*check
;
872 guint original_version
;
873 void (* step
) (SwfdecAsDebugger
*debugger
, SwfdecAsContext
*context
);
874 gboolean check_block
; /* some opcodes avoid a scope check */
876 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
877 g_return_if_fail (context
->frame
!= NULL
);
878 g_return_if_fail (context
->frame
->script
!= NULL
);
879 g_return_if_fail (context
->global
); /* check here because of swfdec_sandbox_(un)use() */
882 frame
= context
->frame
;
883 original_version
= context
->version
;
886 if (context
->state
== SWFDEC_AS_CONTEXT_ABORTED
)
888 if (!swfdec_as_context_check_continue (context
))
890 if (context
->call_depth
> 256) {
891 /* we've exceeded our maximum call depth, throw an error and abort */
892 swfdec_as_context_abort (context
, "Stack overflow");
896 if (context
->debugger
) {
897 SwfdecAsDebuggerClass
*klass
= SWFDEC_AS_DEBUGGER_GET_CLASS (context
->debugger
);
903 script
= frame
->script
;
904 context
->version
= script
->version
;
905 startpc
= script
->buffer
->data
;
906 endpc
= startpc
+ script
->buffer
->length
;
907 exitpc
= script
->exit
;
911 while (context
->state
< SWFDEC_AS_CONTEXT_ABORTED
) {
912 if (context
->exception
) {
913 swfdec_as_frame_handle_exception (context
, frame
);
914 if (frame
!= context
->frame
)
920 swfdec_as_context_return (context
, NULL
);
923 if (pc
< startpc
|| pc
>= endpc
) {
924 SWFDEC_ERROR ("pc %p not in valid range [%p, %p) anymore", pc
, startpc
, endpc
);
927 while (check_block
&& (pc
< frame
->block_start
|| pc
>= frame
->block_end
)) {
928 SWFDEC_LOG ("code exited block");
929 swfdec_as_frame_pop_block (frame
, context
);
931 if (frame
!= context
->frame
)
933 if (context
->exception
)
936 if (context
->exception
)
939 /* decode next action */
941 /* invoke debugger if there is one */
944 (* step
) (context
->debugger
, context
);
945 if (frame
!= context
->frame
)
951 spec
= swfdec_as_actions
+ action
;
953 if (pc
+ 2 >= endpc
) {
954 SWFDEC_ERROR ("action %u length value out of range", action
);
958 len
= pc
[1] | pc
[2] << 8;
959 if (data
+ len
> endpc
) {
960 SWFDEC_ERROR ("action %u length %u out of range", action
, len
);
963 nextpc
= pc
+ 3 + len
;
969 /* check action is valid */
971 SWFDEC_WARNING ("cannot interpret action %3u 0x%02X %s for version %u, skipping it", action
,
972 action
, spec
->name
? spec
->name
: "Unknown", script
->version
);
973 frame
->pc
= pc
= nextpc
;
977 if (script
->version
< spec
->version
) {
978 SWFDEC_WARNING ("cannot interpret action %3u 0x%02X %s for version %u, using version %u instead",
979 action
, action
, spec
->name
? spec
->name
: "Unknown", script
->version
, spec
->version
);
981 if (spec
->remove
> 0) {
982 if (spec
->add
> spec
->remove
)
983 swfdec_as_stack_ensure_free (context
, spec
->add
- spec
->remove
);
984 swfdec_as_stack_ensure_size (context
, spec
->remove
);
987 swfdec_as_stack_ensure_free (context
, spec
->add
);
989 if (context
->state
> SWFDEC_AS_CONTEXT_RUNNING
) {
990 SWFDEC_WARNING ("context not running anymore, aborting");
993 #ifndef G_DISABLE_ASSERT
994 check
= (spec
->add
>= 0 && spec
->remove
>= 0) ? context
->cur
+ spec
->add
- spec
->remove
: NULL
;
997 spec
->exec (context
, action
, data
, len
);
998 /* adapt the pc if the action did not, otherwise, leave it alone */
999 /* FIXME: do this via flag? */
1000 if (frame
->pc
== pc
) {
1001 frame
->pc
= pc
= nextpc
;
1004 if (frame
->pc
< pc
&&
1005 !swfdec_as_context_check_continue (context
)) {
1009 check_block
= FALSE
;
1011 if (frame
== context
->frame
) {
1012 #ifndef G_DISABLE_ASSERT
1013 if (check
!= NULL
&& check
!= context
->cur
) {
1014 g_error ("action %s was supposed to change the stack by %d (+%d -%d), but it changed by %td",
1015 spec
->name
, spec
->add
- spec
->remove
, spec
->add
, spec
->remove
,
1016 context
->cur
- check
+ spec
->add
- spec
->remove
);
1020 /* someone called/returned from a function, reread variables */
1026 if (context
->frame
== frame
)
1027 swfdec_as_context_return (context
, NULL
);
1029 context
->version
= original_version
;
1036 swfdec_as_context_ASSetPropFlags_set_one_flag (SwfdecAsObject
*object
,
1037 const char *s
, guint
*flags
)
1039 swfdec_as_object_unset_variable_flags (object
, s
, flags
[1]);
1040 swfdec_as_object_set_variable_flags (object
, s
, flags
[0]);
1044 swfdec_as_context_ASSetPropFlags_foreach (SwfdecAsObject
*object
,
1045 const char *s
, SwfdecAsValue
*val
, guint cur_flags
, gpointer data
)
1047 guint
*flags
= data
;
1049 /* shortcut if the flags already match */
1050 if (cur_flags
== ((cur_flags
&~ flags
[1]) | flags
[0]))
1053 swfdec_as_context_ASSetPropFlags_set_one_flag (object
, s
, flags
);
1057 SWFDEC_AS_NATIVE (1, 0, swfdec_as_context_ASSetPropFlags
)
1059 swfdec_as_context_ASSetPropFlags (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1060 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1062 guint flags
[2]; /* flags and mask - array so we can pass it as data pointer */
1063 SwfdecAsObject
*obj
;
1068 if (!SWFDEC_AS_VALUE_IS_COMPOSITE (&argv
[0]))
1070 obj
= SWFDEC_AS_VALUE_GET_COMPOSITE (&argv
[0]);
1071 flags
[0] = swfdec_as_value_to_integer (cx
, &argv
[2]);
1072 flags
[1] = (argc
> 3) ? swfdec_as_value_to_integer (cx
, &argv
[3]) : 0;
1074 if (flags
[0] == 0 && flags
[1] == 0) {
1075 // we should add autosizing length attribute here
1076 SWFDEC_FIXME ("ASSetPropFlags to set special length attribute not implemented");
1080 if (SWFDEC_AS_VALUE_IS_NULL (&argv
[1])) {
1081 swfdec_as_object_foreach (obj
, swfdec_as_context_ASSetPropFlags_foreach
, flags
);
1084 g_strsplit (swfdec_as_value_to_string (cx
, argv
[1]), ",", -1);
1086 for (i
= 0; split
[i
]; i
++) {
1087 swfdec_as_context_ASSetPropFlags_set_one_flag (obj
,
1088 swfdec_as_context_get_string (cx
, split
[i
]), flags
);
1094 SWFDEC_AS_NATIVE (200, 19, swfdec_as_context_isFinite
)
1096 swfdec_as_context_isFinite (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1097 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1104 d
= swfdec_as_value_to_number (cx
, &argv
[0]);
1105 SWFDEC_AS_VALUE_SET_BOOLEAN (retval
, isfinite (d
) ? TRUE
: FALSE
);
1108 SWFDEC_AS_NATIVE (200, 18, swfdec_as_context_isNaN
)
1110 swfdec_as_context_isNaN (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1111 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1118 d
= swfdec_as_value_to_number (cx
, &argv
[0]);
1119 SWFDEC_AS_VALUE_SET_BOOLEAN (retval
, isnan (d
) ? TRUE
: FALSE
);
1122 SWFDEC_AS_NATIVE (100, 2, swfdec_as_context_parseInt
)
1124 swfdec_as_context_parseInt (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1125 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1132 SWFDEC_AS_CHECK (0, NULL
, "s|i", &s
, &radix
);
1134 if (argc
>= 2 && (radix
< 2 || radix
> 36)) {
1135 swfdec_as_value_set_number (cx
, retval
, NAN
);
1139 // special case, don't allow sign in front of the 0x
1140 if ((s
[0] == '-' || s
[0] == '+') && s
[1] == '0' &&
1141 (s
[2] == 'x' || s
[2] == 'X')) {
1142 swfdec_as_value_set_number (cx
, retval
, NAN
);
1148 if (s
[0] == '0' && (s
[1] == 'x' || s
[1] == 'X')) {
1150 } else if ((s
[0] == '0' || ((s
[0] == '+' || s
[0] == '-') && s
[1] == '0')) &&
1151 s
[strspn (s
+1, "01234567") + 1] == '\0') {
1158 // skip 0x at the start
1159 if (s
[0] == '0' && (s
[1] == 'x' || s
[1] == 'X'))
1162 // strtoll parses strings with 0x when given radix 16, but we don't want that
1164 const char *skip
= s
+ strspn (s
, " \t\r\n");
1165 if (skip
!= s
&& (skip
[0] == '-' || skip
[0] == '+'))
1167 if (skip
!= s
&& skip
[0] == '0' && (skip
[1] == 'x' || skip
[1] == 'X')) {
1168 swfdec_as_value_set_number (cx
, retval
, 0);
1173 i
= g_ascii_strtoll (s
, &tail
, radix
);
1176 swfdec_as_value_set_number (cx
, retval
, NAN
);
1180 if (i
> G_MAXINT32
|| i
< G_MININT32
) {
1181 swfdec_as_value_set_number (cx
, retval
, i
);
1183 swfdec_as_value_set_integer (cx
, retval
, i
);
1187 SWFDEC_AS_NATIVE (100, 3, swfdec_as_context_parseFloat
)
1189 swfdec_as_context_parseFloat (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1190 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1198 // we need to remove everything after x or I, since strtod parses hexadecimal
1199 // numbers and Infinity
1200 s
= g_strdup (swfdec_as_value_to_string (cx
, argv
[0]));
1201 if ((p
= strpbrk (s
, "xXiI")) != NULL
) {
1205 d
= g_ascii_strtod (s
, &tail
);
1208 swfdec_as_value_set_number (cx
, retval
, NAN
);
1210 swfdec_as_value_set_number (cx
, retval
, d
);
1217 swfdec_as_context_init_global (SwfdecAsContext
*context
)
1221 swfdec_as_value_set_number (context
, &val
, NAN
);
1222 swfdec_as_object_set_variable (context
->global
, SWFDEC_AS_STR_NaN
, &val
);
1223 swfdec_as_value_set_number (context
, &val
, HUGE_VAL
);
1224 swfdec_as_object_set_variable (context
->global
, SWFDEC_AS_STR_Infinity
, &val
);
1228 swfdec_as_context_run_init_script (SwfdecAsContext
*context
, const guint8
*data
,
1229 gsize length
, guint version
)
1231 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
1232 g_return_if_fail (data
!= NULL
);
1233 g_return_if_fail (length
> 0);
1237 SwfdecScript
*script
;
1238 swfdec_bits_init_data (&bits
, data
, length
);
1239 script
= swfdec_script_new_from_bits (&bits
, "init", version
);
1240 if (script
== NULL
) {
1241 g_warning ("script passed to swfdec_as_context_run_init_script is invalid");
1244 swfdec_as_object_run (context
->global
, script
);
1245 swfdec_script_unref (script
);
1247 SWFDEC_LOG ("not running init script, since version is <= 4");
1252 * swfdec_as_context_startup:
1253 * @context: a #SwfdecAsContext
1255 * Starts up the context. This function must be called before any Actionscript
1256 * is called on @context.
1259 swfdec_as_context_startup (SwfdecAsContext
*context
)
1261 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
1262 g_return_if_fail (context
->state
== SWFDEC_AS_CONTEXT_NEW
);
1264 if (context
->cur
== NULL
&&
1265 !swfdec_as_stack_push_segment (context
))
1267 if (context
->global
== NULL
)
1268 context
->global
= swfdec_as_object_new_empty (context
);
1269 /* init the two internal functions */
1270 /* FIXME: remove them for normal contexts? */
1271 swfdec_player_preinit_global (context
);
1272 /* get the necessary objects up to define objects and functions sanely */
1273 swfdec_as_object_init_context (context
);
1274 /* define the global object and other important ones */
1275 swfdec_as_context_init_global (context
);
1277 /* run init script */
1278 swfdec_as_context_run_init_script (context
, swfdec_as_initialize
, sizeof (swfdec_as_initialize
), 8);
1280 if (context
->state
== SWFDEC_AS_CONTEXT_NEW
)
1281 context
->state
= SWFDEC_AS_CONTEXT_RUNNING
;
1285 * swfdec_as_context_check_continue:
1286 * @context: the context that might be running too long
1288 * Checks if the context has been running too long. If it has, it gets aborted.
1290 * Returns: %TRUE if this player aborted.
1293 swfdec_as_context_check_continue (SwfdecAsContext
*context
)
1295 SwfdecAsContextClass
*klass
;
1297 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), TRUE
);
1299 klass
= SWFDEC_AS_CONTEXT_GET_CLASS (context
);
1300 if (klass
->check_continue
== NULL
)
1302 if (!klass
->check_continue (context
)) {
1303 swfdec_as_context_abort (context
, "Runtime exceeded");
1310 * swfdec_as_context_is_aborted:
1311 * @context: a #SwfdecAsContext
1313 * Determines if the given context is aborted. An aborted context is not able
1314 * to execute any scripts. Aborting can happen if the script engine detects bad
1315 * scripts that cause excessive memory usage, infinite loops or other problems.
1316 * In that case the script engine aborts for safety reasons.
1318 * Returns: %TRUE if the player is aborted, %FALSE if it runs normally.
1321 swfdec_as_context_is_aborted (SwfdecAsContext
*context
)
1323 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), TRUE
);
1325 return context
->state
== SWFDEC_AS_CONTEXT_ABORTED
;