Bug 22803 - Make header conform to implementation
[swfdec.git] / swfdec / swfdec_as_context.c
blobce2818872b384b672b535283aef45f179edbe187
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 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
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 gboolean
306 swfdec_as_context_collect_pools (gpointer mem, gpointer pool, gpointer cx)
308 return swfdec_constant_pool_collect (pool);
311 static void
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.
341 void
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.
361 void
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))
368 return;
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))
372 return;
374 switch (type) {
375 case SWFDEC_AS_TYPE_STRING:
376 case SWFDEC_AS_TYPE_NUMBER:
377 SWFDEC_AS_GCABLE_SET_FLAG (gcable, SWFDEC_AS_GC_MARK);
378 break;
379 case SWFDEC_AS_TYPE_OBJECT:
380 swfdec_as_object_mark ((SwfdecAsObject *) gcable);
381 break;
382 case SWFDEC_AS_TYPE_MOVIE:
383 swfdec_as_movie_value_mark ((SwfdecAsMovieValue *) gcable);
384 break;
385 case SWFDEC_AS_TYPE_UNDEFINED:
386 case SWFDEC_AS_TYPE_NULL:
387 case SWFDEC_AS_TYPE_BOOLEAN:
388 case SWFDEC_AS_TYPE_INT:
389 default:
390 g_assert_not_reached ();
391 break;
395 /* FIXME: replace this with refcounted strings? */
396 static void
397 swfdec_as_context_mark_constant_pools (gpointer key, gpointer value, gpointer unused)
399 SwfdecConstantPool *pool = value;
400 guint i;
402 for (i = 0; i < swfdec_constant_pool_size (pool); i++) {
403 const char *s = swfdec_constant_pool_get (pool, i);
404 if (s)
405 swfdec_as_string_mark (s);
409 static void
410 swfdec_as_context_do_mark (SwfdecAsContext *context)
412 /* This if is needed for SwfdecPlayer */
413 if (context->global)
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
427 * allowed.</warning>
429 void
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)
439 return;
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;
448 static gboolean
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
461 * frame advancement.
463 void
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 ***/
476 enum {
477 TRACE,
478 LAST_SIGNAL
481 enum {
482 PROP_0,
483 PROP_DEBUGGER,
484 PROP_RANDOM_SEED,
485 PROP_ABORTED,
486 PROP_UNTIL_GC
489 G_DEFINE_TYPE (SwfdecAsContext, swfdec_as_context, G_TYPE_OBJECT)
490 static guint signals[LAST_SIGNAL] = { 0, };
492 static void
493 swfdec_as_context_get_property (GObject *object, guint param_id, GValue *value,
494 GParamSpec * pspec)
496 SwfdecAsContext *context = SWFDEC_AS_CONTEXT (object);
498 switch (param_id) {
499 case PROP_DEBUGGER:
500 g_value_set_object (value, context->debugger);
501 break;
502 case PROP_ABORTED:
503 g_value_set_boolean (value, context->state == SWFDEC_AS_CONTEXT_ABORTED);
504 break;
505 case PROP_UNTIL_GC:
506 g_value_set_ulong (value, (gulong) context->memory_until_gc);
507 break;
508 default:
509 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
510 break;
514 static void
515 swfdec_as_context_set_property (GObject *object, guint param_id, const GValue *value,
516 GParamSpec * pspec)
518 SwfdecAsContext *context = SWFDEC_AS_CONTEXT (object);
520 switch (param_id) {
521 case PROP_DEBUGGER:
522 context->debugger = SWFDEC_AS_DEBUGGER (g_value_dup_object (value));
523 break;
524 case PROP_RANDOM_SEED:
525 g_rand_set_seed (context->rand, g_value_get_uint (value));
526 break;
527 case PROP_UNTIL_GC:
528 context->memory_until_gc = g_value_get_ulong (value);
529 break;
530 default:
531 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
532 break;
536 static void
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);
565 static void
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;
604 static void
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);
621 /*** STRINGS ***/
623 static const char *
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);
629 new->length = len;
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;
635 return new->string;
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
645 * interpreter.
647 * Returns: the garbage-collected version of @string
649 const char *
650 swfdec_as_context_get_string (SwfdecAsContext *context, const char *string)
652 const SwfdecAsStringValue *ret;
653 gsize len;
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);
659 if (ret)
660 return ret->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
677 const char *
678 swfdec_as_context_give_string (SwfdecAsContext *context, char *string)
680 const char *ret;
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);
686 g_free (string);
687 return ret;
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
702 gboolean
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
719 SwfdecAsFrame *
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.
735 void
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
751 * thrown value
753 * Returns: %TRUE if an exception was catched, %FALSE otherwise
755 gboolean
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)
761 return FALSE;
763 if (value != NULL)
764 *value = context->exception_value;
766 context->exception = FALSE;
767 SWFDEC_AS_VALUE_SET_UNDEFINED (&context->exception_value);
769 return TRUE;
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.
781 void
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);
790 if (klass->get_time)
791 klass->get_time (context, tv);
792 else
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
803 * its parent frame.
805 void
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;
821 } else {
822 SWFDEC_AS_VALUE_SET_UNDEFINED (&retval);
824 /* pop frame and leftover stack */
825 next = frame->next;
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 */
834 if (next) {
835 if (next->stack_begin >= &context->stack->elements[0] &&
836 next->stack_begin <= context->cur) {
837 context->base = next->stack_begin;
838 } else {
839 context->base = &context->stack->elements[0];
841 } else {
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;
848 while (TRUE) {
849 guint n = context->cur - context->base;
850 n = MIN (n, i);
851 swfdec_as_stack_pop_n (context, n);
852 i -= n;
853 if (i == 0)
854 break;
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;
867 } else {
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
882 * application.
883 * <note>A lot of convenience functions like swfdec_as_object_run() call this
884 * function automatically.</note>
886 void
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;
895 #endif
896 guint action, len;
897 const guint8 *data;
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() */
907 /* setup data */
908 frame = context->frame;
909 original_version = context->version;
911 /* sanity checks */
912 if (context->state == SWFDEC_AS_CONTEXT_ABORTED)
913 goto error;
914 if (!swfdec_as_context_check_continue (context))
915 goto error;
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");
919 goto error;
922 if (context->debugger) {
923 SwfdecAsDebuggerClass *klass = SWFDEC_AS_DEBUGGER_GET_CLASS (context->debugger);
924 step = klass->step;
925 } else {
926 step = NULL;
929 script = frame->script;
930 context->version = script->version;
931 startpc = script->buffer->data;
932 endpc = startpc + script->buffer->length;
933 exitpc = script->exit;
934 pc = frame->pc;
935 check_block = TRUE;
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)
941 goto out;
942 pc = frame->pc;
943 continue;
945 if (pc == exitpc) {
946 swfdec_as_context_return (context, NULL);
947 goto out;
949 if (pc < startpc || pc >= endpc) {
950 SWFDEC_ERROR ("pc %p not in valid range [%p, %p) anymore", pc, startpc, endpc);
951 goto error;
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);
956 pc = frame->pc;
957 if (frame != context->frame)
958 goto out;
959 if (context->exception)
960 break;
962 if (context->exception)
963 continue;
965 /* decode next action */
966 action = *pc;
967 /* invoke debugger if there is one */
968 if (step) {
969 frame->pc = pc;
970 (* step) (context->debugger, context);
971 if (frame != context->frame)
972 goto out;
973 if (frame->pc != pc)
974 continue;
976 /* prepare action */
977 spec = swfdec_as_actions + action;
978 if (action & 0x80) {
979 if (pc + 2 >= endpc) {
980 SWFDEC_ERROR ("action %u length value out of range", action);
981 goto error;
983 data = pc + 3;
984 len = pc[1] | pc[2] << 8;
985 if (data + len > endpc) {
986 SWFDEC_ERROR ("action %u length %u out of range", action, len);
987 goto error;
989 nextpc = pc + 3 + len;
990 } else {
991 data = NULL;
992 len = 0;
993 nextpc = pc + 1;
995 /* check action is valid */
996 if (!spec->exec) {
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;
1000 check_block = TRUE;
1001 continue;
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);
1011 } else {
1012 if (spec->add > 0)
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");
1017 goto error;
1019 #ifndef G_DISABLE_ASSERT
1020 check = (spec->add >= 0 && spec->remove >= 0) ? context->cur + spec->add - spec->remove : NULL;
1021 #endif
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;
1028 check_block = TRUE;
1029 } else {
1030 if (frame->pc < pc &&
1031 !swfdec_as_context_check_continue (context)) {
1032 goto error;
1034 pc = frame->pc;
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);
1044 #endif
1045 } else {
1046 /* someone called/returned from a function, reread variables */
1047 goto out;
1051 error:
1052 if (context->frame == frame)
1053 swfdec_as_context_return (context, NULL);
1054 out:
1055 context->version = original_version;
1056 return;
1059 /*** AS CODE ***/
1061 static void
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]);
1069 static gboolean
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]))
1077 return TRUE;
1079 swfdec_as_context_ASSetPropFlags_set_one_flag (object, s, flags);
1080 return TRUE;
1083 SWFDEC_AS_NATIVE (1, 0, swfdec_as_context_ASSetPropFlags)
1084 void
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;
1091 if (argc < 3)
1092 return;
1094 if (!SWFDEC_AS_VALUE_IS_COMPOSITE (argv[0]))
1095 return;
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");
1103 return;
1106 if (SWFDEC_AS_VALUE_IS_NULL (argv[1])) {
1107 swfdec_as_object_foreach (obj, swfdec_as_context_ASSetPropFlags_foreach, flags);
1108 } else {
1109 char **split =
1110 g_strsplit (swfdec_as_value_to_string (cx, argv[1]), ",", -1);
1111 guint i;
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);
1116 g_strfreev (split);
1120 SWFDEC_AS_NATIVE (200, 19, swfdec_as_context_isFinite)
1121 void
1122 swfdec_as_context_isFinite (SwfdecAsContext *cx, SwfdecAsObject *object,
1123 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1125 double d;
1127 if (argc < 1)
1128 return;
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)
1135 void
1136 swfdec_as_context_isNaN (SwfdecAsContext *cx, SwfdecAsObject *object,
1137 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1139 double d;
1141 if (argc < 1)
1142 return;
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)
1149 void
1150 swfdec_as_context_parseInt (SwfdecAsContext *cx, SwfdecAsObject *object,
1151 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1153 const char *s;
1154 char *tail;
1155 int radix = 10;
1156 gint64 i;
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);
1162 return;
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);
1169 return;
1172 // automatic radix
1173 if (argc < 2) {
1174 if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
1175 radix = 16;
1176 } else if ((s[0] == '0' || ((s[0] == '+' || s[0] == '-') && s[1] == '0')) &&
1177 s[strspn (s+1, "01234567") + 1] == '\0') {
1178 radix = 8;
1179 } else {
1180 radix = 10;
1184 // skip 0x at the start
1185 if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
1186 s += 2;
1188 // strtoll parses strings with 0x when given radix 16, but we don't want that
1189 if (radix == 16) {
1190 const char *skip = s + strspn (s, " \t\r\n");
1191 if (skip != s && (skip[0] == '-' || skip[0] == '+'))
1192 skip++;
1193 if (skip != s && skip[0] == '0' && (skip[1] == 'x' || skip[1] == 'X')) {
1194 *retval = swfdec_as_value_from_number (cx, 0);
1195 return;
1199 i = g_ascii_strtoll (s, &tail, radix);
1201 if (tail == s) {
1202 *retval = swfdec_as_value_from_number (cx, NAN);
1203 return;
1206 if (i > G_MAXINT32 || i < G_MININT32) {
1207 *retval = swfdec_as_value_from_number (cx, i);
1208 } else {
1209 *retval = swfdec_as_value_from_integer (cx, i);
1213 SWFDEC_AS_NATIVE (100, 3, swfdec_as_context_parseFloat)
1214 void
1215 swfdec_as_context_parseFloat (SwfdecAsContext *cx, SwfdecAsObject *object,
1216 guint argc, SwfdecAsValue *argv, SwfdecAsValue *retval)
1218 char *s, *p, *tail;
1219 double d;
1221 if (argc < 1)
1222 return;
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) {
1228 *p = '\0';
1231 d = g_ascii_strtod (s, &tail);
1233 if (tail == s) {
1234 *retval = swfdec_as_value_from_number (cx, NAN);
1235 } else {
1236 *retval = swfdec_as_value_from_number (cx, d);
1239 g_free (s);
1242 static void
1243 swfdec_as_context_init_global (SwfdecAsContext *context)
1245 SwfdecAsValue val;
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);
1253 void
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);
1261 if (version > 4) {
1262 SwfdecBits bits;
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");
1268 return;
1270 swfdec_as_object_run (context->global, script);
1271 swfdec_script_unref (script);
1272 } else {
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.
1284 void
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))
1292 return;
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.
1318 gboolean
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)
1327 return TRUE;
1328 if (!klass->check_continue (context)) {
1329 swfdec_as_context_abort (context, "Runtime exceeded");
1330 return FALSE;
1332 return TRUE;
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.
1346 gboolean
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;