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 used 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_pools (gpointer mem
, gpointer pool
, gpointer cx
)
308 return swfdec_constant_pool_collect (pool
);
312 swfdec_as_context_collect (SwfdecAsContext
*context
)
314 /* NB: This functions is called without GC from swfdec_as_context_dispose */
315 SWFDEC_INFO (">> collecting garbage");
317 swfdec_as_context_remove_gc_objects (context
);
319 context
->objects
= swfdec_as_gcable_collect (context
, context
->objects
,
320 (SwfdecAsGcableDestroyNotify
) swfdec_as_object_free
);
321 context
->strings
= swfdec_as_gcable_collect (context
, context
->strings
,
322 swfdec_as_context_collect_string
);
323 context
->numbers
= swfdec_as_gcable_collect (context
, context
->numbers
,
324 swfdec_as_context_collect_double
);
325 context
->movies
= swfdec_as_gcable_collect (context
, context
->movies
,
326 swfdec_as_context_collect_movie
);
328 g_hash_table_foreach_remove (context
->constant_pools
,
329 swfdec_as_context_collect_pools
, context
);
331 SWFDEC_INFO (">> done collecting garbage");
335 * swfdec_as_string_mark:
336 * @string: a garbage-collected string
338 * Mark @string as being in use. Calling this function is only valid during
339 * the marking phase of garbage collection.
342 swfdec_as_string_mark (const char *string
)
344 SwfdecAsStringValue
*value
;
346 g_return_if_fail (string
!= NULL
);
348 value
= (SwfdecAsStringValue
*) (gpointer
) ((guint8
*) string
- G_STRUCT_OFFSET (SwfdecAsStringValue
, string
));
349 if (!SWFDEC_AS_GCABLE_FLAG_IS_SET (value
, SWFDEC_AS_GC_ROOT
))
350 SWFDEC_AS_GCABLE_SET_FLAG (value
, SWFDEC_AS_GC_MARK
);
354 * swfdec_as_value_mark:
355 * @value: a #SwfdecAsValue
357 * Mark @value as being in use. This is just a convenience function that calls
358 * the right marking function depending on the value's type. Calling this
359 * function is only valid during the marking phase of garbage collection.
362 swfdec_as_value_mark (SwfdecAsValue
*value
)
364 SwfdecAsGcable
*gcable
;
365 SwfdecAsValueType type
= SWFDEC_AS_VALUE_GET_TYPE (*value
);
367 if (!SWFDEC_AS_TYPE_IS_GCABLE (type
))
370 gcable
= SWFDEC_AS_VALUE_GET_VALUE (*value
);
371 if (SWFDEC_AS_GCABLE_FLAG_IS_SET (gcable
, SWFDEC_AS_GC_ROOT
| SWFDEC_AS_GC_MARK
))
375 case SWFDEC_AS_TYPE_STRING
:
376 case SWFDEC_AS_TYPE_NUMBER
:
377 SWFDEC_AS_GCABLE_SET_FLAG (gcable
, SWFDEC_AS_GC_MARK
);
379 case SWFDEC_AS_TYPE_OBJECT
:
380 swfdec_as_object_mark ((SwfdecAsObject
*) gcable
);
382 case SWFDEC_AS_TYPE_MOVIE
:
383 swfdec_as_movie_value_mark ((SwfdecAsMovieValue
*) gcable
);
385 case SWFDEC_AS_TYPE_UNDEFINED
:
386 case SWFDEC_AS_TYPE_NULL
:
387 case SWFDEC_AS_TYPE_BOOLEAN
:
388 case SWFDEC_AS_TYPE_INT
:
390 g_assert_not_reached ();
395 /* FIXME: replace this with refcounted strings? */
397 swfdec_as_context_mark_constant_pools (gpointer key
, gpointer value
, gpointer unused
)
399 SwfdecConstantPool
*pool
= value
;
402 for (i
= 0; i
< swfdec_constant_pool_size (pool
); i
++) {
403 const char *s
= swfdec_constant_pool_get (pool
, i
);
405 swfdec_as_string_mark (s
);
410 swfdec_as_context_do_mark (SwfdecAsContext
*context
)
412 /* This if is needed for SwfdecPlayer */
414 swfdec_as_object_mark (context
->global
);
415 if (context
->exception
)
416 swfdec_as_value_mark (&context
->exception_value
);
417 g_hash_table_foreach (context
->constant_pools
, swfdec_as_context_mark_constant_pools
, NULL
);
421 * swfdec_as_context_gc:
422 * @context: a #SwfdecAsContext
424 * Calls the Swfdec Gargbage collector and reclaims any unused memory. You
425 * should call this function or swfdec_as_context_maybe_gc() regularly.
426 * <warning>Calling the GC during execution of code or initialization is not
430 swfdec_as_context_gc (SwfdecAsContext
*context
)
432 SwfdecAsContextClass
*klass
;
434 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
435 g_return_if_fail (context
->frame
== NULL
);
436 g_return_if_fail (context
->state
== SWFDEC_AS_CONTEXT_RUNNING
);
438 if (context
->state
== SWFDEC_AS_CONTEXT_ABORTED
)
440 SWFDEC_INFO ("invoking the garbage collector");
441 klass
= SWFDEC_AS_CONTEXT_GET_CLASS (context
);
442 g_assert (klass
->mark
);
443 klass
->mark (context
);
444 swfdec_as_context_collect (context
);
445 context
->memory_since_gc
= 0;
449 swfdec_as_context_needs_gc (SwfdecAsContext
*context
)
451 return context
->memory_since_gc
>= context
->memory_until_gc
;
455 * swfdec_as_context_maybe_gc:
456 * @context: a #SwfdecAsContext
458 * Calls the garbage collector if necessary. It's a good idea to call this
459 * function regularly instead of swfdec_as_context_gc() as it only does collect
460 * garage as needed. For example, #SwfdecPlayer calls this function after every
464 swfdec_as_context_maybe_gc (SwfdecAsContext
*context
)
466 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
467 g_return_if_fail (context
->state
== SWFDEC_AS_CONTEXT_RUNNING
);
468 g_return_if_fail (context
->frame
== NULL
);
470 if (swfdec_as_context_needs_gc (context
))
471 swfdec_as_context_gc (context
);
474 /*** SWFDEC_AS_CONTEXT ***/
489 G_DEFINE_TYPE (SwfdecAsContext
, swfdec_as_context
, G_TYPE_OBJECT
)
490 static guint signals
[LAST_SIGNAL
] = { 0, };
493 swfdec_as_context_get_property (GObject
*object
, guint param_id
, GValue
*value
,
496 SwfdecAsContext
*context
= SWFDEC_AS_CONTEXT (object
);
500 g_value_set_object (value
, context
->debugger
);
503 g_value_set_boolean (value
, context
->state
== SWFDEC_AS_CONTEXT_ABORTED
);
506 g_value_set_ulong (value
, (gulong
) context
->memory_until_gc
);
509 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
515 swfdec_as_context_set_property (GObject
*object
, guint param_id
, const GValue
*value
,
518 SwfdecAsContext
*context
= SWFDEC_AS_CONTEXT (object
);
522 context
->debugger
= SWFDEC_AS_DEBUGGER (g_value_dup_object (value
));
524 case PROP_RANDOM_SEED
:
525 g_rand_set_seed (context
->rand
, g_value_get_uint (value
));
528 context
->memory_until_gc
= g_value_get_ulong (value
);
531 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
537 swfdec_as_context_dispose (GObject
*object
)
539 SwfdecAsContext
*context
= SWFDEC_AS_CONTEXT (object
);
541 while (context
->stack
)
542 swfdec_as_stack_pop_segment (context
);
543 /* We need to make sure there's no exception here. Otherwise collecting
544 * frames that are inside a try block will assert */
545 swfdec_as_context_catch (context
, NULL
);
546 swfdec_as_context_collect (context
);
547 if (context
->memory
!= 0) {
548 g_critical ("%zu bytes of memory left over\n", context
->memory
);
550 g_assert (context
->strings
== NULL
);
551 g_assert (context
->numbers
== NULL
);
552 g_assert (g_hash_table_size (context
->constant_pools
) == 0);
553 g_assert (context
->gc_objects
== 0);
554 g_hash_table_destroy (context
->constant_pools
);
555 g_hash_table_destroy (context
->interned_strings
);
556 g_rand_free (context
->rand
);
557 if (context
->debugger
) {
558 g_object_unref (context
->debugger
);
559 context
->debugger
= NULL
;
562 G_OBJECT_CLASS (swfdec_as_context_parent_class
)->dispose (object
);
566 swfdec_as_context_class_init (SwfdecAsContextClass
*klass
)
568 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
570 object_class
->dispose
= swfdec_as_context_dispose
;
571 object_class
->get_property
= swfdec_as_context_get_property
;
572 object_class
->set_property
= swfdec_as_context_set_property
;
574 g_object_class_install_property (object_class
, PROP_DEBUGGER
,
575 g_param_spec_object ("debugger", "debugger", "debugger used in this player",
576 SWFDEC_TYPE_AS_DEBUGGER
, G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
577 g_object_class_install_property (object_class
, PROP_RANDOM_SEED
,
578 g_param_spec_uint ("random-seed", "random seed",
579 "seed used for calculating random numbers",
580 0, G_MAXUINT32
, 0, G_PARAM_WRITABLE
)); /* FIXME: make this readwrite for replaying? */
581 g_object_class_install_property (object_class
, PROP_ABORTED
,
582 g_param_spec_boolean ("aborted", "aborted", "set when the script engine aborts due to an error",
583 FALSE
, G_PARAM_READABLE
));
584 g_object_class_install_property (object_class
, PROP_UNTIL_GC
,
585 g_param_spec_ulong ("memory-until-gc", "memory until gc",
586 "amount of bytes that need to be allocated before garbage collection triggers",
587 0, G_MAXULONG
, 8 * 1024 * 1024, G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
));
590 * SwfdecAsContext::trace:
591 * @context: the #SwfdecAsContext affected
592 * @text: the debugging string
594 * Emits a debugging string while running. The effect of calling any swfdec
595 * functions on the emitting @context is undefined.
597 signals
[TRACE
] = g_signal_new ("trace", G_TYPE_FROM_CLASS (klass
),
598 G_SIGNAL_RUN_LAST
, 0, NULL
, NULL
, g_cclosure_marshal_VOID__STRING
,
599 G_TYPE_NONE
, 1, G_TYPE_STRING
);
601 klass
->mark
= swfdec_as_context_do_mark
;
605 swfdec_as_context_init (SwfdecAsContext
*context
)
607 const SwfdecAsConstantStringValue
*s
;
609 context
->version
= G_MAXUINT
;
611 context
->interned_strings
= g_hash_table_new (g_str_hash
, g_str_equal
);
612 context
->constant_pools
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
614 for (s
= swfdec_as_strings
; s
->next
; s
++) {
615 g_hash_table_insert (context
->interned_strings
, (gpointer
) s
->string
, (gpointer
) s
);
617 context
->rand
= g_rand_new ();
618 g_get_current_time (&context
->start_time
);
624 swfdec_as_context_create_string (SwfdecAsContext
*context
, const char *string
, gsize len
)
626 SwfdecAsStringValue
*new;
628 new = swfdec_as_gcable_alloc (context
, sizeof (SwfdecAsStringValue
) + len
+ 1);
630 memcpy (new->string
, string
, new->length
+ 1);
631 g_hash_table_insert (context
->interned_strings
, new->string
, new);
632 SWFDEC_AS_GCABLE_SET_NEXT (new, context
->strings
);
633 context
->strings
= new;
639 * swfdec_as_context_get_string:
640 * @context: a #SwfdecAsContext
641 * @string: a sting that is not garbage-collected
643 * Gets the garbage-collected version of @string. You need to call this function
644 * for every not garbage-collected string that you want to use in Swfdecs script
647 * Returns: the garbage-collected version of @string
650 swfdec_as_context_get_string (SwfdecAsContext
*context
, const char *string
)
652 const SwfdecAsStringValue
*ret
;
655 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), NULL
);
656 g_return_val_if_fail (string
!= NULL
, NULL
);
658 ret
= g_hash_table_lookup (context
->interned_strings
, string
);
662 len
= strlen (string
);
663 return swfdec_as_context_create_string (context
, string
, len
);
667 * swfdec_as_context_give_string:
668 * @context: a #SwfdecAsContext
669 * @string: string to make refcounted
671 * Takes ownership of @string and returns a refcounted version of the same
672 * string. This function is the same as swfdec_as_context_get_string(), but
673 * takes ownership of @string.
675 * Returns: A refcounted string
678 swfdec_as_context_give_string (SwfdecAsContext
*context
, char *string
)
682 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), NULL
);
683 g_return_val_if_fail (string
!= NULL
, NULL
);
685 ret
= swfdec_as_context_get_string (context
, string
);
691 * swfdec_as_context_is_constructing:
692 * @context: a #SwfdecAsConstruct
694 * Determines if the contexxt is currently constructing. This information is
695 * used by various constructors to do different things when they are
696 * constructing and when they are not. The Boolean, Number and String functions
697 * for example setup the newly constructed objects when constructing but only
698 * cast the provided argument when being called.
700 * Returns: %TRUE if the currently executing frame is a constructor
703 swfdec_as_context_is_constructing (SwfdecAsContext
*context
)
705 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), FALSE
);
707 return context
->frame
&& context
->frame
->construct
;
711 * swfdec_as_context_get_frame:
712 * @context: a #SwfdecAsContext
714 * This is a debugging function. It gets the topmost stack frame that is
715 * currently executing. If no function is executing, %NULL is returned.
717 * Returns: the currently executing frame or %NULL if none
720 swfdec_as_context_get_frame (SwfdecAsContext
*context
)
722 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), NULL
);
724 return context
->frame
;
728 * swfdec_as_context_throw:
729 * @context: a #SwfdecAsContext
730 * @value: a #SwfdecAsValue to be thrown
732 * Throws a new exception in the @context using the given @value. This function
733 * can only be called if the @context is not already throwing an exception.
736 swfdec_as_context_throw (SwfdecAsContext
*context
, const SwfdecAsValue
*value
)
738 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
739 g_return_if_fail (!context
->exception
);
741 context
->exception
= TRUE
;
742 context
->exception_value
= *value
;
746 * swfdec_as_context_catch:
747 * @context: a #SwfdecAsContext
748 * @value: a #SwfdecAsValue to be thrown
750 * Removes the currently thrown exception from @context and sets @value to the
753 * Returns: %TRUE if an exception was catched, %FALSE otherwise
756 swfdec_as_context_catch (SwfdecAsContext
*context
, SwfdecAsValue
*value
)
758 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), FALSE
);
760 if (!context
->exception
)
764 *value
= context
->exception_value
;
766 context
->exception
= FALSE
;
767 SWFDEC_AS_VALUE_SET_UNDEFINED (&context
->exception_value
);
773 * swfdec_as_context_get_time:
774 * @context: a #SwfdecAsContext
775 * @tv: a #GTimeVal to be set to the context's time
777 * This function queries the time to be used inside this context. By default,
778 * this is the same as g_get_current_time(), but it may be overwriten to allow
779 * things such as slower or faster playback.
782 swfdec_as_context_get_time (SwfdecAsContext
*context
, GTimeVal
*tv
)
784 SwfdecAsContextClass
*klass
;
786 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
787 g_return_if_fail (tv
!= NULL
);
789 klass
= SWFDEC_AS_CONTEXT_GET_CLASS (context
);
791 klass
->get_time (context
, tv
);
793 g_get_current_time (tv
);
797 * swfdec_as_context_return:
798 * @context: the context to return the topmost frame in
799 * @return_value: return value of the function or %NULL for none. An undefined
800 * value will be used in that case.
802 * Ends execution of the currently executing frame and continues execution with
806 swfdec_as_context_return (SwfdecAsContext
*context
, SwfdecAsValue
*return_value
)
808 SwfdecAsValue retval
;
809 SwfdecAsFrame
*frame
, *next
;
811 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
812 g_return_if_fail (context
->frame
!= NULL
);
814 frame
= context
->frame
;
816 /* save return value in case it was on the stack somewhere */
817 if (frame
->construct
) {
818 retval
= frame
->thisp
;
819 } else if (return_value
) {
820 retval
= *return_value
;
822 SWFDEC_AS_VALUE_SET_UNDEFINED (&retval
);
824 /* pop frame and leftover stack */
826 context
->frame
= next
;
827 g_assert (context
->call_depth
> 0);
828 context
->call_depth
--;
829 while (context
->base
> frame
->stack_begin
||
830 context
->end
< frame
->stack_begin
)
831 swfdec_as_stack_pop_segment (context
);
832 context
->cur
= frame
->stack_begin
;
833 /* setup stack for previous frame */
835 if (next
->stack_begin
>= &context
->stack
->elements
[0] &&
836 next
->stack_begin
<= context
->cur
) {
837 context
->base
= next
->stack_begin
;
839 context
->base
= &context
->stack
->elements
[0];
842 g_assert (context
->stack
->next
== NULL
);
843 context
->base
= &context
->stack
->elements
[0];
845 /* pop argv if on stack */
846 if (frame
->argv
== NULL
&& frame
->argc
> 0) {
847 guint i
= frame
->argc
;
849 guint n
= context
->cur
- context
->base
;
851 swfdec_as_stack_pop_n (context
, n
);
855 swfdec_as_stack_pop_segment (context
);
858 if (context
->debugger
) {
859 SwfdecAsDebuggerClass
*klass
= SWFDEC_AS_DEBUGGER_GET_CLASS (context
->debugger
);
861 if (klass
->leave_frame
)
862 klass
->leave_frame (context
->debugger
, context
, frame
, &retval
);
864 /* set return value */
865 if (frame
->return_value
) {
866 *frame
->return_value
= retval
;
868 swfdec_as_stack_ensure_free (context
, 1);
869 *swfdec_as_stack_push (context
) = retval
;
871 swfdec_as_frame_free (context
, frame
);
875 * swfdec_as_context_run:
876 * @context: a #SwfdecAsContext
878 * Continues running the script engine. Executing code in this engine works
879 * in 2 steps: First, you push the frame to be executed onto the stack, then
880 * you call this function to execute it. So this function is the single entry
881 * point to script execution. This might be helpful when debugging your
883 * <note>A lot of convenience functions like swfdec_as_object_run() call this
884 * function automatically.</note>
887 swfdec_as_context_run (SwfdecAsContext
*context
)
889 SwfdecAsFrame
*frame
;
890 SwfdecScript
*script
;
891 const SwfdecActionSpec
*spec
;
892 const guint8
*startpc
, *pc
, *endpc
, *nextpc
, *exitpc
;
893 #ifndef G_DISABLE_ASSERT
894 SwfdecAsValue
*check
;
898 guint original_version
;
899 void (* step
) (SwfdecAsDebugger
*debugger
, SwfdecAsContext
*context
);
900 gboolean check_block
; /* some opcodes avoid a scope check */
902 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
903 g_return_if_fail (context
->frame
!= NULL
);
904 g_return_if_fail (context
->frame
->script
!= NULL
);
905 g_return_if_fail (context
->global
); /* check here because of swfdec_sandbox_(un)use() */
908 frame
= context
->frame
;
909 original_version
= context
->version
;
912 if (context
->state
== SWFDEC_AS_CONTEXT_ABORTED
)
914 if (!swfdec_as_context_check_continue (context
))
916 if (context
->call_depth
> 256) {
917 /* we've exceeded our maximum call depth, throw an error and abort */
918 swfdec_as_context_abort (context
, "Stack overflow");
922 if (context
->debugger
) {
923 SwfdecAsDebuggerClass
*klass
= SWFDEC_AS_DEBUGGER_GET_CLASS (context
->debugger
);
929 script
= frame
->script
;
930 context
->version
= script
->version
;
931 startpc
= script
->buffer
->data
;
932 endpc
= startpc
+ script
->buffer
->length
;
933 exitpc
= script
->exit
;
937 while (context
->state
< SWFDEC_AS_CONTEXT_ABORTED
) {
938 if (context
->exception
) {
939 swfdec_as_frame_handle_exception (context
, frame
);
940 if (frame
!= context
->frame
)
946 swfdec_as_context_return (context
, NULL
);
949 if (pc
< startpc
|| pc
>= endpc
) {
950 SWFDEC_ERROR ("pc %p not in valid range [%p, %p) anymore", pc
, startpc
, endpc
);
953 while (check_block
&& (pc
< frame
->block_start
|| pc
>= frame
->block_end
)) {
954 SWFDEC_LOG ("code exited block");
955 swfdec_as_frame_pop_block (frame
, context
);
957 if (frame
!= context
->frame
)
959 if (context
->exception
)
962 if (context
->exception
)
965 /* decode next action */
967 /* invoke debugger if there is one */
970 (* step
) (context
->debugger
, context
);
971 if (frame
!= context
->frame
)
977 spec
= swfdec_as_actions
+ action
;
979 if (pc
+ 2 >= endpc
) {
980 SWFDEC_ERROR ("action %u length value out of range", action
);
984 len
= pc
[1] | pc
[2] << 8;
985 if (data
+ len
> endpc
) {
986 SWFDEC_ERROR ("action %u length %u out of range", action
, len
);
989 nextpc
= pc
+ 3 + len
;
995 /* check action is valid */
997 SWFDEC_WARNING ("cannot interpret action %3u 0x%02X %s for version %u, skipping it", action
,
998 action
, spec
->name
? spec
->name
: "Unknown", script
->version
);
999 frame
->pc
= pc
= nextpc
;
1003 if (script
->version
< spec
->version
) {
1004 SWFDEC_WARNING ("cannot interpret action %3u 0x%02X %s for version %u, using version %u instead",
1005 action
, action
, spec
->name
? spec
->name
: "Unknown", script
->version
, spec
->version
);
1007 if (spec
->remove
> 0) {
1008 if (spec
->add
> spec
->remove
)
1009 swfdec_as_stack_ensure_free (context
, spec
->add
- spec
->remove
);
1010 swfdec_as_stack_ensure_size (context
, spec
->remove
);
1013 swfdec_as_stack_ensure_free (context
, spec
->add
);
1015 if (context
->state
> SWFDEC_AS_CONTEXT_RUNNING
) {
1016 SWFDEC_WARNING ("context not running anymore, aborting");
1019 #ifndef G_DISABLE_ASSERT
1020 check
= (spec
->add
>= 0 && spec
->remove
>= 0) ? context
->cur
+ spec
->add
- spec
->remove
: NULL
;
1022 /* execute action */
1023 spec
->exec (context
, action
, data
, len
);
1024 /* adapt the pc if the action did not, otherwise, leave it alone */
1025 /* FIXME: do this via flag? */
1026 if (frame
->pc
== pc
) {
1027 frame
->pc
= pc
= nextpc
;
1030 if (frame
->pc
< pc
&&
1031 !swfdec_as_context_check_continue (context
)) {
1035 check_block
= FALSE
;
1037 if (frame
== context
->frame
) {
1038 #ifndef G_DISABLE_ASSERT
1039 if (check
!= NULL
&& check
!= context
->cur
) {
1040 g_error ("action %s was supposed to change the stack by %d (+%d -%d), but it changed by %td",
1041 spec
->name
, spec
->add
- spec
->remove
, spec
->add
, spec
->remove
,
1042 context
->cur
- check
+ spec
->add
- spec
->remove
);
1046 /* someone called/returned from a function, reread variables */
1052 if (context
->frame
== frame
)
1053 swfdec_as_context_return (context
, NULL
);
1055 context
->version
= original_version
;
1062 swfdec_as_context_ASSetPropFlags_set_one_flag (SwfdecAsObject
*object
,
1063 const char *s
, guint
*flags
)
1065 swfdec_as_object_unset_variable_flags (object
, s
, flags
[1]);
1066 swfdec_as_object_set_variable_flags (object
, s
, flags
[0]);
1070 swfdec_as_context_ASSetPropFlags_foreach (SwfdecAsObject
*object
,
1071 const char *s
, SwfdecAsValue
*val
, guint cur_flags
, gpointer data
)
1073 guint
*flags
= data
;
1075 /* shortcut if the flags already match */
1076 if (cur_flags
== ((cur_flags
&~ flags
[1]) | flags
[0]))
1079 swfdec_as_context_ASSetPropFlags_set_one_flag (object
, s
, flags
);
1083 SWFDEC_AS_NATIVE (1, 0, swfdec_as_context_ASSetPropFlags
)
1085 swfdec_as_context_ASSetPropFlags (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1086 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1088 guint flags
[2]; /* flags and mask - array so we can pass it as data pointer */
1089 SwfdecAsObject
*obj
;
1094 if (!SWFDEC_AS_VALUE_IS_COMPOSITE (argv
[0]))
1096 obj
= SWFDEC_AS_VALUE_GET_COMPOSITE (argv
[0]);
1097 flags
[0] = swfdec_as_value_to_integer (cx
, argv
[2]);
1098 flags
[1] = (argc
> 3) ? swfdec_as_value_to_integer (cx
, argv
[3]) : 0;
1100 if (flags
[0] == 0 && flags
[1] == 0) {
1101 // we should add autosizing length attribute here
1102 SWFDEC_FIXME ("ASSetPropFlags to set special length attribute not implemented");
1106 if (SWFDEC_AS_VALUE_IS_NULL (argv
[1])) {
1107 swfdec_as_object_foreach (obj
, swfdec_as_context_ASSetPropFlags_foreach
, flags
);
1110 g_strsplit (swfdec_as_value_to_string (cx
, argv
[1]), ",", -1);
1112 for (i
= 0; split
[i
]; i
++) {
1113 swfdec_as_context_ASSetPropFlags_set_one_flag (obj
,
1114 swfdec_as_context_get_string (cx
, split
[i
]), flags
);
1120 SWFDEC_AS_NATIVE (200, 19, swfdec_as_context_isFinite
)
1122 swfdec_as_context_isFinite (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1123 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1130 d
= swfdec_as_value_to_number (cx
, argv
[0]);
1131 SWFDEC_AS_VALUE_SET_BOOLEAN (retval
, isfinite (d
) ? TRUE
: FALSE
);
1134 SWFDEC_AS_NATIVE (200, 18, swfdec_as_context_isNaN
)
1136 swfdec_as_context_isNaN (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1137 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1144 d
= swfdec_as_value_to_number (cx
, argv
[0]);
1145 SWFDEC_AS_VALUE_SET_BOOLEAN (retval
, isnan (d
) ? TRUE
: FALSE
);
1148 SWFDEC_AS_NATIVE (100, 2, swfdec_as_context_parseInt
)
1150 swfdec_as_context_parseInt (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1151 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1158 SWFDEC_AS_CHECK (0, NULL
, "s|i", &s
, &radix
);
1160 if (argc
>= 2 && (radix
< 2 || radix
> 36)) {
1161 *retval
= swfdec_as_value_from_number (cx
, NAN
);
1165 // special case, don't allow sign in front of the 0x
1166 if ((s
[0] == '-' || s
[0] == '+') && s
[1] == '0' &&
1167 (s
[2] == 'x' || s
[2] == 'X')) {
1168 *retval
= swfdec_as_value_from_number (cx
, NAN
);
1174 if (s
[0] == '0' && (s
[1] == 'x' || s
[1] == 'X')) {
1176 } else if ((s
[0] == '0' || ((s
[0] == '+' || s
[0] == '-') && s
[1] == '0')) &&
1177 s
[strspn (s
+1, "01234567") + 1] == '\0') {
1184 // skip 0x at the start
1185 if (s
[0] == '0' && (s
[1] == 'x' || s
[1] == 'X'))
1188 // strtoll parses strings with 0x when given radix 16, but we don't want that
1190 const char *skip
= s
+ strspn (s
, " \t\r\n");
1191 if (skip
!= s
&& (skip
[0] == '-' || skip
[0] == '+'))
1193 if (skip
!= s
&& skip
[0] == '0' && (skip
[1] == 'x' || skip
[1] == 'X')) {
1194 *retval
= swfdec_as_value_from_number (cx
, 0);
1199 i
= g_ascii_strtoll (s
, &tail
, radix
);
1202 *retval
= swfdec_as_value_from_number (cx
, NAN
);
1206 if (i
> G_MAXINT32
|| i
< G_MININT32
) {
1207 *retval
= swfdec_as_value_from_number (cx
, i
);
1209 *retval
= swfdec_as_value_from_integer (cx
, i
);
1213 SWFDEC_AS_NATIVE (100, 3, swfdec_as_context_parseFloat
)
1215 swfdec_as_context_parseFloat (SwfdecAsContext
*cx
, SwfdecAsObject
*object
,
1216 guint argc
, SwfdecAsValue
*argv
, SwfdecAsValue
*retval
)
1224 // we need to remove everything after x or I, since strtod parses hexadecimal
1225 // numbers and Infinity
1226 s
= g_strdup (swfdec_as_value_to_string (cx
, argv
[0]));
1227 if ((p
= strpbrk (s
, "xXiI")) != NULL
) {
1231 d
= g_ascii_strtod (s
, &tail
);
1234 *retval
= swfdec_as_value_from_number (cx
, NAN
);
1236 *retval
= swfdec_as_value_from_number (cx
, d
);
1243 swfdec_as_context_init_global (SwfdecAsContext
*context
)
1247 val
= swfdec_as_value_from_number (context
, NAN
);
1248 swfdec_as_object_set_variable (context
->global
, SWFDEC_AS_STR_NaN
, &val
);
1249 val
= swfdec_as_value_from_number (context
, HUGE_VAL
);
1250 swfdec_as_object_set_variable (context
->global
, SWFDEC_AS_STR_Infinity
, &val
);
1254 swfdec_as_context_run_init_script (SwfdecAsContext
*context
, const guint8
*data
,
1255 gsize length
, guint version
)
1257 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
1258 g_return_if_fail (data
!= NULL
);
1259 g_return_if_fail (length
> 0);
1263 SwfdecScript
*script
;
1264 swfdec_bits_init_data (&bits
, data
, length
);
1265 script
= swfdec_script_new_from_bits (&bits
, "init", version
);
1266 if (script
== NULL
) {
1267 g_warning ("script passed to swfdec_as_context_run_init_script is invalid");
1270 swfdec_as_object_run (context
->global
, script
);
1271 swfdec_script_unref (script
);
1273 SWFDEC_LOG ("not running init script, since version is <= 4");
1278 * swfdec_as_context_startup:
1279 * @context: a #SwfdecAsContext
1281 * Starts up the context. This function must be called before any Actionscript
1282 * is called on @context.
1285 swfdec_as_context_startup (SwfdecAsContext
*context
)
1287 g_return_if_fail (SWFDEC_IS_AS_CONTEXT (context
));
1288 g_return_if_fail (context
->state
== SWFDEC_AS_CONTEXT_NEW
);
1290 if (context
->cur
== NULL
&&
1291 !swfdec_as_stack_push_segment (context
))
1293 if (context
->global
== NULL
)
1294 context
->global
= swfdec_as_object_new_empty (context
);
1295 /* init the two internal functions */
1296 /* FIXME: remove them for normal contexts? */
1297 swfdec_player_preinit_global (context
);
1298 /* get the necessary objects up to define objects and functions sanely */
1299 swfdec_as_object_init_context (context
);
1300 /* define the global object and other important ones */
1301 swfdec_as_context_init_global (context
);
1303 /* run init script */
1304 swfdec_as_context_run_init_script (context
, swfdec_as_initialize
, sizeof (swfdec_as_initialize
), 8);
1306 if (context
->state
== SWFDEC_AS_CONTEXT_NEW
)
1307 context
->state
= SWFDEC_AS_CONTEXT_RUNNING
;
1311 * swfdec_as_context_check_continue:
1312 * @context: the context that might be running too long
1314 * Checks if the context has been running too long. If it has, it gets aborted.
1316 * Returns: %TRUE if this player aborted.
1319 swfdec_as_context_check_continue (SwfdecAsContext
*context
)
1321 SwfdecAsContextClass
*klass
;
1323 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), TRUE
);
1325 klass
= SWFDEC_AS_CONTEXT_GET_CLASS (context
);
1326 if (klass
->check_continue
== NULL
)
1328 if (!klass
->check_continue (context
)) {
1329 swfdec_as_context_abort (context
, "Runtime exceeded");
1336 * swfdec_as_context_is_aborted:
1337 * @context: a #SwfdecAsContext
1339 * Determines if the given context is aborted. An aborted context is not able
1340 * to execute any scripts. Aborting can happen if the script engine detects bad
1341 * scripts that cause excessive memory usage, infinite loops or other problems.
1342 * In that case the script engine aborts for safety reasons.
1344 * Returns: %TRUE if the player is aborted, %FALSE if it runs normally.
1347 swfdec_as_context_is_aborted (SwfdecAsContext
*context
)
1349 g_return_val_if_fail (SWFDEC_IS_AS_CONTEXT (context
), TRUE
);
1351 return context
->state
== SWFDEC_AS_CONTEXT_ABORTED
;