make swfdec_as_object_mark() only mark if not marked yet
[swfdec.git] / swfdec / swfdec_as_context.c
blob8bec3c11371cc5b8ff510fb854365ad8bd1e6c38
1 /* Swfdec
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.
8 *
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
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
24 #include <math.h>
25 #include <string.h>
26 #include <stdlib.h>
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 ***/
49 /**
50 * SECTION:Internals
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
58 * this section.
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
69 * lookups.
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(),
92 * respectively.
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.
108 /*** GTK-DOC ***/
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
122 * debugger.
123 * <note>The Actionscript engine is similar, but not equal to Javascript. It
124 * is not very different, but it is different.</note>
128 * SwfdecAsContext
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
134 * encouraged.
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
143 * debugger
144 * @SWFDEC_AS_CONTEXT_ABORTED: the context has aborted execution due to a
145 * fatal error
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.
164 void
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.
189 gboolean
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)
196 return FALSE;
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);
203 return TRUE;
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
214 * memory error.
216 void
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.
239 void
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);
251 /*** GC ***/
253 static void
254 swfdec_as_context_remove_gc_objects (SwfdecAsContext *context)
256 SwfdecGcObject *gc, *prev, *next;
258 prev = NULL;
259 gc = context->gc_objects;
260 while (gc) {
261 next = gc->next;
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);
267 prev = gc;
268 } else {
269 SWFDEC_LOG ("deleted: %s %p", G_OBJECT_TYPE_NAME (gc), gc);
270 g_object_unref (gc);
271 if (prev) {
272 prev->next = next;
273 } else {
274 context->gc_objects = next;
277 gc = next;
281 static void
282 swfdec_as_context_collect_string (SwfdecAsContext *context, gpointer gc)
284 SwfdecAsStringValue *string;
286 string = gc;
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);
293 static void
294 swfdec_as_context_collect_double (SwfdecAsContext *context, gpointer gc)
296 swfdec_as_gcable_free (context, gc, sizeof (SwfdecAsDoubleValue));
299 static void
300 swfdec_as_context_collect_movie (SwfdecAsContext *context, gpointer gc)
302 swfdec_as_movie_value_free ((SwfdecAsMovieValue *) gc);
305 static void
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.
332 void
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.
352 void
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? */
370 static void
371 swfdec_as_context_mark_constant_pools (gpointer key, gpointer value, gpointer unused)
373 SwfdecConstantPool *pool = value;
374 guint i;
376 for (i = 0; i < swfdec_constant_pool_size (pool); i++) {
377 const char *s = swfdec_constant_pool_get (pool, i);
378 if (s)
379 swfdec_as_string_mark (s);
383 static void
384 swfdec_as_context_do_mark (SwfdecAsContext *context)
386 /* This if is needed for SwfdecPlayer */
387 if (context->global)
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
401 * allowed.</warning>
403 void
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)
413 return;
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;
422 static gboolean
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
435 * frame advancement.
437 void
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 ***/
450 enum {
451 TRACE,
452 LAST_SIGNAL
455 enum {
456 PROP_0,
457 PROP_DEBUGGER,
458 PROP_RANDOM_SEED,
459 PROP_ABORTED,
460 PROP_UNTIL_GC
463 G_DEFINE_TYPE (SwfdecAsContext, swfdec_as_context, G_TYPE_OBJECT)
464 static guint signals[LAST_SIGNAL] = { 0, };
466 static void
467 swfdec_as_context_get_property (GObject *object, guint param_id, GValue *value,
468 GParamSpec * pspec)
470 SwfdecAsContext *context = SWFDEC_AS_CONTEXT (object);
472 switch (param_id) {
473 case PROP_DEBUGGER:
474 g_value_set_object (value, context->debugger);
475 break;
476 case PROP_ABORTED:
477 g_value_set_boolean (value, context->state == SWFDEC_AS_CONTEXT_ABORTED);
478 break;
479 case PROP_UNTIL_GC:
480 g_value_set_ulong (value, (gulong) context->memory_until_gc);
481 break;
482 default:
483 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
484 break;
488 static void
489 swfdec_as_context_set_property (GObject *object, guint param_id, const GValue *value,
490 GParamSpec * pspec)
492 SwfdecAsContext *context = SWFDEC_AS_CONTEXT (object);
494 switch (param_id) {
495 case PROP_DEBUGGER:
496 context->debugger = SWFDEC_AS_DEBUGGER (g_value_dup_object (value));
497 break;
498 case PROP_RANDOM_SEED:
499 g_rand_set_seed (context->rand, g_value_get_uint (value));
500 break;
501 case PROP_UNTIL_GC:
502 context->memory_until_gc = g_value_get_ulong (value);
503 break;
504 default:
505 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
506 break;
510 static void
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);
539 static void
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;
578 static void
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);
595 /*** STRINGS ***/
597 static const char *
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);
603 new->length = len;
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;
609 return new->string;
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
619 * interpreter.
621 * Returns: the garbage-collected version of @string
623 const char *
624 swfdec_as_context_get_string (SwfdecAsContext *context, const char *string)
626 const SwfdecAsStringValue *ret;
627 gsize len;
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);
633 if (ret)
634 return ret->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
651 const char *
652 swfdec_as_context_give_string (SwfdecAsContext *context, char *string)
654 const char *ret;
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);
660 g_free (string);
661 return ret;
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
676 gboolean
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
693 SwfdecAsFrame *
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.
709 void
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
725 * thrown value
727 * Returns: %TRUE if an exception was catched, %FALSE otherwise
729 gboolean
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)
735 return FALSE;
737 if (value != NULL)
738 *value = context->exception_value;
740 context->exception = FALSE;
741 SWFDEC_AS_VALUE_SET_UNDEFINED (&context->exception_value);
743 return TRUE;
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.
755 void
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);
764 if (klass->get_time)
765 klass->get_time (context, tv);
766 else
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
777 * its parent frame.
779 void
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;
795 } else {
796 SWFDEC_AS_VALUE_SET_UNDEFINED (&retval);
798 /* pop frame and leftover stack */
799 next = frame->next;
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 */
808 if (next) {
809 if (next->stack_begin >= &context->stack->elements[0] &&
810 next->stack_begin <= context->cur) {
811 context->base = next->stack_begin;
812 } else {
813 context->base = &context->stack->elements[0];
815 } else {
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;
822 while (TRUE) {
823 guint n = context->cur - context->base;
824 n = MIN (n, i);
825 swfdec_as_stack_pop_n (context, n);
826 i -= n;
827 if (i == 0)
828 break;
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;
841 } else {
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
856 * application.
857 * <note>A lot of convenience functions like swfdec_as_object_run() call this
858 * function automatically.</note>
860 void
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;
869 #endif
870 guint action, len;
871 const guint8 *data;
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() */
881 /* setup data */
882 frame = context->frame;
883 original_version = context->version;
885 /* sanity checks */
886 if (context->state == SWFDEC_AS_CONTEXT_ABORTED)
887 goto error;
888 if (!swfdec_as_context_check_continue (context))
889 goto error;
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");
893 goto error;
896 if (context->debugger) {
897 SwfdecAsDebuggerClass *klass = SWFDEC_AS_DEBUGGER_GET_CLASS (context->debugger);
898 step = klass->step;
899 } else {
900 step = NULL;
903 script = frame->script;
904 context->version = script->version;
905 startpc = script->buffer->data;
906 endpc = startpc + script->buffer->length;
907 exitpc = script->exit;
908 pc = frame->pc;
909 check_block = TRUE;
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)
915 goto out;
916 pc = frame->pc;
917 continue;
919 if (pc == exitpc) {
920 swfdec_as_context_return (context, NULL);
921 goto out;
923 if (pc < startpc || pc >= endpc) {
924 SWFDEC_ERROR ("pc %p not in valid range [%p, %p) anymore", pc, startpc, endpc);
925 goto error;
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);
930 pc = frame->pc;
931 if (frame != context->frame)
932 goto out;
933 if (context->exception)
934 break;
936 if (context->exception)
937 continue;
939 /* decode next action */
940 action = *pc;
941 /* invoke debugger if there is one */
942 if (step) {
943 frame->pc = pc;
944 (* step) (context->debugger, context);
945 if (frame != context->frame)
946 goto out;
947 if (frame->pc != pc)
948 continue;
950 /* prepare action */
951 spec = swfdec_as_actions + action;
952 if (action & 0x80) {
953 if (pc + 2 >= endpc) {
954 SWFDEC_ERROR ("action %u length value out of range", action);
955 goto error;
957 data = pc + 3;
958 len = pc[1] | pc[2] << 8;
959 if (data + len > endpc) {
960 SWFDEC_ERROR ("action %u length %u out of range", action, len);
961 goto error;
963 nextpc = pc + 3 + len;
964 } else {
965 data = NULL;
966 len = 0;
967 nextpc = pc + 1;
969 /* check action is valid */
970 if (!spec->exec) {
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;
974 check_block = TRUE;
975 continue;
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);
985 } else {
986 if (spec->add > 0)
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");
991 goto error;
993 #ifndef G_DISABLE_ASSERT
994 check = (spec->add >= 0 && spec->remove >= 0) ? context->cur + spec->add - spec->remove : NULL;
995 #endif
996 /* execute action */
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;
1002 check_block = TRUE;
1003 } else {
1004 if (frame->pc < pc &&
1005 !swfdec_as_context_check_continue (context)) {
1006 goto error;
1008 pc = frame->pc;
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);
1018 #endif
1019 } else {
1020 /* someone called/returned from a function, reread variables */
1021 goto out;
1025 error:
1026 if (context->frame == frame)
1027 swfdec_as_context_return (context, NULL);
1028 out:
1029 context->version = original_version;
1030 return;
1033 /*** AS CODE ***/
1035 static void
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]);
1043 static gboolean
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]))
1051 return TRUE;
1053 swfdec_as_context_ASSetPropFlags_set_one_flag (object, s, flags);
1054 return TRUE;
1057 SWFDEC_AS_NATIVE (1, 0, swfdec_as_context_ASSetPropFlags)
1058 void
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;
1065 if (argc < 3)
1066 return;
1068 if (!SWFDEC_AS_VALUE_IS_COMPOSITE (&argv[0]))
1069 return;
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");
1077 return;
1080 if (SWFDEC_AS_VALUE_IS_NULL (&argv[1])) {
1081 swfdec_as_object_foreach (obj, swfdec_as_context_ASSetPropFlags_foreach, flags);
1082 } else {
1083 char **split =
1084 g_strsplit (swfdec_as_value_to_string (cx, argv[1]), ",", -1);
1085 guint i;
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);
1090 g_strfreev (split);
1094 SWFDEC_AS_NATIVE (200, 19, swfdec_as_context_isFinite)
1095 void
1096 swfdec_as_context_isFinite (SwfdecAsContext *cx, SwfdecAsObject *object,
1097 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1099 double d;
1101 if (argc < 1)
1102 return;
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)
1109 void
1110 swfdec_as_context_isNaN (SwfdecAsContext *cx, SwfdecAsObject *object,
1111 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1113 double d;
1115 if (argc < 1)
1116 return;
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)
1123 void
1124 swfdec_as_context_parseInt (SwfdecAsContext *cx, SwfdecAsObject *object,
1125 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1127 const char *s;
1128 char *tail;
1129 int radix = 10;
1130 gint64 i;
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);
1136 return;
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);
1143 return;
1146 // automatic radix
1147 if (argc < 2) {
1148 if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
1149 radix = 16;
1150 } else if ((s[0] == '0' || ((s[0] == '+' || s[0] == '-') && s[1] == '0')) &&
1151 s[strspn (s+1, "01234567") + 1] == '\0') {
1152 radix = 8;
1153 } else {
1154 radix = 10;
1158 // skip 0x at the start
1159 if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
1160 s += 2;
1162 // strtoll parses strings with 0x when given radix 16, but we don't want that
1163 if (radix == 16) {
1164 const char *skip = s + strspn (s, " \t\r\n");
1165 if (skip != s && (skip[0] == '-' || skip[0] == '+'))
1166 skip++;
1167 if (skip != s && skip[0] == '0' && (skip[1] == 'x' || skip[1] == 'X')) {
1168 swfdec_as_value_set_number (cx, retval, 0);
1169 return;
1173 i = g_ascii_strtoll (s, &tail, radix);
1175 if (tail == s) {
1176 swfdec_as_value_set_number (cx, retval, NAN);
1177 return;
1180 if (i > G_MAXINT32 || i < G_MININT32) {
1181 swfdec_as_value_set_number (cx, retval, i);
1182 } else {
1183 swfdec_as_value_set_integer (cx, retval, i);
1187 SWFDEC_AS_NATIVE (100, 3, swfdec_as_context_parseFloat)
1188 void
1189 swfdec_as_context_parseFloat (SwfdecAsContext *cx, SwfdecAsObject *object,
1190 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1192 char *s, *p, *tail;
1193 double d;
1195 if (argc < 1)
1196 return;
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) {
1202 *p = '\0';
1205 d = g_ascii_strtod (s, &tail);
1207 if (tail == s) {
1208 swfdec_as_value_set_number (cx, retval, NAN);
1209 } else {
1210 swfdec_as_value_set_number (cx, retval, d);
1213 g_free (s);
1216 static void
1217 swfdec_as_context_init_global (SwfdecAsContext *context)
1219 SwfdecAsValue val;
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);
1227 void
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);
1235 if (version > 4) {
1236 SwfdecBits bits;
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");
1242 return;
1244 swfdec_as_object_run (context->global, script);
1245 swfdec_script_unref (script);
1246 } else {
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.
1258 void
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))
1266 return;
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.
1292 gboolean
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)
1301 return TRUE;
1302 if (!klass->check_continue (context)) {
1303 swfdec_as_context_abort (context, "Runtime exceeded");
1304 return FALSE;
1306 return TRUE;
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.
1320 gboolean
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;