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_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_constant_pool.h"
40 #include "swfdec_debug.h"
41 #include "swfdec_gc_object.h"
42 #include "swfdec_internal.h" /* for swfdec_player_preinit_global() */
43 #include "swfdec_script.h"
45 /*** GARBAGE COLLECTION DOCS ***/
49 * @title: Internals of the script engine
50 * @short_description: understanding internals such as garbage collection
51 * @see_also: #SwfdecAsContext, #SwfdecGcObject
53 * This section deals with the internals of the Swfdec Actionscript engine. You
54 * should probably read this first when trying to write code with it. If you're
55 * just trying to use Swfdec for creating Flash content, you can probably skip
58 * First, I'd like to note that the Swfdec script engine has to be modeled very
59 * closely after the existing Flash players. So if there are some behaviours
60 * that seem stupid at first sight, it might very well be that it was chosen for
61 * a very particular reason. Now on to the features.
63 * The Swfdec script engine tries to imitate Actionscript as good as possible.
64 * Actionscript is similar to Javascript, but not equal. Depending on the
65 * version of the script executed it might be more or less similar. An important
66 * example is that Flash in versions up to 6 did case-insensitive variable
69 * The script engine starts its life when it is initialized via
70 * swfdec_as_context_startup(). At that point, the basic objects are created.
71 * After this function has been called, you can start executing code. Code
72 * execution happens by calling swfdec_as_function_call_full() or convenience
73 * wrappers like swfdec_as_object_run() or swfdec_as_object_call().
75 * It is also easily possible to extend the environment by adding new objects.
76 * In fact, without doing so, the environment is pretty bare as it just contains
77 * the basic Math, String, Number, Array, Date and Boolean objects. This is done
78 * by adding #SwfdecAsNative functions to the environment. The easy way
79 * to do this is via swfdec_as_object_add_function().
81 * The Swfdec script engine is dynamically typed and knows about different types
82 * of values. See #SwfdecAsValue for the different values. Memory management is
83 * done using a mark and sweep garbage collector. You can initiate a garbage
84 * collection cycle by calling swfdec_as_context_gc() or
85 * swfdec_as_context_maybe_gc(). You should do this regularly to avoid excessive
86 * memory use. The #SwfdecAsContext will then collect the objects and strings it
87 * is keeping track of. If you want to use an object or string in the script
88 * engine, you'll have to add it first via swfdec_as_object_add() or
89 * swfdec_as_context_get_string() and swfdec_as_context_give_string(),
92 * Garbage-collected strings are special in Swfdec in that they are unique. This
93 * means the same string exists exactly once. Because of this you can do
94 * equality comparisons using == instead of strcmp. It also means that if you
95 * forget to add a string to the context before using it, your app will most
96 * likely not work, since the string will not compare equal to any other string.
98 * When a garbage collection cycle happens, all reachable objects and strings
99 * are marked and all unreachable ones are freed. This is done by calling the
100 * context class's mark function which will mark all reachable objects. This is
101 * usually called the "root set". For any reachable object, the object's mark
102 * function is called so that the object in turn can mark all objects it can
103 * reach itself. Marking is done via functions described below.
109 * SECTION:SwfdecAsContext
110 * @title: SwfdecAsContext
111 * @short_description: the main script engine context
112 * @see_also: SwfdecPlayer
114 * A #SwfdecAsContext provides the main execution environment for Actionscript
115 * execution. It provides the objects typically available in ECMAScript and
116 * manages script execution, garbage collection etc. #SwfdecPlayer is a
117 * subclass of the context that implements Flash specific objects on top of it.
118 * However, it is possible to use the context for completely different functions
119 * where a sandboxed scripting environment is needed. An example is the Swfdec
121 * <note>The Actionscript engine is similar, but not equal to Javascript. It
122 * is not very different, but it is different.</note>
128 * This is the main object ued to hold the state of a script engine. All members
129 * are private and should not be accessed.
131 * Subclassing this structure to get scripting support in your own appliation is
136 * SwfdecAsContextState
137 * @SWFDEC_AS_CONTEXT_NEW: the context is not yet initialized,
138 * swfdec_as_context_startup() needs to be called.
139 * @SWFDEC_AS_CONTEXT_RUNNING: the context is running normally
140 * @SWFDEC_AS_CONTEXT_INTERRUPTED: the context has been interrupted by a
142 * @SWFDEC_AS_CONTEXT_ABORTED: the context has aborted execution due to a
145 * The state of the context describes what operations are possible on the context.
146 * It will be in the state @SWFDEC_AS_CONTEXT_STATE_RUNNING almost all the time. If
147 * it is in the state @SWFDEC_AS_CONTEXT_STATE_ABORTED, it will not work anymore and
148 * every operation on it will instantly fail.
151 /*** RUNNING STATE ***/
154 * swfdec_as_context_abort:
155 * @context: a #SwfdecAsContext
156 * @reason: a string describing why execution was aborted
158 * Aborts script execution in @context. Call this functon if the script engine
159 * encountered a fatal error and cannot continue. A possible reason for this is
160 * an out-of-memory condition.
163 swfdec_as_context_abort (SwfdecAsContext
*context
, const char *reason
)
165 g_return_if_fail (context
);
167 if (context
->state
!= SWFDEC_AS_CONTEXT_ABORTED
) {
168 SWFDEC_ERROR ("abort: %s", reason
);
169 context
->state
= SWFDEC_AS_CONTEXT_ABORTED
;
170 g_object_notify (G_OBJECT (context
), "aborted");
174 /*** MEMORY MANAGEMENT ***/
177 * swfdec_as_context_try_use_mem:
178 * @context: a #SwfdecAsContext
179 * @bytes: number of bytes to use
181 * Tries to register @bytes additional bytes as in use by the @context. This
182 * function keeps track of the memory that script code consumes. The scripting
183 * engine won't be stopped, even if there wasn't enough memory left.
185 * Returns: %TRUE if the memory could be allocated. %FALSE on OOM.
188 swfdec_as_context_try_use_mem (SwfdecAsContext
*context
, gsize bytes
)
190 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), FALSE
);
191 g_return_val_if_fail (bytes
> 0, FALSE
);
193 if (context
->state
== SWFDEC_AS_CONTEXT_ABORTED
)
196 context
->memory
+= bytes
;
197 context
->memory_since_gc
+= bytes
;
198 SWFDEC_LOG ("+%4"G_GSIZE_FORMAT
" bytes, total %7"G_GSIZE_FORMAT
" (%7"G_GSIZE_FORMAT
" since GC)",
199 bytes
, context
->memory
, context
->memory_since_gc
);
205 * swfdec_as_context_use_mem:
206 * @context: a #SwfdecAsContext
207 * @bytes: number of bytes to use
209 * Registers @bytes additional bytes as in use by the @context. This function
210 * keeps track of the memory that script code consumes. If too much memory is
211 * in use, this function may decide to stop the script engine with an out of
215 swfdec_as_context_use_mem (SwfdecAsContext
*context
, gsize bytes
)
217 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
218 g_return_if_fail (bytes
> 0);
220 /* FIXME: Don't forget to abort on OOM */
221 if (!swfdec_as_context_try_use_mem (context
, bytes
)) {
222 swfdec_as_context_abort (context
, "Out of memory");
223 /* add the memory anyway, as we're gonna make use of it. */
224 context
->memory
+= bytes
;
225 context
->memory_since_gc
+= bytes
;
230 * swfdec_as_context_unuse_mem:
231 * @context: a #SwfdecAsContext
232 * @bytes: number of bytes to release
234 * Releases a number of bytes previously allocated using
235 * swfdec_as_context_use_mem(). See that function for details.
238 swfdec_as_context_unuse_mem (SwfdecAsContext
*context
, gsize bytes
)
240 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
241 g_return_if_fail (bytes
> 0);
242 g_return_if_fail (context
->memory
>= bytes
);
244 context
->memory
-= bytes
;
245 SWFDEC_LOG ("-%4"G_GSIZE_FORMAT
" bytes, total %7"G_GSIZE_FORMAT
" (%7"G_GSIZE_FORMAT
" since GC)",
246 bytes
, context
->memory
, context
->memory_since_gc
);
252 swfdec_as_context_remove_strings (gpointer key
, gpointer value
, gpointer data
)
254 SwfdecAsContext
*context
= data
;
257 string
= (char *) value
;
258 /* it doesn't matter that rooted strings aren't destroyed, they're constant */
259 if (string
[0] & SWFDEC_AS_GC_ROOT
) {
260 SWFDEC_LOG ("rooted: %s", (char *) key
);
262 } else if (string
[0] & SWFDEC_AS_GC_MARK
) {
263 SWFDEC_LOG ("marked: %s", (char *) key
);
264 string
[0] &= ~SWFDEC_AS_GC_MARK
;
268 SWFDEC_LOG ("deleted: %s", (char *) key
);
269 len
= (strlen ((char *) key
) + 2);
270 swfdec_as_context_unuse_mem (context
, len
);
271 g_slice_free1 (len
, value
);
277 swfdec_as_context_remove_objects (gpointer key
, gpointer value
, gpointer debugger
)
282 /* we only check for mark here, not root, since this works on destroy, too */
283 if (gc
->flags
& SWFDEC_AS_GC_MARK
) {
284 gc
->flags
&= ~SWFDEC_AS_GC_MARK
;
285 SWFDEC_LOG ("%s: %s %p", (gc
->flags
& SWFDEC_AS_GC_ROOT
) ? "rooted" : "marked",
286 G_OBJECT_TYPE_NAME (gc
), gc
);
289 SWFDEC_LOG ("deleted: %s %p", G_OBJECT_TYPE_NAME (gc
), gc
);
296 swfdec_as_context_collect (SwfdecAsContext
*context
)
298 SWFDEC_INFO (">> collecting garbage");
299 /* NB: This functions is called without GC from swfdec_as_context_dispose */
300 g_hash_table_foreach_remove (context
->strings
,
301 swfdec_as_context_remove_strings
, context
);
302 g_hash_table_foreach_remove (context
->objects
,
303 swfdec_as_context_remove_objects
, context
->debugger
);
304 SWFDEC_INFO (">> done collecting garbage");
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_gc_object_mark (value
->value
.object
);
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 SwfdecGcObject
*object
= key
;
351 if ((object
->flags
& (SWFDEC_AS_GC_MARK
| SWFDEC_AS_GC_ROOT
)) == SWFDEC_AS_GC_ROOT
)
352 swfdec_gc_object_mark (object
);
355 /* FIXME: replace this with refcounted strings? */
357 swfdec_as_context_mark_constant_pools (gpointer key
, gpointer value
, gpointer unused
)
359 SwfdecConstantPool
*pool
= value
;
362 for (i
= 0; i
< swfdec_constant_pool_size (pool
); i
++) {
363 const char *s
= swfdec_constant_pool_get (pool
, i
);
365 swfdec_as_string_mark (s
);
370 swfdec_as_context_do_mark (SwfdecAsContext
*context
)
372 /* This if is needed for SwfdecPlayer */
373 if (context
->global
) {
374 swfdec_gc_object_mark (context
->global
);
375 swfdec_gc_object_mark (context
->Function
);
376 swfdec_gc_object_mark (context
->Function_prototype
);
377 swfdec_gc_object_mark (context
->Object
);
378 swfdec_gc_object_mark (context
->Object_prototype
);
380 if (context
->exception
)
381 swfdec_as_value_mark (&context
->exception_value
);
382 g_hash_table_foreach (context
->objects
, swfdec_as_context_mark_roots
, NULL
);
383 g_hash_table_foreach (context
->constant_pools
, swfdec_as_context_mark_constant_pools
, NULL
);
387 * swfdec_as_context_gc:
388 * @context: a #SwfdecAsContext
390 * Calls the Swfdec Gargbage collector and reclaims any unused memory. You
391 * should call this function or swfdec_as_context_maybe_gc() regularly.
392 * <warning>Calling the GC during execution of code or initialization is not
396 swfdec_as_context_gc (SwfdecAsContext
*context
)
398 SwfdecAsContextClass
*klass
;
400 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
401 g_return_if_fail (context
->frame
== NULL
);
402 g_return_if_fail (context
->state
== SWFDEC_AS_CONTEXT_RUNNING
);
404 if (context
->state
== SWFDEC_AS_CONTEXT_ABORTED
)
406 SWFDEC_INFO ("invoking the garbage collector");
407 klass
= SWFDEC_AS_CONTEXT_GET_CLASS (context
);
408 g_assert (klass
->mark
);
409 klass
->mark (context
);
410 swfdec_as_context_collect (context
);
411 context
->memory_since_gc
= 0;
415 swfdec_as_context_needs_gc (SwfdecAsContext
*context
)
417 return context
->memory_since_gc
>= context
->memory_until_gc
;
421 * swfdec_as_context_maybe_gc:
422 * @context: a #SwfdecAsContext
424 * Calls the garbage collector if necessary. It's a good idea to call this
425 * function regularly instead of swfdec_as_context_gc() as it only does collect
426 * garage as needed. For example, #SwfdecPlayer calls this function after every
430 swfdec_as_context_maybe_gc (SwfdecAsContext
*context
)
432 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
433 g_return_if_fail (context
->state
== SWFDEC_AS_CONTEXT_RUNNING
);
434 g_return_if_fail (context
->frame
== NULL
);
436 if (swfdec_as_context_needs_gc (context
))
437 swfdec_as_context_gc (context
);
440 /*** SWFDEC_AS_CONTEXT ***/
455 G_DEFINE_TYPE (SwfdecAsContext
, swfdec_as_context
, G_TYPE_OBJECT
)
456 static guint signals
[LAST_SIGNAL
] = { 0, };
459 swfdec_as_context_get_property (GObject
*object
, guint param_id
, GValue
*value
,
462 SwfdecAsContext
*context
= SWFDEC_AS_CONTEXT (object
);
466 g_value_set_object (value
, context
->debugger
);
469 g_value_set_boolean (value
, context
->state
== SWFDEC_AS_CONTEXT_ABORTED
);
472 g_value_set_ulong (value
, (gulong
) context
->memory_until_gc
);
475 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
481 swfdec_as_context_set_property (GObject
*object
, guint param_id
, const GValue
*value
,
484 SwfdecAsContext
*context
= SWFDEC_AS_CONTEXT (object
);
488 context
->debugger
= SWFDEC_AS_DEBUGGER (g_value_dup_object (value
));
490 case PROP_RANDOM_SEED
:
491 g_rand_set_seed (context
->rand
, g_value_get_uint (value
));
494 context
->memory_until_gc
= g_value_get_ulong (value
);
497 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
503 swfdec_as_context_dispose (GObject
*object
)
505 SwfdecAsContext
*context
= SWFDEC_AS_CONTEXT (object
);
507 while (context
->stack
)
508 swfdec_as_stack_pop_segment (context
);
509 /* We need to make sure there's no exception here. Otherwise collecting
510 * frames that are inside a try block will assert */
511 swfdec_as_context_catch (context
, NULL
);
512 swfdec_as_context_collect (context
);
513 if (context
->memory
!= 0) {
514 g_critical ("%zu bytes of memory left over\n", context
->memory
);
516 g_assert (g_hash_table_size (context
->objects
) == 0);
517 g_assert (g_hash_table_size (context
->constant_pools
) == 0);
518 g_hash_table_destroy (context
->constant_pools
);
519 g_hash_table_destroy (context
->objects
);
520 g_hash_table_destroy (context
->strings
);
521 g_rand_free (context
->rand
);
522 if (context
->debugger
) {
523 g_object_unref (context
->debugger
);
524 context
->debugger
= NULL
;
527 G_OBJECT_CLASS (swfdec_as_context_parent_class
)->dispose (object
);
531 swfdec_as_context_class_init (SwfdecAsContextClass
*klass
)
533 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
535 object_class
->dispose
= swfdec_as_context_dispose
;
536 object_class
->get_property
= swfdec_as_context_get_property
;
537 object_class
->set_property
= swfdec_as_context_set_property
;
539 g_object_class_install_property (object_class
, PROP_DEBUGGER
,
540 g_param_spec_object ("debugger", "debugger", "debugger used in this player",
541 SWFDEC_TYPE_AS_DEBUGGER
, G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
542 g_object_class_install_property (object_class
, PROP_RANDOM_SEED
,
543 g_param_spec_uint ("random-seed", "random seed",
544 "seed used for calculating random numbers",
545 0, G_MAXUINT32
, 0, G_PARAM_WRITABLE
)); /* FIXME: make this readwrite for replaying? */
546 g_object_class_install_property (object_class
, PROP_ABORTED
,
547 g_param_spec_boolean ("aborted", "aborted", "set when the script engine aborts due to an error",
548 FALSE
, G_PARAM_READABLE
));
549 g_object_class_install_property (object_class
, PROP_UNTIL_GC
,
550 g_param_spec_ulong ("memory-until-gc", "memory until gc",
551 "amount of bytes that need to be allocated before garbage collection triggers",
552 0, G_MAXULONG
, 8 * 1024 * 1024, G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
));
555 * SwfdecAsContext::trace:
556 * @context: the #SwfdecAsContext affected
557 * @text: the debugging string
559 * Emits a debugging string while running. The effect of calling any swfdec
560 * functions on the emitting @context is undefined.
562 signals
[TRACE
] = g_signal_new ("trace", G_TYPE_FROM_CLASS (klass
),
563 G_SIGNAL_RUN_LAST
, 0, NULL
, NULL
, g_cclosure_marshal_VOID__STRING
,
564 G_TYPE_NONE
, 1, G_TYPE_STRING
);
566 klass
->mark
= swfdec_as_context_do_mark
;
570 swfdec_as_context_init (SwfdecAsContext
*context
)
574 context
->version
= G_MAXUINT
;
576 context
->strings
= g_hash_table_new (g_str_hash
, g_str_equal
);
577 context
->objects
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
578 context
->constant_pools
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
580 for (s
= swfdec_as_strings
; *s
== '\2'; s
+= strlen (s
) + 1) {
581 g_hash_table_insert (context
->strings
, (char *) s
+ 1, (char *) s
);
584 context
->rand
= g_rand_new ();
585 g_get_current_time (&context
->start_time
);
591 swfdec_as_context_create_string (SwfdecAsContext
*context
, const char *string
, gsize len
)
595 if (!swfdec_as_context_try_use_mem (context
, sizeof (char) * (2 + len
))) {
596 swfdec_as_context_abort (context
, "Out of memory");
597 return SWFDEC_AS_STR_EMPTY
;
600 new = g_slice_alloc (2 + len
);
601 memcpy (&new[1], string
, len
);
603 new[0] = 0; /* GC flags */
604 g_hash_table_insert (context
->strings
, new + 1, new);
610 * swfdec_as_context_get_string:
611 * @context: a #SwfdecAsContext
612 * @string: a sting that is not garbage-collected
614 * Gets the garbage-collected version of @string. You need to call this function
615 * for every not garbage-collected string that you want to use in Swfdecs script
618 * Returns: the garbage-collected version of @string
621 swfdec_as_context_get_string (SwfdecAsContext
*context
, const char *string
)
626 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), NULL
);
627 g_return_val_if_fail (string
!= NULL
, NULL
);
629 if (g_hash_table_lookup_extended (context
->strings
, string
, (gpointer
) &ret
, NULL
))
632 len
= strlen (string
);
633 return swfdec_as_context_create_string (context
, string
, len
);
637 * swfdec_as_context_give_string:
638 * @context: a #SwfdecAsContext
639 * @string: string to make refcounted
641 * Takes ownership of @string and returns a refcounted version of the same
642 * string. This function is the same as swfdec_as_context_get_string(), but
643 * takes ownership of @string.
645 * Returns: A refcounted string
648 swfdec_as_context_give_string (SwfdecAsContext
*context
, char *string
)
652 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), NULL
);
653 g_return_val_if_fail (string
!= NULL
, NULL
);
655 ret
= swfdec_as_context_get_string (context
, string
);
661 * swfdec_as_context_is_constructing:
662 * @context: a #SwfdecAsConstruct
664 * Determines if the contexxt is currently constructing. This information is
665 * used by various constructors to do different things when they are
666 * constructing and when they are not. The Boolean, Number and String functions
667 * for example setup the newly constructed objects when constructing but only
668 * cast the provided argument when being called.
670 * Returns: %TRUE if the currently executing frame is a constructor
673 swfdec_as_context_is_constructing (SwfdecAsContext
*context
)
675 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), FALSE
);
677 return context
->frame
&& context
->frame
->construct
;
681 * swfdec_as_context_get_frame:
682 * @context: a #SwfdecAsContext
684 * This is a debugging function. It gets the topmost stack frame that is
685 * currently executing. If no function is executing, %NULL is returned. You can
686 * easily get a backtrace with code like this:
687 * |[for (frame = swfdec_as_context_get_frame (context); frame != NULL;
688 * frame = swfdec_as_frame_get_next (frame)) {
689 * char *s = swfdec_as_object_get_debug (SWFDEC_AS_OBJECT (frame));
690 * g_print ("%s\n", s);
694 * Returns: the currently executing frame or %NULL if none
697 swfdec_as_context_get_frame (SwfdecAsContext
*context
)
699 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), NULL
);
701 return context
->frame
;
705 * swfdec_as_context_throw:
706 * @context: a #SwfdecAsContext
707 * @value: a #SwfdecAsValue to be thrown
709 * Throws a new exception in the @context using the given @value. This function
710 * can only be called if the @context is not already throwing an exception.
713 swfdec_as_context_throw (SwfdecAsContext
*context
, const SwfdecAsValue
*value
)
715 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
716 g_return_if_fail (SWFDEC_IS_AS_VALUE (value
));
717 g_return_if_fail (!context
->exception
);
719 context
->exception
= TRUE
;
720 context
->exception_value
= *value
;
724 * swfdec_as_context_catch:
725 * @context: a #SwfdecAsContext
726 * @value: a #SwfdecAsValue to be thrown
728 * Removes the currently thrown exception from @context and sets @value to the
731 * Returns: %TRUE if an exception was catched, %FALSE otherwise
734 swfdec_as_context_catch (SwfdecAsContext
*context
, SwfdecAsValue
*value
)
736 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), FALSE
);
738 if (!context
->exception
)
742 *value
= context
->exception_value
;
744 context
->exception
= FALSE
;
745 SWFDEC_AS_VALUE_SET_UNDEFINED (&context
->exception_value
);
751 * swfdec_as_context_get_time:
752 * @context: a #SwfdecAsContext
753 * @tv: a #GTimeVal to be set to the context's time
755 * This function queries the time to be used inside this context. By default,
756 * this is the same as g_get_current_time(), but it may be overwriten to allow
757 * things such as slower or faster playback.
760 swfdec_as_context_get_time (SwfdecAsContext
*context
, GTimeVal
*tv
)
762 SwfdecAsContextClass
*klass
;
764 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
765 g_return_if_fail (tv
!= NULL
);
767 klass
= SWFDEC_AS_CONTEXT_GET_CLASS (context
);
769 klass
->get_time (context
, tv
);
771 g_get_current_time (tv
);
775 * swfdec_as_context_run:
776 * @context: a #SwfdecAsContext
778 * Continues running the script engine. Executing code in this engine works
779 * in 2 steps: First, you push the frame to be executed onto the stack, then
780 * you call this function to execute it. So this function is the single entry
781 * point to script execution. This might be helpful when debugging your
783 * <note>A lot of convenience functions like swfdec_as_object_run() call this
784 * function automatically.</note>
787 swfdec_as_context_run (SwfdecAsContext
*context
)
789 SwfdecAsFrame
*frame
;
790 SwfdecScript
*script
;
791 const SwfdecActionSpec
*spec
;
792 const guint8
*startpc
, *pc
, *endpc
, *nextpc
, *exitpc
;
793 #ifndef G_DISABLE_ASSERT
794 SwfdecAsValue
*check
;
798 guint original_version
;
799 void (* step
) (SwfdecAsDebugger
*debugger
, SwfdecAsContext
*context
);
800 gboolean check_block
; /* some opcodes avoid a scope check */
802 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
803 g_return_if_fail (context
->frame
!= NULL
);
804 g_return_if_fail (context
->frame
->script
!= NULL
);
805 g_return_if_fail (context
->global
); /* check here because of swfdec_sandbox_(un)use() */
808 frame
= context
->frame
;
809 original_version
= context
->version
;
812 if (context
->state
== SWFDEC_AS_CONTEXT_ABORTED
)
814 if (!swfdec_as_context_check_continue (context
))
816 if (context
->call_depth
> 256) {
817 /* we've exceeded our maximum call depth, throw an error and abort */
818 swfdec_as_context_abort (context
, "Stack overflow");
822 if (context
->debugger
) {
823 SwfdecAsDebuggerClass
*klass
= SWFDEC_AS_DEBUGGER_GET_CLASS (context
->debugger
);
829 g_assert (frame
->target
);
830 script
= frame
->script
;
831 context
->version
= script
->version
;
832 startpc
= script
->buffer
->data
;
833 endpc
= startpc
+ script
->buffer
->length
;
834 exitpc
= script
->exit
;
838 while (context
->state
< SWFDEC_AS_CONTEXT_ABORTED
) {
839 if (context
->exception
) {
840 swfdec_as_frame_handle_exception (frame
);
841 if (frame
!= context
->frame
)
847 swfdec_as_frame_return (frame
, NULL
);
850 if (pc
< startpc
|| pc
>= endpc
) {
851 SWFDEC_ERROR ("pc %p not in valid range [%p, %p) anymore", pc
, startpc
, endpc
);
854 while (check_block
&& (pc
< frame
->block_start
|| pc
>= frame
->block_end
)) {
855 SWFDEC_LOG ("code exited block");
856 swfdec_as_frame_pop_block (frame
, context
);
858 if (frame
!= context
->frame
)
860 if (context
->exception
)
863 if (context
->exception
)
866 /* decode next action */
868 /* invoke debugger if there is one */
871 (* step
) (context
->debugger
, context
);
872 if (frame
!= context
->frame
)
878 spec
= swfdec_as_actions
+ action
;
880 if (pc
+ 2 >= endpc
) {
881 SWFDEC_ERROR ("action %u length value out of range", action
);
885 len
= pc
[1] | pc
[2] << 8;
886 if (data
+ len
> endpc
) {
887 SWFDEC_ERROR ("action %u length %u out of range", action
, len
);
890 nextpc
= pc
+ 3 + len
;
896 /* check action is valid */
898 SWFDEC_WARNING ("cannot interpret action %3u 0x%02X %s for version %u, skipping it", action
,
899 action
, spec
->name
? spec
->name
: "Unknown", script
->version
);
900 frame
->pc
= pc
= nextpc
;
904 if (script
->version
< spec
->version
) {
905 SWFDEC_WARNING ("cannot interpret action %3u 0x%02X %s for version %u, using version %u instead",
906 action
, action
, spec
->name
? spec
->name
: "Unknown", script
->version
, spec
->version
);
908 if (spec
->remove
> 0) {
909 if (spec
->add
> spec
->remove
)
910 swfdec_as_stack_ensure_free (context
, spec
->add
- spec
->remove
);
911 swfdec_as_stack_ensure_size (context
, spec
->remove
);
914 swfdec_as_stack_ensure_free (context
, spec
->add
);
916 if (context
->state
> SWFDEC_AS_CONTEXT_RUNNING
) {
917 SWFDEC_WARNING ("context not running anymore, aborting");
920 #ifndef G_DISABLE_ASSERT
921 check
= (spec
->add
>= 0 && spec
->remove
>= 0) ? context
->cur
+ spec
->add
- spec
->remove
: NULL
;
924 spec
->exec (context
, action
, data
, len
);
925 /* adapt the pc if the action did not, otherwise, leave it alone */
926 /* FIXME: do this via flag? */
927 if (frame
->pc
== pc
) {
928 frame
->pc
= pc
= nextpc
;
931 if (frame
->pc
< pc
&&
932 !swfdec_as_context_check_continue (context
)) {
938 if (frame
== context
->frame
) {
939 #ifndef G_DISABLE_ASSERT
940 if (check
!= NULL
&& check
!= context
->cur
) {
941 g_error ("action %s was supposed to change the stack by %d (+%d -%d), but it changed by %td",
942 spec
->name
, spec
->add
- spec
->remove
, spec
->add
, spec
->remove
,
943 context
->cur
- check
+ spec
->add
- spec
->remove
);
947 /* someone called/returned from a function, reread variables */
953 if (context
->frame
== frame
)
954 swfdec_as_frame_return (frame
, NULL
);
956 context
->version
= original_version
;
963 swfdec_as_context_ASSetPropFlags_set_one_flag (SwfdecAsObject
*object
,
964 const char *s
, guint
*flags
)
966 swfdec_as_object_unset_variable_flags (object
, s
, flags
[1]);
967 swfdec_as_object_set_variable_flags (object
, s
, flags
[0]);
971 swfdec_as_context_ASSetPropFlags_foreach (SwfdecAsObject
*object
,
972 const char *s
, SwfdecAsValue
*val
, guint cur_flags
, gpointer data
)
976 /* shortcut if the flags already match */
977 if (cur_flags
== ((cur_flags
&~ flags
[1]) | flags
[0]))
980 swfdec_as_context_ASSetPropFlags_set_one_flag (object
, s
, flags
);
984 SWFDEC_AS_NATIVE (1, 0, swfdec_as_context_ASSetPropFlags
)
986 swfdec_as_context_ASSetPropFlags (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
987 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
989 guint flags
[2]; /* flags and mask - array so we can pass it as data pointer */
995 if (!SWFDEC_AS_VALUE_IS_OBJECT (&argv
[0]))
997 obj
= SWFDEC_AS_VALUE_GET_OBJECT (&argv
[0]);
998 flags
[0] = swfdec_as_value_to_integer (cx
, &argv
[2]);
999 flags
[1] = (argc
> 3) ? swfdec_as_value_to_integer (cx
, &argv
[3]) : 0;
1001 if (flags
[0] == 0 && flags
[1] == 0) {
1002 // we should add autosizing length attribute here
1003 SWFDEC_FIXME ("ASSetPropFlags to set special length attribute not implemented");
1007 if (SWFDEC_AS_VALUE_IS_NULL (&argv
[1])) {
1008 swfdec_as_object_foreach (obj
, swfdec_as_context_ASSetPropFlags_foreach
, flags
);
1011 g_strsplit (swfdec_as_value_to_string (cx
, &argv
[1]), ",", -1);
1013 for (i
= 0; split
[i
]; i
++) {
1014 swfdec_as_context_ASSetPropFlags_set_one_flag (obj
,
1015 swfdec_as_context_get_string (cx
, split
[i
]), flags
);
1021 SWFDEC_AS_NATIVE (200, 19, swfdec_as_context_isFinite
)
1023 swfdec_as_context_isFinite (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1024 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1031 d
= swfdec_as_value_to_number (cx
, &argv
[0]);
1032 SWFDEC_AS_VALUE_SET_BOOLEAN (retval
, isfinite (d
) ? TRUE
: FALSE
);
1035 SWFDEC_AS_NATIVE (200, 18, swfdec_as_context_isNaN
)
1037 swfdec_as_context_isNaN (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1038 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1045 d
= swfdec_as_value_to_number (cx
, &argv
[0]);
1046 SWFDEC_AS_VALUE_SET_BOOLEAN (retval
, isnan (d
) ? TRUE
: FALSE
);
1049 SWFDEC_AS_NATIVE (100, 2, swfdec_as_context_parseInt
)
1051 swfdec_as_context_parseInt (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1052 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1059 SWFDEC_AS_CHECK (0, NULL
, "s|i", &s
, &radix
);
1061 if (argc
>= 2 && (radix
< 2 || radix
> 36)) {
1062 SWFDEC_AS_VALUE_SET_NUMBER (retval
, NAN
);
1066 // special case, don't allow sign in front of the 0x
1067 if ((s
[0] == '-' || s
[0] == '+') && s
[1] == '0' &&
1068 (s
[2] == 'x' || s
[2] == 'X')) {
1069 SWFDEC_AS_VALUE_SET_NUMBER (retval
, NAN
);
1075 if (s
[0] == '0' && (s
[1] == 'x' || s
[1] == 'X')) {
1077 } else if ((s
[0] == '0' || ((s
[0] == '+' || s
[0] == '-') && s
[1] == '0')) &&
1078 s
[strspn (s
+1, "01234567") + 1] == '\0') {
1085 // skip 0x at the start
1086 if (s
[0] == '0' && (s
[1] == 'x' || s
[1] == 'X'))
1089 // strtoll parses strings with 0x when given radix 16, but we don't want that
1091 const char *skip
= s
+ strspn (s
, " \t\r\n");
1092 if (skip
!= s
&& (skip
[0] == '-' || skip
[0] == '+'))
1094 if (skip
!= s
&& skip
[0] == '0' && (skip
[1] == 'x' || skip
[1] == 'X')) {
1095 SWFDEC_AS_VALUE_SET_NUMBER (retval
, 0);
1100 i
= g_ascii_strtoll (s
, &tail
, radix
);
1103 SWFDEC_AS_VALUE_SET_NUMBER (retval
, NAN
);
1107 if (i
> G_MAXINT32
|| i
< G_MININT32
) {
1108 SWFDEC_AS_VALUE_SET_NUMBER (retval
, i
);
1110 SWFDEC_AS_VALUE_SET_INT (retval
, i
);
1114 SWFDEC_AS_NATIVE (100, 3, swfdec_as_context_parseFloat
)
1116 swfdec_as_context_parseFloat (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1117 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1125 // we need to remove everything after x or I, since strtod parses hexadecimal
1126 // numbers and Infinity
1127 s
= g_strdup (swfdec_as_value_to_string (cx
, &argv
[0]));
1128 if ((p
= strpbrk (s
, "xXiI")) != NULL
) {
1132 d
= g_ascii_strtod (s
, &tail
);
1135 SWFDEC_AS_VALUE_SET_NUMBER (retval
, NAN
);
1137 SWFDEC_AS_VALUE_SET_NUMBER (retval
, d
);
1144 swfdec_as_context_init_global (SwfdecAsContext
*context
)
1148 SWFDEC_AS_VALUE_SET_NUMBER (&val
, NAN
);
1149 swfdec_as_object_set_variable (context
->global
, SWFDEC_AS_STR_NaN
, &val
);
1150 SWFDEC_AS_VALUE_SET_NUMBER (&val
, HUGE_VAL
);
1151 swfdec_as_object_set_variable (context
->global
, SWFDEC_AS_STR_Infinity
, &val
);
1155 swfdec_as_context_run_init_script (SwfdecAsContext
*context
, const guint8
*data
,
1156 gsize length
, guint version
)
1158 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
1159 g_return_if_fail (data
!= NULL
);
1160 g_return_if_fail (length
> 0);
1164 SwfdecScript
*script
;
1165 swfdec_bits_init_data (&bits
, data
, length
);
1166 script
= swfdec_script_new_from_bits (&bits
, "init", version
);
1167 if (script
== NULL
) {
1168 g_warning ("script passed to swfdec_as_context_run_init_script is invalid");
1171 swfdec_as_object_run (context
->global
, script
);
1172 swfdec_script_unref (script
);
1174 SWFDEC_LOG ("not running init script, since version is <= 4");
1179 * swfdec_as_context_startup:
1180 * @context: a #SwfdecAsContext
1182 * Starts up the context. This function must be called before any Actionscript
1183 * is called on @context.
1186 swfdec_as_context_startup (SwfdecAsContext
*context
)
1188 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
1189 g_return_if_fail (context
->state
== SWFDEC_AS_CONTEXT_NEW
);
1191 if (context
->cur
== NULL
&&
1192 !swfdec_as_stack_push_segment (context
))
1194 if (context
->global
== NULL
)
1195 context
->global
= swfdec_as_object_new_empty (context
);
1196 /* init the two internal functions */
1197 /* FIXME: remove them for normal contexts? */
1198 swfdec_player_preinit_global (context
);
1199 /* get the necessary objects up to define objects and functions sanely */
1200 swfdec_as_function_init_context (context
);
1201 swfdec_as_object_init_context (context
);
1202 /* define the global object and other important ones */
1203 swfdec_as_context_init_global (context
);
1205 /* run init script */
1206 swfdec_as_context_run_init_script (context
, swfdec_as_initialize
, sizeof (swfdec_as_initialize
), 8);
1208 if (context
->state
== SWFDEC_AS_CONTEXT_NEW
)
1209 context
->state
= SWFDEC_AS_CONTEXT_RUNNING
;
1213 * swfdec_as_context_check_continue:
1214 * @context: the context that might be running too long
1216 * Checks if the context has been running too long. If it has, it gets aborted.
1218 * Returns: %TRUE if this player aborted.
1221 swfdec_as_context_check_continue (SwfdecAsContext
*context
)
1223 SwfdecAsContextClass
*klass
;
1225 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), TRUE
);
1227 klass
= SWFDEC_AS_CONTEXT_GET_CLASS (context
);
1228 if (klass
->check_continue
== NULL
)
1230 if (!klass
->check_continue (context
)) {
1231 swfdec_as_context_abort (context
, "Runtime exceeded");
1238 * swfdec_as_context_is_aborted:
1239 * @context: a #SwfdecAsContext
1241 * Determines if the given context is aborted. An aborted context is not able
1242 * to execute any scripts. Aborting can happen if the script engine detects bad
1243 * scripts that cause excessive memory usage, infinite loops or other problems.
1244 * In that case the script engine aborts for safety reasons.
1246 * Returns: %TRUE if the player is aborted, %FALSE if it runs normally.
1249 swfdec_as_context_is_aborted (SwfdecAsContext
*context
)
1251 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), TRUE
);
1253 return context
->state
== SWFDEC_AS_CONTEXT_ABORTED
;